Linq to Sql:N层应用中的查询(上) : 返回自定义实体

如果允许在UI层直接访问Linq to Sql的DataContext,可以省去很多问题,譬如在处理多表join的时候,我们使用var来定义L2S查询,让IDE自动推断变量的具体类型 (IQueryable<匿名类型>),并提供友好的智能提示;而且可以充分应用L2S的延迟加载特性,来进行动态查询。但如果我们希望将业务逻辑放在一个独立的层中(譬如封装在远程的WCF应用中),又希望在逻辑层应用Linq to sql,则情况就比较复杂了;由于我们只能使用var( IQueryable<匿名类型>),而var只能定义方法(Method)范围中声明的变量,出了方法(Method)之后IDE就不认得它了;在这种对IQueryable<匿名类型>一无所知的情况下,又希望能在开发时也能应用上IDE的智能感应,我们该怎么定义层之间交互的数据传输载体呢?又如何对它进行动态查询呢?

  内容比较多,分上下两篇,上篇介绍查询返回自定义实体,下篇介绍动态查询。

  下面来看一个示例(以NorthWind数据库为示例),现在我们要在界面上展示某个用户什么时间订购了哪些产品。



  如果允许在UI层直接访问DataContext,我们可以这样来写:

  1: using (NorthWindDataContext context = new NorthWindDataContext())
  2: {
  3:   var query0 = from C in context.Customers
  4:         join O in context.Orders
  5:           on C.CustomerID equals O.CustomerID
  6:         join OD in context.Order_Details
  7:           on O.OrderID equals OD.OrderID
  8:         join P in context.Products
  9:           on OD.ProductID equals P.ProductID
 10:         select new
 11:         {
 12:           C.CustomerID,
 13:           C.CompanyName,
 14:           C.ContactName,
 15:           C.Address,
 16:           O.OrderDate,
 17:           P.ProductName
 18:         };
 19:   gridView.DataSource = query0.ToList();
 20:   gridView.DataBind();
 21: }

  这里只查询需要显示的列,避免返回不必要的列。查询返回的是一个泛型匿名对象集合,由于绑定操作与查询操作在同一个方法内,所以IDE会自动帮忙推断var的对象类型。但如果要将查询逻辑封装在远程的WCF中,我们该用啥作为层之间交互的数据传输载体呢?List<???>,里面的“???”该是啥呢?

  以下是我尝试过的几种方案和走过的弯路。

  1. 扩展默认实体定义

  从上面的代码中可以看到,我们需要返回的属性信息主要来源于Customers实体,下面来尝试下能否在该实体的定义中直接附加字段

  OrderDate和ProductName:

  1: partial class Customers
  2: {
  3:   public DateTime OrderDate {get;set;}
  4:   public string ProductName { get; set; }
  5: }

  然后这样来写查询,看看能不能欺骗L2S来自动匹配这新增的两个属性:

  1: public List<Customers> GetOrderInfo(string customerID)
  2: {
  3:   using (NorthWindDataContext context = new NorthWindDataContext())
  4:   {
  5:     var query1 = from C in context.Customers
  6:           join O in context.Orders
  7:             on C.CustomerID equals O.CustomerID
  8:           join OD in context.Order_Details
  9:             on O.OrderID equals OD.OrderID
 10:           join P in context.Products
 11:             on OD.ProductID equals P.ProductID
 12:           where C.CustomerID == customerID
 13:           select 
C
; //直接返回实体
 14:  
 15:     //或者这样
 16:     var query2 = from C in context.Customers
 17:           join O in context.Orders
 18:             on C.CustomerID equals O.CustomerID
 19:           join OD in context.Order_Details
 20:             on O.OrderID equals OD.OrderID
 21:           join P in context.Products
 22:             on OD.ProductID equals P.ProductID
 23:           where C.CustomerID == customerID
 24:           select 
new Customers
 //显示构造实体
构造实体
 25:           {
 26:             CustomerID = C.CustomerID,
 27:             CompanyName = C.CompanyName,
 28:             ContactName = C.ContactName,
 29:             Address = C.Address,
 30:             OrderDate = O.OrderDate,
 31:             ProductName = P.ProductName
 32:           };
 33:     return query1.ToList(); //query2.ToList()
 34:   }
 35: }

  很遗憾的是,query1查询执行的结果,没有取得我们需要的数据:



    查看原图(大图)

  而query2也抛出了

  NotSupportedException:不允许在查询中显式构造实体类型“TestLINQ.Customers”。

  看来,这种方法行不通。

  2. 使用Translate来返回自定义实体

  在老赵的这篇文章中:《在LINQ to SQL中使用Translate方法以及修改查询用SQL》,里面提出了一种方法来来砍掉那些不需要加载的信息,且可以继续使用LINQ to SQL进行查询。

  这里借鉴下里面的思路,看看在增加属性的情况下,结果会怎样:

  1: public List<Customers> GetOrderInfo(string customerID)
  2: {
  3:   using (NorthWindDataContext context = new NorthWindDataContext())
  4:   {
  5:     var query3 = query0;
  6:     return context.ExecuteQuery<Customers>(query);
  7:   }
  8: }

  说明:
