我可以使用什么强类型数据结构来保存具有不同形状的多个记录集的集合

本文关键字:集合 记录 什么 可以使 强类型 数据结构 保存 我可以 | 更新日期: 2023-09-27 18:14:34

应回复我最初问题的人的要求,我被要求重新表述这个问题,以努力达到实际需求的底部。

我可以使用什么强类型数据结构来保存多个RecordSets其中每个RecordSet将包含可能与其他RecordSets具有不同形状的行?

这个需求是由于需要处理从存储过程通过DbDataReader返回的数据。存储过程可以有多个RecordSet,每个RecordSet返回不同的列和列数。

将有DTO类来表示各自数据集的每个。这些在编译时是已知的。我需要的是一个数据结构,可以容纳多个RecordSets,并且对于每个RecordSet保持DTOs表示从RecordSet返回的行。

存储过程示例:
CREATE PROCEDURE [dbo].[MultipleRecordSetStoredProcedure]
    @Id                 INT
,   @Name               VARCHAR(20)
,   @Active             BIT
,   @Price              DECIMAL(10, 4)
,   @UniqueIdentifier   UNIQUEIDENTIFIER
,   @Count              TINYINT
AS
BEGIN
    /* First Record Set */
    SELECT 
        @Id AS Id
    ,   @Name AS Name
    UNION
    SELECT
        17 AS Id
    ,   'Bill' AS Name;
    /* Second Record Set */
    SELECT 
        @Name AS Name,
        @Active as Active
    UNION
    SELECT
        'Bill' AS Name
    ,   CAST(0 AS BIT) AS Active;
    /* Third Record Set */
    SELECT
        @UniqueIdentifier   AS UNIQUEIDENTIFIER
    ,   @Count              AS Count
    UNION
    SELECT
        NEWID() AS UNIQUEIDENTIFIER
   ,    CAST(10 AS TINYINT) AS Count;
END

示例调用代码:

DbConnection connection = CreateConnection();
CommandBehavior commandBehavior = CommandBehavior.Default;
// Create a command to execute the stored storedProcedure
using (DbCommand command = connection.CreateStoredProcedureCommand(
    procedureName,
    procedureParameters,
    commandTimeout,
    transaction))
{
    // Populate a DataReder by calling the command
    using (DbDataReader reader = command.ExecuteReader(commandBehavior))
    {
        // Iterate through each result set...
        do
        {
            // Process the result set line by line
            while (reader.Read())
            {   
                // Read data into DataStructure
            }
        } while (reader.NextResult());
    }    
}

示例:

internal class MultipleRecordSetStoredProcedureReturnType1
{
    public int Id { get; set; }
    public string Name { get; set; }
}
internal class MultipleRecordSetStoredProcedureReturnType2
{
    public bool Active { get; set; }
    public decimal Decimal { get; set; }
}
internal class MultipleRecordSetStoredProcedureReturnType3
{
    public Guid UniqueIdentifier { get; set; }
    public Byte Count { get; set; }
}

理想情况下,我不想要一个对象列表动态列表,而是一个记录集内容的dto列表。希望这能更好地澄清我最初的问题。

我可以使用什么强类型数据结构来保存具有不同形状的多个记录集的集合

在这种情况下,我认为最好保持简单。

  1. 您有一个返回3个结果集的存储过程,因此创建一个包含这3个结果集的模型。
  2. 要填充ResultModel,最好使用SqlDataAdapterDataSet。它使事情变得非常简单,比数据阅读器更容易。

ResultModel代码:

public class ResultModel
{
    public List<Type1> List1 { get; set; }
    public List<Type2> List2 { get; set; }
    public List<Type3> List3 { get; set; }
}

我想这些是你的类型:

public class Type1
{
    public int A { get; set; }
}
public class Type2
{
    public int B { get; set; }
    public string C { get; set; }
}
public class Type3
{
    public int D { get; set; }
    public string E { get; set; }
    public string F { get; set; }
}

你可以填充你的ResultModel,使用SqlDataAdapter和DataSet:

