引言
最近在学习LINQ,已经逐步深入到LINQ to SQL和LINQ to Entity的部分。但是对SQL Entity专题中的EntityRef<T>很是迷惑,始终未能理解它是如何从数据库的1:1关系转变为C#中两个对象的相互引用的,于是有了这段摸索。
阅读本文,需要对SQL Entity的内部结构有一定程度的了解。
线索
问题源自于下面我自己写的这段旨在测试引用关系的代码,Customers与Orders是通过“LINQ to SQL类”(.dbml)的方式,从数据库生成的SQL Entity类。其中,测试用例的客户Thomas Hardy的CustomerID为AROUT。
using (NorthwindDataContext db = new NorthwindDataContext()) { db.Log = Console.Out; Customers customer = db.Customers.Where(c => c.ContactName == "Thomas Hardy").SingleOrDefault(); Console.WriteLine("the customer {0} lives in {1}", customer.City); Console.WriteLine("he gets {0} orders.", customer.Orders.Count); Orders order = db.Orders.Where(o => o.CustomerID == "AROUT").FirstOrDefault(); Console.WriteLine("get the first order id is: {0}", order.OrderID); Console.WriteLine(object.ReferenceEquals("is the reference equal? {0}", customer, order.Customers)); }
运行的结果
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactT itle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Coun try], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE [t0].[ContactName] = @p0 -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Thomas Hardy] -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1 the customer Thomas Hardy lives in London SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [ t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[Sh ipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPosta lCode], [t0].[ShipCountry] FROM [dbo].[Orders] AS [t0] WHERE [t0].[CustomerID] = @p0 -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [AROUT] -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1 he gets 13 orders. SELECT TOP (1) [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[Order Date], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[S hipPostalCode], [t0].[ShipCountry] FROM [dbo].[Orders] AS [t0] WHERE [t0].[CustomerID] = @p0 -- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [AROUT] -- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1 get the first order id is: 10355 is the reference equal? True
从结果看,对象customer与order在我们没有显式地建立彼此的引用的情况下,通过LINQ to SQL仍实现了完美的相互引用,从而使最后的引用比较得到了True的结果。
摸索过程
打断点+单步调试,这是深入Entity内部的简易方法。最后得出如下的一张流程图。
从图中可以看出,
1. 子表对应的Entity对象并不会在父表Entity对象被填充时一并被构造并填充(除非显式地LoadWith)。EntitySet<T>与EntityRef<T>都是独立于作为其内部元素的Entity对象的数据结构。
2. 总是先生成SQL查询语句,随后才由LINQ构造并填充Entity对象。
3. attach/detach方法,是建立彼此引用的核心。
EntityRef<T>的结构
除了MSDN的帮助,我利用反射,获得了EntityRef<T>的结构图:
利用调试工具,还能看见其内部的私有成员:
到此,终于对LINQ to SQL Entity Class的数据加载,以及EntityRef<T>与EntitySet<T>的关系有了更深入的了解,心中的疑惑也终于有了一个答案。
LINQ to Entity还在等待着我……