Linq To Sql(以下简称LS)从一降世似乎就是个问题宝宝。批更新问题, Like问题,RTM之前的"BUG"(select new不能显式创建实体)等等接踵而至,很多时候我们不得不回到SqlCommand去来"扩展"LS,不巧的是,LS留给程序员的灵活性很有限,我们的Extension总是有不尽完美的地方,在自我扩展的同时我更多的是期待Entity Framework正式Release时候能解决这些问题,至少是能够带给我们更多的灵活性。今天我要说的是LS中的继承问题,先看实体关系:
图中已经去掉了多余的字段,仅仅留下关键字段,简要说明一下:Order实体和Supplier实体对应数据库中的表,而OrderInfo实体很明显是连接了两个实体的“视图”。这样的应用场景很常见,我可能需要OrderInfo去填充一个Grid,而又需要在OrderInfo被修改之后提交回数据库, 很显然OrderInfo必须是一个实际类型而非匿名类型。有人会问为什么不用视图去生成OrderInfo实体,原因很简单,视图是无法提交修改的,为了让能够OrderInfo直接提交回数据库更好的做法是让他继承于Order,于是乎,很自然的想到OrderInfo的代码是这样:
| 以下是引用片段: [Table(Name = "dbo.Order")] public class OrderInfo : Order { private string _SupplierName; public OrderInfo() { } public string SupplierName { get { return this._SupplierName; } set { if ((this._SupplierName != value)) { this._SupplierName = value; } } } } |
| 以下是引用片段: using (DataClasses1DataContext context = new DataClasses1DataContext()) { OrderInfo[] infos = from o in context.Orders join s in context.Suppliers on o.SupplierId equals s.SupplierId select new OrderInfo { OrderId = o.OrderId, SupplierId = o.SupplierId, SupplierName = s.SupplierName }; return infos; } |
| 以下是引用片段: [System.Data.Linq.Mapping.InheritanceMapping(Code = "Order", Type = typeof(Order), IsDefault = true)] [System.Data.Linq.Mapping.InheritanceMapping(Code = "OrderInfo", Type = typeof(OrderInfo))] public partial class Order { [Column(IsDiscriminator = true, IsDbGenerated = true, AutoSync = AutoSync.Never)] public string Type { get; set; } } |
| 以下是引用片段: [Column(IsDiscriminator = true)] public string Type { get; set; } |
| 以下是引用片段: [Column(IsDiscriminator = true, IsDbGenerated = true, AutoSync = AutoSync.Never)] public string Type { get; set; } |
好了,现在我们再用JeffreyZhao的那个扩展去查询,在这里借花献佛了(很感谢老赵提供了这个扩展),展示一个那个扩展是怎么用的:
| 以下是引用片段: using (DataClasses1DataContext context = new DataClasses1DataContext()) { IQueryable infos = from o in context.Orders join s in context.Suppliers on o.SupplierId equals s.SupplierId select new { OrderId = o.OrderId, SupplierId = o.SupplierId, SupplierName = s.SupplierName }; return context.ExecuteQuery(infos); } |
| 以下是引用片段: select new { OrderId = o.OrderId, SupplierId = o.SupplierId, SupplierName = s.SupplierName, Type = "OrderInfo" }; |
| 以下是引用片段: SELECT [t0].[OrderId], [t0].[SupplierId], [t1].[SupplierName] FROM [Order] AS [t0] INNER JOIN [Supplier] AS [t1] ON [t0].[SupplierId] = [t1].[SupplierId] |
| 以下是引用片段: SELECT [t0].[OrderId], [t0].[SupplierId], [t1].[SupplierName], 'OrderInfo' AS [Type] FROM [Order] AS [t0] INNER JOIN [Supplier] AS [t1] ON [t0].[SupplierId] = [t1].[SupplierId] |
| 以下是引用片段: TypeExtension public static class TypeExtension { /**//// /// 获取一个Linq实体类型在Linq继承层次中的鉴别列的值(类型必须是继承连中的顶级类型) /// /// /// 要查找Code值的子类别 public static object GetDiscriminatorCode(this Type type, Type typeChild) { InheritanceMappingAttribute[] inheritances = (InheritanceMappingAttribute[])type.GetCustomAttributes(typeof(InheritanceMappingAttribute), true); if (inheritances.Length > 0) { foreach (InheritanceMappingAttribute att in inheritances) { if (typeChild.Equals(att.Type)) { return att.Code; } } } return null; } /**//// /// 获取一个Linq实体类型的鉴别器列的列名(不查找继承链) /// public static string GetDiscriminatorColumnName(this Type type) { PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetField); foreach (PropertyInfo property in properties) { ColumnAttribute[] colAtts = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true); if (colAtts != null && colAtts.Length > 0) { ColumnAttribute colAtt = colAtts[0]; if (colAtt.IsDiscriminator) { return (String.IsNullOrEmpty(colAtt.Name)) ? property.Name : colAtt.Name; } } } return null; } /**//// /// 获取一个Linq实体类型的在继承层次中的顶层基类 /// /// /// public static Type FindTopEntityType(this Type type) { Type ty = type; while (!ty.IsLinqDataEntity()) { ty = type.BaseType; if (ty.Equals(typeof(Object))) { ty = null; break; } } return ty; } /**//// /// 指示一个类型是否是一个Linq To Sql实体(不查找继承链) /// public static bool IsLinqDataEntity(this Type type) { TableAttribute[] atts = (TableAttribute[])type.GetCustomAttributes(typeof(TableAttribute), false); return (atts != null && atts.Length > 0); } } |
| 以下是引用片段: private static Regex m_fieldResgex = new Regex(@"\sFROM\s", RegexOptions.IgnoreCase); private static void AddDiscriminatorColum(DbCommand cmd) { string cmdText = cmd.CommandText; IEnumerable matches = m_fieldResgex.Matches(cmdText).Cast().OrderByDescending(m => m.Index); foreach (Match m in matches) { int splitIndex = m.Index; cmdText = cmdText.Substring(0, splitIndex) + GetDiscriminatorColumSelectString() + cmdText.Substring(splitIndex); } cmd.CommandText = cmdText; } private static string GetDiscriminatorColumSelectString() { Type type = typeof(T); Type topType = type.FindTopEntityType(); string value = topType.GetDiscriminatorCode(type).ToString(); string columnName = topType.GetDiscriminatorColumnName(); if (!String.IsNullOrEmpty(value) && !String.IsNullOrEmpty(columnName)) { return String.Format(@", '{0}' AS [{1}] ", value, columnName); } return String.Empty; } 完成很扩展(有的代码我有所修改,我不太理解为什么JeffreyZhao的代码中要在方法外部关闭数据库连接,为了降低打开连接的开销吗?可是有连接池啊,疑惑中。。。。): public static List ExecuteQuery(this DataContext dataContext, IQueryable query, bool withNoLock, bool generateDiscriminateColumn) { string discriminatorCode = null; Type type = typeof(T); DbCommand command = dataContext.GetCommand(query, withNoLock); if (generateDiscriminateColumn) { AddDiscriminatorColum(command); } try { command.Connection.Open(); using (DbDataReader reader = command.ExecuteReader()) { return dataContext.Translate(reader).ToList(); } } finally { if (command != null) { command.Connection.Close(); } command.Dispose(); } } |
| 以下是引用片段: using (DataClasses1DataContext context = new DataClasses1DataContext()) { IQueryable infos = from o in context.Orders join s in context.Suppliers on o.SupplierId equals s.SupplierId select new { OrderId = o.OrderId, SupplierId = o.SupplierId, SupplierName = s.SupplierName }; return context.ExecuteQuery(infos, true, true); } |
使查询结果中的列与对象中的字段和属性相匹配的算法如下所示:
通过先查找区分大小写的匹配来执行比较。如果未找到匹配项,则会继续搜索不区分大小写的匹配项。
如果同时满足下列所有条件,则该查询应当返回(除延迟加载的对象外的)对象的所有跟踪的字段和属性:
我标注了红色的两个地方个人认为是对立的,我的理解是如果是DataContext追踪的实体则只映射对象追踪的字段和属性,如果不是DataContext追踪的实体则会映射追踪的和不追踪的属性和字段。
解决之道是在OrderInfo.SupplierName属性上加上ColumnAttribute特性,同时处于更新时候的需要,一样要欺骗LS:
| 以下是引用片段: [Column(Storage="_SupplierName", IsDbGenerated = true, AutoSync = AutoSync.Never)] public string SupplierName { get { return this._SupplierName; } set { if ((this._SupplierName != value)) { this._SupplierName = value; } } } |
| 以下是引用片段: OrderInfo info = new OrderInfo(); using (DataClasses1DataContext context = new DataClasses1DataContext()) { context.Orders.InsertOnSubmit(info); context.SubmitChanges(); } |
关注此文的读者还看过: