`
anzitlan10
  • 浏览: 18586 次
  • 性别: Icon_minigender_1
  • 来自: 柳州
社区版块
存档分类
最新评论

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

    博客分类:
  • .net
阅读更多
如果允许在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 貌似可行 --

分享到:
评论

相关推荐

    LINQ自定义实体及LINQtoDATASET

    本文档详细介绍了LINQ TO DATASET以以及LINQ TO SQL创建实体类的例子,本人亲自测试通过。可以做参考,源代码,注释很清楚

    LINQ 实战 7/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 1/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 3/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 4/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 2/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 11/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 5/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 6/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 8/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 10/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    LINQ 实战 9/11

    本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ...

    【建模&代码生成利器】EntityDeveloper

    Entity Developer是一个用于LINQ to SQL建模和代码产生的强大工具。你可以从零开始或者从一个现有数据库逆向设计LINQ to SQL。 Entity Developer设计LINQ to SQL模型的目标: SQL Server使用Entity Developer for ...

    linqTempDemo

    其中DAL层使用LinqTo Sql,BLL层使用CodeSimith模板生成实体类。 目的是推进linq技术的使用。 实例demo下载:linqTempDemo 一.CodeSimith生成Linq To Sql文件模板功能介绍 1、生成常用的增、删、改的逻辑方法...

    Devart Entity Developer v6.4.719 Professional破解版,支持vs2019

    强大的代码生成 Entity Developer提供基于T4类似的模板生成代码框架,针对不同使用情况提供大量预定义的模板,模板化生成上下文,实体,映射,支持流,属性和XML映射,包括持久层感知和持久层无感知实体,支持验证...

    LinqCache:LINQ 缓存框架

    链接缓存LinqCache 是一个用于缓存 LINQ 查询的简单而强大的框架。 无论后备数据存储是对象、XML、LINQ-to-SQL、实体框架、NHibernate 还是任何其他支持 ... 无限期缓存 LINQ-to-SQL/实体框架查询 using ( var context

    ASP.NET.4揭秘

    20.2.2 使用linq to sql designer创建实体735 20.2.3 构建实体关联737 20.2.4 使用linqdatasource控件738 20.3 使用linq to sql执行标准数据库命令741 20.3.1 linq to objects与linq to sql741 20.3.2 使用linq to ...

    ASP.NET 控件的使用

    16.1.4 绑定到LINQ to SQL查询 502 16.1.5 绑定到Web服务 503 16.2 使用ObjectDataSource控件与参数 506 16.2.1 使用不同的参数类型 509 16.2.2 作为参数传递对象 511 16.3 使用ObjectDataSource控件分页、排序和...

    ASP.NET客户资产管理系统源码

    基于微软Microsoft Visual Studio2010和SQL2005开发,采用三层架构:Web层Model、BLL、IDAL、DAL,前端使用中国人开发的DWZ富客户端框架给合一般处理程序ashx;后台使用Linq结合ADO与数据库交互,DAL类继承及实现...

    Microsoft .NET Framework 3.5 Service Pack 1

    例如,基于 .NET Framework 的客户端可以使用 LINQ 查询数据服务,也可以使用简单的 .NET Framework 对象层更新此服务中的数据。 现在,Windows Communication Foundation 改进了对互操作性的支持,增强了部分受...

Global site tag (gtag.js) - Google Analytics