从SQLDataReader到域对象的ADO.NET映射?

时间:2020-03-05 18:41:58  来源:igfitidea点击:

我有一个非常简单的映射函数,称为" BuildEntity",该函数执行将阅读器数据转储到域对象中所需的通常无聊的"左/右"编码。 (如下所示)我的问题是,如果我不按原样带回此映射中的所有列,则会收到" System.IndexOutOfRangeException"异常,并想知道ado.net是否有任何内容可以更正此错误,所以我没有需要带回每次调用SQL的每一列...

我真正想要的是类似" IsValidColumn"的东西,因此我可以在DataAccess类中保留所有定义了左/右映射的1映射函数,即使当sproc不会返回列出的每个列时,它也可以正常工作.. 。

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

解决方案

回答

我们为什么不使用LinqToSql呢,我们所需的一切都是自动完成的。为了通用起见,我们可以将任何其他ORM工具用于.NET

回答

在启动while循环之前,我将为每个字段名称调用reader.GetOrdinal。不幸的是,如果该字段不存在,GetOrdinal会抛出IndexOutOfRangeException,因此它的性能不会很高。

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

回答

还要检查一下我编写的用于数据命令的扩展方法:

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());

其中"产品"是要填充的IList <Product>," GetProduct"包含从数据读取器创建Product实例的逻辑。没有所有字段存在的特定问题将无济于事,但是,如果我们要像这样做很多老式的ADO.NET,它将非常方便。

回答

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

回答

为什么不让每个sproc返回完整的列集,在没有数据的情况下使用null,-1或者可接受的值。避免必须捕获IndexOutOfRangeException或者重写LinqToSql中的所有内容。

回答

如果我们不想使用ORM,则也可以对此类内容使用反射(尽管在这种情况下,因为ProductID在两侧均没有相同的名称,所以我们不能以此处演示的简单方式进行操作):
C#中的列表提供程序

回答

尽管connection.GetSchema(" Tables")确实返回有关数据库中表的元数据,但是如果我们定义任何自定义列,它将不会在存储过程中返回所有内容。

例如,如果我们抛出一些随机的临时列,例如* SELECT ProductName,将'Testing'作为ProductTestName FROM dbo.Products",我们将不会看到'ProductTestName'作为列,因为它不在Products表的Schema中。若要解决此问题,并要求返回数据中的每一列,请在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

老实说,这是一种技巧,因为我们应该返回每一列以正确填充对象。但是我想包括此读取器方法,因为它实际上将捕获所有列,即使未在表模式中定义它们也是如此。

当我们进入惰性加载方案时,这种将关系数据映射到域模型的方法可能会导致一些问题。