(1) 这里的Customers类型定义,继续用上一节中的对实体类的扩展;
(2) DataContext.ExcuteQuery<T>(IQuery query)方法,使用的老赵的DataContext扩展;
(3) 为避免L2S查询占用太多的版面,前面对每个查询都进行了编号,query0, query1, query2….,下面如果需要用到同样的查询时,直接引用前面的查询,以节省版面和突出重点。

  很遗憾的是,这次希望又落空了。

  返回的结果中,OrderDate和ProductName依然为空。

  老赵只提供了砍掉不需要的字段的方法,把增加字段的方法自己留着了/:)

  另外补充一点,这里对老赵提供的方法做了一点儿改进:如果调用OpenConnection时打开了新的连接,则需要在用完后关闭该连接,下面是代码:

  1: public static List<T> ExecuteQuery<T>(this DataContext dataContext, IQueryable query)
  2: {
  3:   using (DbCommand command = dataContext.GetCommand(query))
  4:   {
  5:     bool openNewConnecion = false;
  6:     try
  7:     {
  8:       openNewConnecion = dataContext.OpenConnection();
  9:       using (DbDataReader reader = command.ExecuteReader())
 10:       {
 11:         return dataContext.Translate<T>(reader).ToList();
 12:       }
 13:     }
 14:     finally
 15:     {
 16:       if (openNewConnecion) //如果打开了新的连接,则需要手动Close
 17:         dataContext.Connection.Close();
 18:     }
 19:   }
 20: }
 21:  
 22: /// <summary>
 23: /// 打开连接
 24: /// </summary>
 25: /// <param name="dataContext"></param>
 26: /// <returns>是否打开了新的连接(这个返回值可能容易让人误解,汗...)</returns>
 27: private static bool OpenConnection(this DataContext dataContext)
 28: {
 29:   if (dataContext.Connection.State == ConnectionState.Closed)
 30:   {
 31:     dataContext.Connection.Open();
 32:     return true; 
 33:   }
 34:   return false;
 35: }

  3. 执行TSQL

  使用DataContext自带的ExcuteQuery<T>方法:

  1: public List<Customers> GetOrderInfo(string customerID)
  2: {
  3:   using (NorthWindDataContext context = new NorthWindDataContext())
  4:   {
  5:     string sql = @"SELECT C.CustomerID, C.CompanyName, C.ContactName, C.[Address], O.OrderDate, P.ProductName 
  6: dbo.Customers AS C
  7: dbo.Orders AS O
  8: ON O.CustomerID = C.CustomerID
  9: dbo.[Order Details] AS OD
 10: ON OD.OrderID = O.OrderID
 11: dbo.Products AS P
 12: ON P.ProductID = OD.ProductID
 13: E C.CustomerID={0}";
 14:     return context.ExecuteQuery<Customers>(sql, customerID).ToList();
 15:   }
 16: }

  结果跟第二节中的结果相同,又失败了……

  补充,MSDN上关于Translate和ExcuteQuery对查询结果进行转换的描述如下:

  1. 使查询结果中的列与对象中的字段和属性相匹配的算法如下所示:

  1.1 如果字段或属性映射到特定列名称,则结果集中应包含该列名称。

  1.2 如果未映射字段或属性,则结果集中应包含其名称与该字段或属性相同的列。

  1.3 通过先查找区分大小写的匹配来执行比较。如果未找到匹配项,则会继续搜索不区分大小写的匹配项。

  2. 如果同时满足下列所有条件,则该查询应当返回(除延迟加载的对象外的)对象的所有跟踪的字段和属性:

  2.1 T 是由 DataContext 显式跟踪的实体。

  2.2 ObjectTrackingEnabled 为 true。

  2.3 实体具有主键。

  否则会引发异常。

  我愣是看了好多遍,还是没有搞明白,为啥将结果集转换到对象集合时L2S把我增加的字段给抛弃了……

  4. 继承默认实体定义

  既然不让我在L2S生成的默认实体上直接进行扩展,那我可以派生一个实体并添加我们需要的字段吗?

  1: public class 
