首页 > 代码库 > Linq to Sql:N层应用中的查询(上) : 返回自定义实体

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

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

L2S_Search_Partial

    而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:\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!

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

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

L2S_ObjectConverter

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

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

EF_LinqInAction

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

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

 

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

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