LINQ Entity Data Reader

原文:http://archive.msdn.microsoft.com/LinqEntityDataReader


ts a really good class to help speed up db inserts when using entity framework or object collections.


Resource Page Description
An IDataReader implementation that reads a collecion of entities to enable using SqlbulkCopy with LINQ. 


This simple class wraps a collection of CLR objects (IEnumerable<T>) and exposes the collection as an IDataReader. This is usefull for using SqlBulkCopy with collections of entities, especially entities generated by LINQ to SQL and Entity Framework.

For instance, here's a snippet of code that creates a collection of entity objects and uses SqlBulkCopy to load them into a SQL Server table.

Notice the AsDataReader extension method that transforms the entity collection into an IDataReader.

    static int SendOrders(int totalToSend)
    {
      using (SqlConnection con = new SqlConnection(connectionString))
      {
        con.Open();
        using (SqlTransaction tran = con.BeginTransaction())
        {
          var newOrders =
                  from i in Enumerable.Range(0, totalToSend)
                  select new Order
                  {
                    customer_name = "Customer " + i % 100,
                    quantity = i % 9,
                    order_id = i,
                    order_entry_date = DateTime.Now
                  };
 
          SqlBulkCopy bc = new SqlBulkCopy(con,
            SqlBulkCopyOptions.CheckConstraints |
            SqlBulkCopyOptions.FireTriggers |
            SqlBulkCopyOptions.KeepNulls, tran);
 
          bc.BatchSize = 1000;
          bc.DestinationTableName = "order_queue";
          bc.WriteToServer(newOrders.AsDataReader()); 
 
          tran.Commit();
        }
        con.Close();
 
      }
 
      return totalToSend;
 
    }
 

Also can be used to load a DataTable from an IEnumerable<T>, like this

    static void Main(string[] args)
    {
      using (var db = new testDataContext())
      {
        var q = from o in db.sales_facts
                select o;
 
        DataTable t = q.ToDataTable();
        
 
        t.WriteXml(Console.Out);
      }
      Console.ReadKey();
    }
  }

The EntityDataReader wraps a collection of CLR objects in a DbDataReader. Only "scalar" properties are projected, with the exception that Entity Framework EntityObjects that have references to other EntityObjects will have key values for the related entity projected. This is useful for doing high-speed data loads with SqlBulkCopy, and copying collections of entities ot a DataTable for use with SQL Server Table-Valued parameters, or for interop with older ADO.NET applciations.

For explicit control over the fields projected by the DataReader, just wrap your collection of entities in a anonymous type projection before wrapping it in an EntityDataReader.

For explicit mapping, instead of 
      IEnumerable<Order> orders;
      ...
      IDataReader dr = orders.AsDataReader();
      
 
do
      IEnumerable<Order> orders;
      ...
      var q = from o in orders
              select new 
              {
                 ID=o.ID,
                 ShipDate=o.ShipDate,
                 ProductName=o.Product.Name,
                 ...
              }
      IDataReader dr = q.AsDataReader();
 

The EntityDataReader now uses dynamic methods to access the properties on your collection of objects, providing dramatically improved performance over using Reflection for property accessors. Common CLR scalar types and nullable value types are all enabled for dynamic method access. 

