我可以使用什么强类型数据结构来保存具有不同形状的多个 RecordSet 的集合
What Strongly-Typed Data-Structure can I use to hold a collection of multiple RecordSets with different shape
应我最初问题 here 的回复者的要求,我被要求改写问题以努力弄清实际要求的底部。
我可以使用什么 Strongly-Typed 数据结构来保存多个 RecordSets 的集合,其中每个 RecordSet 将包含可能与其他行形状不同的行 RecordSets?
这种需求是由于需要通过 DbDataReader 处理从 Stored Procedure 返回的数据。 存储过程可能有多个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());
}
}
示例 DTO:
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; }
}
理想情况下,我不想要 objects 或 dynamics 的列表,而是我的记录集内容的 DTO 列表。希望这能更好地澄清我原来的问题。
阅读原始问题和这个问题后,听起来您最终会得到一个列表,其中包含其他列表,每个列表都有自己的类型。当您遍历列表时,您将不知道内部列表的类型是什么,因为它是可变的。鉴于此,您将不可避免地必须测试类型然后进行转换,例如
foreach(var resultset in resultsets)
{
if(resultset is MultipleRecordSetStoredProcedureReturnType1)
//... cast it and do something...
}
考虑到你不可避免地要测试内部类型和转换,即使你能实现你的目标,我不确定它最终是否会起到任何作用。该列表也可能只是
List<List<Object>>
或者也许
List<List<IResultSet>>
根据最后一个示例,您可以考虑为内部类型定义一个接口,以确保其中只包含从该接口继承的对象。即使接口没有定义任何行为而只是一个简单的声明,这也可能是有益的。
我不确定 return 包含不同结果的 List
是否有意义。我可能会创建一个包装器 class 来对你的 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; }
}
如果您想使数据库代码更进一步以使其通用化,您可以按照这些思路做一些事情。
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;
}
我承认这不是最漂亮或可读的代码,但它应该允许您调用任何多记录集存储过程,只要 属性 名称与列名称匹配。此代码的一个缺点是,虽然编译器将强制您在表达式中仅使用 select IList
属性,但它无法确保您选择通用列表(方法本身实际上需要)。
对于您的示例,该方法将按如下方式调用
CallMultipleRecordSetStoredProcedure<RecordSetContainer>(
rsc => rsc.RecordSet1,
rsc => rsc.RecordSet2,
rsc => rsc.RecordSet3);
在这种情况下,我认为最好让事情保持简单。
- 您有一个包含 returns 3 个结果集的存储过程,因此创建一个包含这 3 个结果集的模型。
- 要填充你的
ResultModel
,最好使用 SqlDataAdapter
和 DataSet
。它使事情变得非常简单,比数据 reader. 更容易
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; }
}
您可以使用 SqlDataAdapter 和 DataSet 填充您的 ResultModel:
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
- 使用
SqlDataAdapter
和 DataSet
可以轻松读取多个结果。
- 使用
Cast<DataRow>()
使我们能够使用 Linq
对抗 DataTable.Rows
- 使用
Field<T>("field")
使我们能够获取字段的类型值
应我最初问题 here 的回复者的要求,我被要求改写问题以努力弄清实际要求的底部。
我可以使用什么 Strongly-Typed 数据结构来保存多个 RecordSets 的集合,其中每个 RecordSet 将包含可能与其他行形状不同的行 RecordSets?
这种需求是由于需要通过 DbDataReader 处理从 Stored Procedure 返回的数据。 存储过程可能有多个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());
}
}
示例 DTO:
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; }
}
理想情况下,我不想要 objects 或 dynamics 的列表,而是我的记录集内容的 DTO 列表。希望这能更好地澄清我原来的问题。
阅读原始问题和这个问题后,听起来您最终会得到一个列表,其中包含其他列表,每个列表都有自己的类型。当您遍历列表时,您将不知道内部列表的类型是什么,因为它是可变的。鉴于此,您将不可避免地必须测试类型然后进行转换,例如
foreach(var resultset in resultsets)
{
if(resultset is MultipleRecordSetStoredProcedureReturnType1)
//... cast it and do something...
}
考虑到你不可避免地要测试内部类型和转换,即使你能实现你的目标,我不确定它最终是否会起到任何作用。该列表也可能只是
List<List<Object>>
或者也许
List<List<IResultSet>>
根据最后一个示例,您可以考虑为内部类型定义一个接口,以确保其中只包含从该接口继承的对象。即使接口没有定义任何行为而只是一个简单的声明,这也可能是有益的。
我不确定 return 包含不同结果的 List
是否有意义。我可能会创建一个包装器 class 来对你的 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; }
}
如果您想使数据库代码更进一步以使其通用化,您可以按照这些思路做一些事情。
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;
}
我承认这不是最漂亮或可读的代码,但它应该允许您调用任何多记录集存储过程,只要 属性 名称与列名称匹配。此代码的一个缺点是,虽然编译器将强制您在表达式中仅使用 select IList
属性,但它无法确保您选择通用列表(方法本身实际上需要)。
对于您的示例,该方法将按如下方式调用
CallMultipleRecordSetStoredProcedure<RecordSetContainer>(
rsc => rsc.RecordSet1,
rsc => rsc.RecordSet2,
rsc => rsc.RecordSet3);
在这种情况下,我认为最好让事情保持简单。
- 您有一个包含 returns 3 个结果集的存储过程,因此创建一个包含这 3 个结果集的模型。
- 要填充你的
ResultModel
,最好使用SqlDataAdapter
和DataSet
。它使事情变得非常简单,比数据 reader. 更容易
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; }
}
您可以使用 SqlDataAdapter 和 DataSet 填充您的 ResultModel:
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
- 使用
SqlDataAdapter
和DataSet
可以轻松读取多个结果。 - 使用
Cast<DataRow>()
使我们能够使用Linq
对抗DataTable.Rows
- 使用
Field<T>("field")
使我们能够获取字段的类型值