CustomerExt : Customers
  2: {
  3:   public DateTime? OrderDate {get;set;}
  4:   public string ProductName { get; set; }
  5: }

  然后在业务逻辑层里面这样写:

  1: public List<
CustomerExt
> GetOrderInfo(string customerID)
  2: {
  3:   using (NorthWindDataContext context = new NorthWindDataContext())
  4:   {
  5:     var query4 = query0
  6:     return context.ExecuteQuery<
CustomerExt
>(query).ToList();
  7:   }
  8: }

  遗憾的是,程序执行到dataContext.Translate<T>(reader).ToList()时,又出错了,抛出了InvalidOperationException异常:

未处理 System.InvalidOperationException
 Message="类型为“TestLINQ.Customers”的数据成员“System.String CustomerID”不是类型“CustomerExt”的映射的一部分。该成员是否位于继承层次结构根节点的上方?"
 Source="System.Data.Linq"
 StackTrace:
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.GetRequiredInheritanceDataMember(MetaType type, MemberInfo mi)
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.AccessMember(SqlMember m, SqlExpression expo)
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitMember(SqlMember m)
    在 System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr)
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitNew(SqlNew sox)
    在 System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr)
    在 System.Data.Linq.SqlClient.SqlVisitor.VisitUserQuery(SqlUserQuery suq)
    在 System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitUserQuery(SqlUserQuery suq)
    在 System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
    在 System.Data.Linq.SqlClient.SqlBinder.Bind(SqlNode node)
    在 System.Data.Linq.SqlClient.SqlProvider.BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection`1 parentParameters, SqlNodeAnnotations annotations)
    在 System.Data.Linq.SqlClient.SqlProvider.GetDefaultFactory(MetaType rowType)
    在 System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Translate(Type elementType, DbDataReader reader)
    在 System.Data.Linq.DataContext.Translate(Type elementType, DbDataReader reader)
    在 System.Data.Linq.DataContext.Translate[TResult](DbDataReader reader)
    在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, DbCommand command) 位置 D:4.OtherWinFormTestLINQDataContextExtensions.cs:行号 74
    在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, IQueryable query, Boolean withNoLock) 位置 D:4.OtherWinFormTestLINQDataContextExtensions.cs:行号 53
    在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, IQueryable query) 位置 D:4.OtherWinFormTestLINQDataContextExtensions.cs:行号 28
    在 TestLINQ.Program.GetOrderInfo(String customerID) 位置 D:4.OtherWinFormTestLINQClass1.cs:行号 49
    在 TestLINQ.Program.Main(String[] args) 位置 D:4.OtherWinFormTestLINQClass1.cs:行号 21
    在 System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
    在 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
    在 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    在 System.Threading.ThreadHelper.ThreadStart()
 InnerException: 

  回过头来看看L2S中的继承,MSDN说法如下:

  若要在 LINQ 中执行继承映射,您必须在继承层次结构的根类中指定属性 (Attribute) 和属性 (Attribute) 的属性 (Property)。(FROM MSDN: 映射继承层次结构 (LINQ to SQL))

  看得我有点儿晕晕的....如果我不想修改L2S帮我生成的类型定义文件,则需要通过partial类对默认生成的Customers进行扩展:扩展一个属性作为鉴别器值?
     好像挺绕的,我最终还是没有尝试成功……

  上面啰嗦了这么多废话,是我使用L2S过程中走过的一些弯路,列出来供大家参考,避免重蹈我的覆辙。

  5. 显式自定义实体

  在上面一节尝试使用继承时,查看错误堆栈信息,最后定位到GetRequiredInheritanceDataMember这里,这是在访问基类成员时出错了。于是我起了个邪恶的念头,把基类抛弃掉,显式再定义一个实体看看:

  1: public class 
CustomerOrderDetial
  2: {
  3:   public string CustomerID { get; set; }
  4:   public string CompanyName { get; set; }
  5:   public string ContactName { get; set; }
  6:   public string Address { get; set; }
  7:   public DateTime? OrderDate { get; set; }
  8:   public string ProductName { get; set; }
  9: }
 10:  
 11: public List<
CustomerOrderDetial
> GetOrderInfo(string customerID)
 12: {
 13:   using (NorthWindDataContext context = new NorthWindDataContext())
 14:   {
 15:     var query5 = query0
 16:     return context.ExecuteQuery<
CustomerOrderDetial
>(query5).ToList();
 17:   }
 18: }

  这次运行通过了,而且得到了我们想要的结果,Congratulations!



    查看原图(大图)
     但是,这样操作的话,每次我们都要去手工编写代码,将我们需要的字段封装成一个实体类型。

  结合上面第3节中的结论,我推测Translate和ExcuteQuery是按照下列逻辑来将结果集转换成对象集合的:

  1: if(实体是由Table影射的实体)
  2: {
  3:   转换时,只匹配标记为[Column]的属性
  4: }
  5: else //显式自定义实体(参考下面第4节)
  6: {
  7:  转换时,根据属性名与结果集中的列名进行匹配
  8: }

  6. 使用视图/存储过程/自定义函数

  另一种方法是使用视图、或存储过程、或自定义函数,让L2S设计器或者SqlMeta工具将视图映射成实体,或生成调用存储过程和自定义函数的代码。
    可以参考MSDN:存储过程 (LINQ to SQL)。使用自定义函数过程与存储过程差不错,使用视图的过程与表差不多,具体可以看MSDN中介绍,及L2S生成的源代码,这里就不啰嗦了。

  然而,视图、存储过程、自定义函数也不是万金油。就拿本文的例子来说,我们的应用场景是“查询客户什么时间订了哪些产品”,于是我们定义了一个视图来关联相关的四张表;但一个应用系统中,往往会有很多场景;各种场景相互之间很相似,但又有不同,譬如“查询客户什么时间订了哪些公司生产的哪些产品”、“查询客户什么时间订了 哪些雇员销售的哪些产品”,我们又该怎么处理呢?为每个场景定制一个视图?还是做一个“聪明 ”的大视图,把所有关联的表都join起来?
    使用前者的结果可能会是,试图的数量呈爆炸式增长;
    使用后者的结果可能会是:聪明反被聪明误,性能不是一般地差。

  7. 自定义对象转换器

  前面的两种方法虽然都可行,但用起来还是有点儿麻烦,能不能简单一点儿呢?

  在使用LINQ之前,我们经常使用Ado.Net从数据库中取得一个数据集(DataSet或者DataTable),然后再根据列名称与对象的属性名进行匹配,将数据集转换成对象集合List<T>。在本节中,我将参考这个思路,自定义一个对象转换器。

  LINQ中,有一个扩展方法IEnumerable.Cast<TResult>,实现了从IEnumerable到 IEnumerable<TResult>的转换,里面实现的是遍历源集合,然后将里面的元素进强制类型转换TResult类型,最后返回 IEnumerable<TResult>。但这里,我们要实现的是,将IEnumerable<匿名类型>转换成 IEnumerable<命名类型>,使用该转换器的代码示例如下图所示:



    查看原图(大图)

  下面是执行结果(其中CustomerExt使用第4节中的实体定义,继承自Customers):



    查看原图(大图)

  使用起来还算比较清爽;当然,也有不足之处,性能怎样是一个考虑点,还有就是如上面的运行结果截图,一些被我们坎掉的字段也会显示出来;虽然这些额外字段的值都为空,但考虑下列情况:UI层取得的结果是List<CustomerExt>,但他怎么知道CustomerExt中哪些字段可以用,哪些字段被阉割了呢?答案是:源代码前面没有秘密,只有看底层的源代码了-.-

  下面来看下这个对象转换器的源代码:

  1: public static class ObjectConverter
  2: {
  3:   private class CommonProperty
  4:   {
  5:     public PropertyInfo SourceProperty { get; set; }
  6:     public PropertyInfo TargetProperty { get; set; }
  7:   }
  8:  
  9:   public static 
List<TResult>
 ConvertTo<TResult>(this IEnumerable source)
 10:     where TResult : new()
 11:   {
 12:     if (source == null) //啥都不用干
 13:       return null;
 14:  
 15:     if (source is IEnumerable<TResult>)
 16:       return source.Cast<TResult>().ToList();//源类型于目标类型一致,可以直接转换
 17:  
 18:     List<TResult> result = new List<TResult>();
 19:     bool hasGetElementType = false;
 20:     IEnumerable<CommonProperty> commonProperties = null; //公共属性(按属性名称进行匹配)
 21:  
 22:     foreach (var s in source)
 23:     {
 24:       if (
!hasGetElementType
) //访问第一个元素时,取得属性对应关系;后续的元素就不用再重新计算了
 25:       {
 26:         if (s is TResult) //如果源类型是目标类型的子类,可以直接Cast<T>扩展方法
 27:         {
 28:           
return source.Cast<TResult>().ToList();
 29:         }
 30:         commonProperties = GetCommonProperties(s.GetType(), typeof(TResult));
 31:         hasGetElementType = true;
 32:       }
 33:  
 34:       TResult t = new TResult();
 35:       foreach (CommonProperty commonProperty in commonProperties) //逐个属性拷贝
 36:       {
 37:         object value = commonProperty.SourceProperty.GetValue(s, null);
 38:         commonProperty.TargetProperty.SetValue(t, value, null);
 39:       }
 40:       result.Add(t);
 41:     }
 42:  
 43:     
return result;
 44:   }
 45:  
 46:   private static IEnumerable<CommonProperty> GetCommonProperties(Type sourceType, Type targetType)
 47:   {
 48:     PropertyInfo[] sourceTypeProperties = sourceType.GetProperties();//获取源对象所有属性
 49:     PropertyInfo[] targetTypeProperties = targetType.GetProperties(); //获取目标对象所有属性
 50:     return from SP in sourceTypeProperties
 51:        join TP in targetTypeProperties
 52:          on SP.Name.ToLower() equals TP.Name.ToLower() //根据属性名进行对应(不区分大小写)
 53:        select new CommonProperty
 54:        {
 55:          SourceProperty = SP,
 56:          TargetProperty = TP
 57:        };
 58:   }
 59: }

  源代码前没有秘密,里面就是实现了最简单的转换:将源对象集合中的元素逐个转换成目标对象。

  关于这段代码的一点补充说明(下面的源类型和目标类型,是指泛型中的T,而不是IEnumerable<T>):

  (1).如果源类型于目标类型一致,或者源类型是目标类型的子类,则可以不用逐个元素遍历了,直接调用IEnumerable的扩展方法Cast<T>()即可;

  用Reflector看了下其源代码实现,里面比较绕,不知道性能咋样,暂时不管了,用着先,而且这样很省事儿。
    另外List<T>也提供了一个ConvertAll<TOutput>(Converter<T, TOutput> converter)方法,可以自己定义一个对象转换器方法,然后传给Converter<T, TOutput>委托;但这里用不上该方法,原因如下:
    a. 看其源代码实现,可以发现其就是遍历集合循环执行Converter委托,这样不便于进行优化(参考下面的第(2)点);
    b. 虽然我可以实现一个Converter<T, TOutput>,但在外面该怎样调用呢?因为query的类型是IQueryable<匿名类型>,所以在调用时,我们根本不知道该传啥进去。

  (2). 如果不满足(1),则需要逐个元素进行转换。由于在进入foreach(上面代码的第22行)之前,还不知道源类型是什么类型,因此将GetCommonProperties方法放到循环中;但如果源集合中有100个元素,而循环中每次都来执行这个方法,合计执行100次,这样会显得很傻X,因此里面加了点控制,只在处理第一个元素时调用该方法,然后将属性匹配结果缓存下来(使用局部变量commonProperties进行缓存),从而避免每次都做无用功。

  (3). 执行返回的结果时List<TResult>,也即是执行此方法时,如果传进来的是IQueryable<T>,则会立即进行计算。

  (4). 这里面还有继续优化的余地:如果有100个用户同时在执行这个查询请求,则每个请求里面都在进行执行 GetCommonProperties函数,然后各自进行着反射(取得“特定匿名类型”和CustomerExt类型的属性集合)和属性匹配(取得“特定匿名类型”和CustomerExt类型的公共属性)运算,这样又会显得傻X了。对于一个普通的已经部署完毕的应用系统,其中的实体类型定义是恒定的(不考虑动态编译的情况;对于匿名类型,在编译时,编译器会为其创建类型定义),而且类型之间的转换关系也是恒定的,因此我们可以这些信息缓存下来,避免每次请求都执行重复计算。下面是一个最简单的属性缓存器,采用静态变量来保存计算过的信息,直接替换上面的GetCommonProperties方法即可:

  1: private static class PropertyCache
  2: {
  3:   private static object syncProperty = new object();
  4:   private static object syncCommon = new object();
  5:  
  6:   private static Dictionary<Type, PropertyInfo[]> PropertyDictionary =
  7:     new Dictionary<Type, PropertyInfo[]>(); //缓存类型的PropertyInfo数组
  8:   private static Dictionary<string, IEnumerable<CommonProperty>> CommonPropertyDictionary =
  9:     new Dictionary<string, IEnumerable<CommonProperty>>(); //缓存两种类型的公共属性对应关系
 10:  
 11:   private static PropertyInfo[] GetPropertyInfoArray(Type type)
 12:   {
 13:     if (!PropertyCache.PropertyDictionary.ContainsKey(type))
 14:     {
 15:       lock (syncProperty)
 16:       {
 17:         if (!PropertyCache.PropertyDictionary.ContainsKey(type)) //双重检查
 18:         {
 19:           PropertyInfo[] properties = type.GetProperties();
 20:           PropertyCache.PropertyDictionary.Add(type, properties); //Type是单例的(Singleton),可以直接作为Key
 21:         }
 22:       }
 23:     }
 24:     return PropertyCache.PropertyDictionary[type];
 25:   }
 26:  
 27:   public static IEnumerable<CommonProperty> GetCommonProperties(Type sourceType, Type targetType)
 28:   {
 29:     string key = sourceType.ToString() + targetType.ToString();
 30:     if (!PropertyCache.CommonPropertyDictionary.ContainsKey(key))
 31:     {
 32:       lock (syncCommon)
 33:       {
 34:         if (!PropertyCache.CommonPropertyDictionary.ContainsKey(key)) //双重检查
 35:         {
 36:           PropertyInfo[] sourceTypeProperties = GetPropertyInfoArray(sourceType);//获取源对象所有属性
 37:           PropertyInfo[] targetTypeProperties = GetPropertyInfoArray(targetType);//获取目标对象所有属性
 38:           IEnumerable<CommonProperty> commonProperties = from SP in sourceTypeProperties
 39:                                  join TP in targetTypeProperties
 40:   on SP.Name.ToLower() equals TP.Name.ToLower()
 41:                                  select new CommonProperty
 42:                                  {
 43:                                    SourceProperty = SP,
 44:                                    TargetProperty = TP
 45:                                  };
 46:           PropertyCache.CommonPropertyDictionary.Add(key, commonProperties);
 47:         }
 48:       }
 49:     }
 50:     return PropertyCache.CommonPropertyDictionary[key];
 51:   }
 52: }

  8. Something Others

  上面第7节中,看起来好像解决了文章标题所提出的问题,但这种方式也可能是个陷阱。

  其中使用了CustomerExt,其继承自L2S生成的默认实体Customers,这样带来的一个好处就是可以复用Customers中的属性定义,而不必像第5节中一样,重新定义一套。但是从继承的语义上来讲,继承体现的是一种IS-A的关系,因此套用过来的话就是这样:“客户什么时间订购哪些商品”是一个“客户”!???这是啥?幼儿园没毕业吧?打回去重读……

  在某些场景下,我们可以应用继承,譬如NorthWind数据库中有张表dbo.Contacts记录用户的联系信息,则我们可以对Customer或者 Employee进行扩展,添加联系信息;而对于本文所举的这个例子,继承是被滥用了。当然,本文的重点是Linq to Sql,而不是OO,因此,这里就请各位看官不要追究我的错误了………我先原谅我自己,愿主也原谅我吧,阿弥陀佛。

  为了将功补过,这里引入一点Entity Framework的东西,下面这个截图来自《Linq in Action》:



    查看原图(大图)

  在Linq to Sql中,我们只能将表或者视图影射成实体定义,且这种影射是1对1影射。从上图可以看到,在EF中,可以建立一个概念模型,将多个表影射到一个实体定义;于是,整个世界清静了……

  我也只是撇了一眼,还没有用过EF,不知道自己理解的对不对;这里只是做个引子,有兴趣的话,各位可以自己研究研究,记得把研究结果分享给我/:)

  最有来个总结(由于个人认知的局限性,这些结论可能不一定正确):

    可行性   缺点
扩展默认实体定义 否 --
使用Translate来返回自定义实体 否 --
执行TSQL返回自定义实体 否 --
继承默认实体定义 否 --
显式自定义实体 是 麻烦,要自己Code,定义新的实体类型
使用视图/存储过程/自定义函数 是 不够灵活,无法为每个应用场景都去订制视图
自定义对象转换器 是 继承关系可能会被滥用;返回的实体集合是个黑盒子,上层可能不知道实体的哪些属性可用,哪些不可用
Entity Framework 貌似可行 --

你可能感兴趣的:(sql,C++,c,LINQ,WCF)