Here's a simple example of using the DataReader to pass a collection to a SQL Server Table-Valued Parameter (TVP). This just passes a list of integers, but you could pass a full collection of entities the same way.

        using (SqlConnection con = new SqlConnection("Data Source=(local);Database=AdventureWorks;Integrated Security=true"))
        {
          con.Open();
 
          SqlCommand cmd = new SqlCommand(
              @"select max(LineTotal) 
              from Sales.SalesOrderDetail 
              where SalesOrderId in (select Value from @ids)", con);
          SqlParameter pIds = cmd.Parameters.Add(new SqlParameter("@ids", SqlDbType.Structured));
 
          //created with this DDL: CREATE TYPE Int_TableType AS TABLE(Value int NOT NULL)
          pIds.TypeName = "Int_TableType";
 
          //create a list of ID's
          var ids = Enumerable.Range(43659, 1000);
          pIds.Value = ids.AsDataReader();
 
          object val = cmd.ExecuteScalar();
 
          Console.WriteLine(val);
        }

Here's another TVP sample, this time using a multi-column TVP to pass a collection of entities to SQL 2008 and MERGE them into a table.

      using (var db = new AdventureWorksContextDataContext())
      using (var con = db.Connection)
      {
        //db.ObjectTrackingEnabled = false;
        var lo = new System.Data.Linq.DataLoadOptions();
        lo.LoadWith<SalesOrderHeader>(o => o.SalesOrderDetail );
        db.LoadOptions = lo;
        db.Connection.Open();
 
        var customerId = (from o in db.SalesOrderHeaders
                          where o.SalesOrderDetails.Count() > 2
                          select o).Take(1).First().CustomerID;
              
        
        var q = from o in db.SalesOrderHeaders
                where o.CustomerID == customerId
                select o;
 
        var orders = q.ToList();
 
        foreach (var o in orders)
        {
          foreach (var od in o.SalesOrderDetails)
          {
            od.UnitPrice = od.UnitPrice * 1.04M;
          }
        }
 
        string sql = @"
declare @output Sales_SalesOrderDetail_type
 
MERGE Sales.SalesOrderDetail AS target
USING 
( 
  SELECT
    SalesOrderID,
    SalesOrderDetailID,
    UnitPrice
  FROM @OrderDetails 
) AS source (SalesOrderID, SalesOrderDetailID, UnitPrice)
ON 
(
      target.SalesOrderID = source.SalesOrderID
  and target.SalesOrderDetailID = source.SalesOrderDetailID
)
WHEN MATCHED 
    THEN UPDATE SET target.UnitPrice = source.UnitPrice, 
                    target.ModifiedDate = GETDATE()
OUTPUT inserted.*
into @output;
 
select * from @output;
";
 
        var cmd = new SqlCommand(sql, (SqlConnection)db.Connection);
        var pOrderDetails = cmd.Parameters.Add(new SqlParameter("@OrderDetails", SqlDbType.Structured));
        pOrderDetails.TypeName = "Sales_SalesOrderDetail_type";
        
        /*
        CREATE TYPE Sales_SalesOrderDetail_type as TABLE
        (
	        [SalesOrderID] [int] NOT NULL,
	        [SalesOrderDetailID] [int]  NOT NULL,
	        [CarrierTrackingNumber] [nvarchar](25) NULL,
	        [OrderQty] [smallint] NOT NULL,
	        [ProductID] [int] NOT NULL,
	        [SpecialOfferID] [int] NOT NULL,
	        [UnitPrice] [money] NOT NULL,
	        [UnitPriceDiscount] [money] NOT NULL,
	        [LineTotal]  MONEY,
	        [rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
	        [ModifiedDate] [datetime] NOT NULL
        ) 
       */
        //project the results into an Anonymous type matching the TVP declaration
        var dtq = from od in orders.SelectMany(o => o.SalesOrderDetails).Take(1000)
                  select new 
                  {
                    SalesOrderID = od.SalesOrderID,
                    SalesOrderDetailId = od.SalesOrderDetailID,
                    CarrierTrackingNumber = od.CarrierTrackingNumber,
                    OrderQty = od.OrderQty,
                    ProductID = od.ProductID,
                    SpecialOfferID = od.SpecialOfferID,
                    UnitPrice = od.UnitPrice,
                    UnitPriceDiscount = od.UnitPriceDiscount,
                    LineTotal = od.LineTotal,
                    rowguid = od.rowguid,
                    ModifiedDate = od.ModifiedDate
                  };
 
        var i = 0;
 
        var f = new { a = i++, b = i++, c = i++ };
 
        //wrap the collection in a DataReader for sending to the server
        pOrderDetails.Value = dtq.AsDataReader();
 
        IList<L2S.SalesOrderDetail> results;
        using (var dr = cmd.ExecuteReader())
        {
          results = db.Translate<L2S.SalesOrderDetail>(dr).ToList();
        }
 
        
        foreach (var r in results)
        {
          Console.WriteLine("{0} {1}",r.SalesOrderDetailID,r.UnitPrice);
 
        }
 
        if (dtq.Count() != results.Count())
        {
          throw new InvalidOperationException("Wrong number of rows affected by MERGE");
        }
      }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值