ADO.NET如何将SQLDataReader映射到领域对象?

3
我有一个非常简单的映射函数叫做“BuildEntity”,它执行通常需要进行的“左/右”编码,将读取器数据转储到我的域对象中。如果我不完全按原样返回此映射中的每一列,则会出现“System.IndexOutOfRangeException”异常,并想知道ado.net是否有任何方法可以纠正此问题,以便我不需要在每次调用SQL时都带回每一列...
我真正寻找的是类似于“IsValidColumn”的东西,这样我就可以在我的DataAccess类中保持这个1个映射函数,并定义所有的左/右映射 - 即使存储过程没有返回每个列也可以工作...
Using reader As SqlDataReader = cmd.ExecuteReader()
  Dim product As Product
  While reader.Read()
    product = New Product()
    product.ID = Convert.ToInt32(reader("ProductID"))
    product.SupplierID = Convert.ToInt32(reader("SupplierID"))
    product.CategoryID = Convert.ToInt32(reader("CategoryID"))
    product.ProductName = Convert.ToString(reader("ProductName"))
    product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
    product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
    product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
    product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
    product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
    productList.Add(product)
  End While
8个回答

6

还要查看我编写的扩展方法,用于数据命令:

public static void Fill<T>(this IDbCommand cmd,
    IList<T> list, Func<IDataReader, T> rowConverter)
{
    using (var rdr = cmd.ExecuteReader())
    {
        while (rdr.Read())
        {
            list.Add(rowConverter(rdr));
        }
    }
}

你可以像这样使用它:
cmd.Fill(products, r => r.GetProduct());

其中,“products”是您想要填充的IList<Product>,而“GetProduct”包含从数据读取器创建Product实例的逻辑。它不能解决没有所有字段的特定问题,但如果您像这样经常使用老式的ADO.NET,它可能非常方便。


1
使用GetSchemaTable()方法检索DataReader的元数据。返回的DataTable可用于检查特定列是否存在。

1
为什么不让每个存储过程返回完整的列集,使用 null、-1 或可接受的值来代替没有数据的情况。这样可以避免捕获 IndexOutOfRangeException 或在 LinqToSql 中重新编写所有内容。

1

虽然connection.GetSchema("Tables")可以返回有关数据库中表的元数据,但如果定义了任何自定义列,则不会返回存储过程中的所有内容。

例如,如果您添加了一些随机的临时列,如*SELECT ProductName,'Testing' As ProductTestName FROM dbo.Products",则不会将“ProductTestName”显示为列,因为它不在Products表的模式中。为了解决这个问题,并要求返回的数据中的每个列,请利用SqlDataReader对象上的一个方法“GetSchemaTable()”

如果我将此代码添加到您在原始问题中列出的现有代码示例中,您将注意到在声明读取器后,我添加了一个数据表来捕获读取器本身的元数据。接下来,我循环遍历此元数据,并将每个列添加到另一个表中,我在左右代码中使用该表来检查每个列是否存在。

更新的源代码

Using reader As SqlDataReader = cmd.ExecuteReader() 
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
 colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product  While reader.Read()    
product = New Product()  
If Not colNames.Columns("ProductID") Is Nothing Then
  product.ID = Convert.ToInt32(reader("ProductID"))
End If    
product.SupplierID = Convert.ToInt32(reader("SupplierID"))    
product.CategoryID = Convert.ToInt32(reader("CategoryID"))    
product.ProductName = Convert.ToString(reader("ProductName"))    
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))    
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))    
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))    
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))    
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))    
productList.Add(product)  
End While

说实话,这是一个hack,因为你应该返回每一列以正确地填充你的对象。但我想包括这个reader方法,因为它实际上会获取所有列,即使它们在你的表模式中没有定义。

当你进入延迟加载场景时,这种将关系数据映射到域模型的方法可能会引起一些问题。


0

0

为什么不使用 LinqToSql - 一切都会自动完成。为了通用性,您可以使用任何其他 .NET ORM 工具


0

在开始 while 循环之前,我将为每个字段名称调用 reader.GetOrdinal。不幸的是,如果字段不存在,GetOrdinal 抛出一个 IndexOutOfRangeException,所以它的性能不会很好。

您可能可以将结果存储在 Dictionary<string, int> 中,并使用它的 ContainsKey 方法来确定是否提供了该字段。


0

如果您不想使用ORM,您也可以使用反射来处理此类事情(尽管在这种情况下,因为ProductID在两侧的命名不同,您无法像这里演示的那样以简单的方式完成): C#中的提供程序列表


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接