您的位置:首页 > 数据库

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

2014-08-21 12:08 302 查看
原文: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:\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貌似可行--
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: