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

原文: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数据库为示例),现在我们要在界面上展示某个用户什么时间订购了哪些产品。

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

如果允许在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查询执行的结果,没有取得我们需要的数据:

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

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

  • 否则会引发异常。

    我愣是看了好多遍,还是没有搞明白,为啥将结果集转换到对象集合时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:\04.Other\WinForm\TestLINQ\DataContextExtensions.cs:行号 74
           在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, IQueryable query, Boolean withNoLock) 位置 D:\04.Other\WinForm\TestLINQ\DataContextExtensions.cs:行号 53
           在 TestLINQ.DataContextExtensions.ExecuteQuery[T](DataContext dataContext, IQueryable query) 位置 D:\04.Other\WinForm\TestLINQ\DataContextExtensions.cs:行号 28
           在 TestLINQ.Program.GetOrderInfo(String customerID) 位置 D:\04.Other\WinForm\TestLINQ\Class1.cs:行号 49
           在 TestLINQ.Program.Main(String[] args) 位置 D:\04.Other\WinForm\TestLINQ\Class1.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过程中走过的一些弯路,列出来供大家参考,避免重蹈我的覆辙。

    ---------------------------------------------------------------------------------------------------------------

    --------------------------我是华丽的分割线(happyhippy.cnblogs.com)-------------------------------

    ---------------------------------------------------------------------------------------------------------------

    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!

    Linq to Sql:N层应用中的查询(上) : 返回自定义实体
         但是,这样操作的话,每次我们都要去手工编写代码,将我们需要的字段封装成一个实体类型。

    结合上面第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<命名类型>,使用该转换器的代码示例如下图所示:

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

    下面是执行结果(其中CustomerExt使用第4节中的实体定义,继承自Customers):Linq to Sql:N层应用中的查询(上) : 返回自定义实体

    使用起来还算比较清爽;当然,也有不足之处,性能怎样是一个考虑点,还有就是如上面的运行结果截图,一些被我们坎掉的字段也会显示出来;虽然这些额外字段的值都为空,但考虑下列情况: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:N层应用中的查询(上) : 返回自定义实体

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

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

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

      可行性 缺点
    扩展默认实体定义 --
    使用Translate来返回自定义实体 --
    执行TSQL返回自定义实体 --
    继承默认实体定义 --
    显式自定义实体 麻烦,要自己Code,定义新的实体类型
    使用视图/存储过程/自定义函数 不够灵活,无法为每个应用场景都去订制视图
    自定义对象转换器 继承关系可能会被滥用;返回的实体集合是个黑盒子,上层可能不知道实体的哪些属性可用,哪些不可用
    Entity Framework 貌似可行 --
    上一篇:Spark RDD/Core 编程 API入门系列 之rdd实战(rdd基本操作实战及transformation和action流程图)(源码)(三)


    下一篇:C/C++中调用python文件