public ResultModel GetData()
{
    var connection = @"data source=(localdb)'v11.0;initial catalog=TestDB;integrated security=True;MultipleActiveResultSets=True;";
    var command = "dbo.Procedure";
    var tableAdapter = new System.Data.SqlClient.SqlDataAdapter(command, connection);
    tableAdapter.SelectCommand.CommandType = CommandType.StoredProcedure;
    var dataSet = new DataSet();
    tableAdapter.Fill(dataSet); 
    var t1 = dataSet.Tables[0].Rows.Cast<DataRow>()
        .ToList().Select(row => new Type1
        {
            A = row.Field<int>("A"),
        }).ToList();
    var t2 = dataSet.Tables[1].Rows.Cast<DataRow>()
        .ToList().Select(row => new Type2
        {
            B = row.Field<int>("B"),
            C = row.Field<string>("C")
        }).ToList();
    var t3 = dataSet.Tables[1].Rows.Cast<DataRow>()
        .ToList().Select(row => new Type3
        {
            D = row.Field<int>("D"),
            E = row.Field<string>("E"),
            F = row.Field<string>("F")
        }).ToList();
    var result = new ResultModel() { List1 = t1, List2 = t2, List3 = t3 };
    return result;
}

这里的关键点是:

    我们有一个干净简单的ResultModel
  • 使用SqlDataAdapterDataSet可以轻松读取多个结果。
  • 使用Cast<DataRow>()使我们能够使用Linq来对抗DataTable.Rows
  • 使用Field<T>("field")使我们能够获得字段
  • 的类型值

我不确定返回包含不同结果的List是否有意义。我可能会创建一个包装器类来对dto对象进行分组,如下所示。

public class RecordSetContainer
{
    public RecordSetContainer()
    {
        RecordSet1 = new List<MultipleRecordSetStoredProcedureReturnType1>();
        RecordSet2 = new List<MultipleRecordSetStoredProcedureReturnType2>();
        RecordSet3 = new List<MultipleRecordSetStoredProcedureReturnType3>();
    }
    public List<MultipleRecordSetStoredProcedureReturnType1> RecordSet1 { get; set; }
    public List<MultipleRecordSetStoredProcedureReturnType2> RecordSet2 { get; set; }
    public List<MultipleRecordSetStoredProcedureReturnType3> RecordSet3 { get; set; }
}

如果你想让db代码更进一步,使其泛型,你可以按照这些行做一些事情。

public T CallMultipleRecordSetStoredProcedure<T>(
    params Expression<Func<T, IList>>[] recordSetPropertiesInOrder)
    where T : class, new()
{
    var outputType = typeof (T);
    var output = new T();
    // DbConnection & Command setup hidden 
    var recordSetNumber = 0;
    do
    {
        var outputRecordSetPropertyName =
            ((MemberExpression) recordSetPropertiesInOrder[recordSetNumber].Body).Member.Name;
        var dtoList = (IList) outputType.GetProperty(outputRecordSetPropertyName).GetValue(output);
        var dtoListType = dtoList.GetType().GetGenericArguments()[0];
        while (reader.Read())
        {
            var item = Activator.CreateInstance(dtoListType);
            var propertiesToWrite = dtoListType.GetProperties().Where(p => p.CanWrite);
            foreach (var property in propertiesToWrite)
            {
                property.SetValue(
                    item,
                    Convert.ChangeType(reader[property.Name], property.PropertyType));
            }
            dtoList.Add(item);
        }
        recordSetNumber++;
    } while (reader.NextResult());
    return output;
}
我承认,

不是最漂亮或可读性最好的代码,但它应该允许您调用任何多记录集存储过程,只要属性名称与列名匹配。这段代码的一个缺点是,虽然编译器将强制您只在表达式中选择IList属性,但它无法确保您选择泛型列表(方法本身实际上需要)。

对于您的示例,该方法将如下所示调用

CallMultipleRecordSetStoredProcedure<RecordSetContainer>(
    rsc => rsc.RecordSet1,
    rsc => rsc.RecordSet2,
    rsc => rsc.RecordSet3);

在阅读了最初的问题和这个问题之后,听起来您最终会得到一个列表,其中包含其他列表,每个列表都有自己的类型。在遍历列表时,您将不知道内部列表的类型是什么,因为它是变量。鉴于此,您将不可避免地必须测试类型,然后强制转换它,例如

foreach(var resultset in resultsets)
{
    if(resultset is MultipleRecordSetStoredProcedureReturnType1)
        //... cast it and do something...
}

考虑到您将不可避免地必须测试内部类型和强制类型转换,即使您可以实现您的目标,我也不确定它最终会起到任何作用。列表也可以只是

List<List<Object>>

或者

List<List<IResultSet>>
根据最后一个例子,您可以考虑为内部类型定义一个接口,以确保只包含从该接口继承的对象。这可能是有益的,即使接口没有定义任何行为,只是一个简单的声明。