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

否则会引发异常。

我愣是看了好多遍,还是没有搞明白,为啥将结果集转换到对象集合时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!


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

结合上面第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 貌似可行 --

Linq to Sql:N层应用中的查询(上) : 返回自定义实体的更多相关文章

  1. Linq to Sql:N层应用中的查询(下) : 根据条件进行动态查询

    原文:Linq to Sql:N层应用中的查询(下) : 根据条件进行动态查询 如果允许在UI层直接访问Linq to Sql的DataContext,可以省去很多问题,譬如在处理多表join的时候, ...

  2. 在Linq to sql 和 Entity framework 中使用lambda表达式实现left join

    在Linq to sql 和 Entity framework 中使用lambda表达式实现left join 我们知道lambda表达式在Linq to sql 和 Entity framework ...

  3. Linq to Sql : 动态构造Expression进行动态查询

    原文:Linq to Sql : 动态构造Expression进行动态查询 前一篇在介绍动态查询时,提到一个问题:如何根据用户的输入条件,动态构造这个过滤条件表达式呢?Expression<Fu ...

  4. Linq to sql 实现多条件的动态查询(方法一)

    /// <summary> /// Linq to sql 多字段动态查询 /// </summary> /// <returns></returns> ...

  5. linq to sql: 在Entityfamework Core中使用多个DbContext

    最近在学习DotNetCore并做一个自己的小项目,分为了多个数据库,AccountDbContext和BlogDbContext, 发blog的时候需要用到Account的信息,但是再Blog中只记 ...

  6. 让LINQ中的查询语法使用自定义的查询方法

    使用LINQ时有两种查询语法:查询语法和方法语法 查询语法:一种类似 SQL 语法的查询方式 方法语法:通过扩展方法和Lambda表达式来创建查询 例如: List<, , , }; //查询语 ...

  7. LINQ之路10:LINQ to SQL 和 Entity Framework(下)

    在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...

  8. LINQ之路 9:LINQ to SQL 和 Entity Framework(上)

    在上一篇中,我们从理论和概念上详细的了解了LINQ的第二种架构“解释查询”.在这接下来的二个篇章中,我们将使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术的 ...

  9. LINQ To SQL

    议程 1.LINQ To SQL概述 2.LINQ To SQL对象模型 3.LINQ To SQL查询 用到的数据库 SQL Server 2005,数据库名为Test. 两张表,分别为Studen ...

随机推荐

  1. Team Foundation Server 2015使用教程--读取器tfs组的checkin权限修改

  2. Team Foundation Server 2015使用教程--默认团队checkin权限修改

  3. C++学习笔记36 (模板的细节明确template specialization)和显式实例(template instantiation)

    C++有时模板很可能无法处理某些类型的. 例如: #include <iostream> using namespace std; class man{ private: string n ...

  4. 终结者:具体解释Nginx(一)

            相信非常多人都听过Nginx.这个小巧的东西能够和Apache及IIS相媲美. 那么它有什么作用呢?一句话.它是一个减轻Web应用server(如Tomcat)压力和实现Web应用se ...

  5. NSIS:强制结束软件进程

    原文NSIS:强制结束软件进程 有时候,我们选择卸载软件后发现安装目录中的主文件依然存在,不是我们卸载代码写的不对,而是卸载的时候软件根本就没有关闭! 在卸载前加上下面这个宏可以在一定程度上免除上述的 ...

  6. Java NIO系列教程(三) Buffer(转)

    Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO ...

  7. Install Typical IIS Workloads

    原文 Install Typical IIS Workloads Introduction The IIS 7.0 and above modular architecture is designed ...

  8. JavaEE(17) - JPA查询API和JPQL

    1. 获取查询结果 2. JPQL函数和JPQL表达式 #1. 使用from子句 #2. 查询部分属性 #3. 查询中使用构造器 3. JPQL的关联查询和多态查询 #1. 多态查询 #2. 隐式连接 ...

  9. JAVA转让JS功能

    今天,在发展中使用js和Java互动.通常我们使用更多的是js转让Java方法.可以使用dwr.Ajax.jquery.突然发现Java转让js然后,我真的没见过,今天,互联网提供以下信息,顺便总结: ...

  10. hdu 1251(字典树)

    题目链接:http://acm.hdu.edu.cn/status.php?user=NYNU_WMH&pid=1251&status=5 Trie树的基本实现 字母树的插入(Inse ...