提示37. 怎样进行按条件包含(Conditional Include)

问题

几天前有人在StackOverflow上询问怎样进行按条件包含。

他们打算查询一些实体(比方说Movies),并且希望预先加载一个相关项目(比方说,Reviews),但又仅要那些匹配一些条件的reviews(如,Review.Stars==5)。

不幸的是EF的预先加载对此没有完整的支持,如,对于 ObjectQuery<Movie>.Include(…) 方法,Include或者是全部加载或者是不加载任何东西。

解决方案

但也有一种变通方法。

下面是使这个解决方案“成真”的一个示例场景:

 1 public class Movie
2 {
3 public int ID {get;set;}
4 public string Name {get;set;}
5 public string Genre {get;set;}
6 public List<Review> Reviews {get;set;}
7 }
8
9 public class Review
10 {
11 public int ID {get;set;}
12 public int Stars {get;set;}
13
14 public string Summary {get;set;}
15 public Movie Movie {get;set;}
16 public User User {get;set;}
17 }

想象你想检索所有“Horror”电影以及它们所有的5星评论。

你可以这样做:

 1 var dbquery =
2 from movie in ctx.Movies
3 where movie.Genre == “Horror”
4 select new {
5 movie,
6 reviews = from review in movie.Reviews
7 where review.Stars == 5
8 select review
9 };
10 var movies = dbquery
11 .AsEnumerable()
12 .Select(m => m.movie);

现在的问题是这是如何工作的呢?

第一个查询创建了一个匿名类型的新实例,其中包含了每个Horror电影及它的5星评论。

由于调用了 AsEnumerable() 方法,第二个查询使用LINQ to Objects运行于内存中,仅是有匿名类型包装的对象中取出movie。

并且有趣的是每个电影都包含已经加载的其5星评论。

所以这段代码:

1 foreach(var movie in movies)
2 {
3 foreach(var review in movie.Reviews)
4 Assert(review.Rating == 5);
5 }

会顺利通过!

这可以工作因为EF实现了一些称作关联组合(relationship fix-up)的东西。

Relationship fix-up确保当第二个实体进入ObjectContext时相关的对象自动被链接。

并且因为我们同时加载Movie与一个过滤后的前者的评论列表,这两者都进入ObjectContext后,EF会确保它们自动链接,这意味着在Movie.Reviews集合中会出现匹配评论。

例如,条件包含。

在这个主题上有一些不同的合成方法:

执行两个独立的查询:一个查询Movie,一个查询Reviews,然后让关联组合完成剩下的工作。

执行一个如这所示的选择多个类型的查询。

排序关联 – 见提示1

一旦你理解关联组合如何工作,你可以真正充分利用它。

Enjoy。

提示38. 怎样在数据服务框架(.NET Data Service,开发代号Astoria)中使用Code Only

通常创建一个ADO.NET数据服务(又称Astoria)的服务的方法是创建一个继承自DataService<T>的类。

public class BloggingService : DataService<BloggingEntities>

如果你要在底层使用Entity Framework,你提供的T的类型必须继承自ObjectContext。

大部分时间这可以很好的工作,但是使用CodeOnly时不可以,而上面所述就是原因。

在DataService框架内部构建了一个BloggingEntities的实例,并由其MetadataWorkspace得到模型。

问题是如果你使用Code-Only来配置模型,构造BloggingEntities的唯一方法是通过Code-Only ContextBuilder,而Astoria对此一无所知。

嗯…

谢天谢地这有一个很简单的变通方法,你只需像这样重写DataServie<T>的 CreateDataSource() 方法:

1 protected override BloggingService CreateDataSource()
2 {
3 //Code-Only code goes here:
4 var contextBuilder = GetConfiguredContextBuilder();
5 var connection = GetSqlConnection();
6 return contextBuilder.Create(connection);
7 }

正如你所见,这相当简单。

要点

由于性能原因,避免每次重新配置ContextBuilder花费的开销很重要,所以 GetConfiguredContextBuilder() 方法应该仅创建并配置builder一次,并缓存这个builder用于接下来的调用。

警告

这条提示仅适用于.NET 4.0 Beta2及以上版本。

Code-Only仅工作于.NET 4.0并作为一个单独的下载(本文写作时)。ADO.NET数据服务(又称Astoria)*将*作为.NET 4.0的一部分发行,但并不在Beta1中,所以暂时不能在Astoria中使用CodeOnly,你不得不等待Astoria出现在.NET 4.0 Beta2中,可能你也等待Code-Only的另一个发布。

这意味着在可以尝试这个提示前,你还需的呢古代一小段时间。

提示39. 怎样设置重叠的关联 – 仅EF 4.0

场景:

在EF 4中有了外键关联(FK Relationship),首次出现在.NET 4.0 Beta2中,所以现在有可能有一个像这样的模型:

 1 public class Division
2 {
3 public int DivisionID {get;set} // Primary Key
4 public string Name {get;set;}
5 public virtual List<Lawyer> Lawyers {get;set;}
6 public virtual List<Unit> Units {get;set;}
7 }
8 public class Lawyer
9 {
10 public int LawyerID {get;set;} // Primary Key
11 public int DivisionID {get;set;} // Primary Key + FK to Division
12 public string Name {get;set;}
13 public virtual Division Division {get;set;}
14 public virtual List<Unit> Units {get;set;}
15 }
16 public class ProductTeam
17 {
18 public int ProductID {get;set;} // Primary Key
19 public int? DivisionID {get;set;} // FK to Division & Lawyer
20 public int? LawyerID {get;set;} // FK to Lawyer
21 public string Name {get;set;}
22 public virtual Division Division {get;set;}
23 public virtual Lawyer Lawyer {get;set;}
24 }

注意Lawyer有一个由LawyerIDDivisionID组合成的复合主键。

当你开始操作ProductTeam类的时候有趣的事情就出现了,这个类中同时存在LawyerDivision两个引用及必要的FK属性。

如果你进行这样的操作:

1 var team = (from t in ctx.ProductTeams
2 where t.Lawyer.Name == “Fred Bloggs”
3 select t).FirstOrDefault();
4 team.Lawyer = null;
5 ctx.SaveChanges();

这到底做了什么呢?

是否意味着清空team.LawyerIDteam.DivisionID或仅是team.LawyerID呢?

由关系的角度看,清空任何FK属性都足以让关联断开。

嗯…

很难正确的得到用户想要,所以EF使用了一个你可以依赖的一致的规则,而不是引入一些基于命名约定等的奇妙规则:

当用户将一个引用关系设置为null时,EF会清空所有支持关联的可空类型的FK属性,而不管这个FK是否参与了其它关联。

问题:

所以在这种情况下EF清空了DivisionIDLawyerID,因为它们都支持着Lawyer导航属性。

这意味着将Lawyer置空同时*也会*置空Division

然而你真的要那么做吗?

可能是,可能不是。

解决方案:

如果你只想将Lawyer置空,你有两个选择:

更改模型将DivisionID这个FK变为非可空类型,这样EF就只能将LawyerID置空,这样到Division的关联就被完整的保留下来。

但是一个改变模型的解决方案并不总是合适,如果Division真的也需要为可空呢?

更好的选择是直接通过FK属性操作关联:

1 var team = (from t in ctx.ProductTeams
2 where t.Lawyer.Name == “Fred Bloggs”
3 select t).FirstOrDefault();
4 team.LawyerID = null;
5 ctx.SaveChanges();

正如期望的,DivisionID与Division会不受影响的保留下来。

提示40. 怎样通过L2E得到展现层模型

问题:

想象你有这些实体:

 1 public class Product
2 {
3 public int ID { get; set; }
4 public string Name { get; set; }
5 public virtual Category Category { get; set; }
6 }
7 public class Category
8 {
9 public int ID { get; set; }
10 public string Name { get; set; }
11 public virtual List<Product> Products { get; set; }
12 }

但在你的UI中,你想要显示产品 id,产品名称与产品的类别名称。

你可能尝试将这个查询传递到展现层。

1 var displayData = from product in ctx.Products.Include("Category")
2 select product;

但是这样你传了一些不需要的东西,并且把UI绑定到概念模型上产生紧耦合,所以这不是一个好注意。

你可能再尝试一个这样的查询:

1 var displayData = from product in ctx.Products
2 select new {
3 ID = product.ID,
4 Name = product.Name,
5 CategoryName = product.Category.Name
6 };

但是你将很快发现你不可以将匿名类型对象传递给另一个方法,至少在不使用这个不友好的方法下。

解决方案:

大部分人认为LINQ to Entities只可以查询得到Entity与匿名类型。

但是事实上它可以查询得到任何有一个默认构造函数的非泛型类型的对象。

总之这就意味着你可以创建一个这样的视图类:

1 public class ProductView
2 {
3 public int ID { get; set; }
4 public string Name { get; set; }
5 public string CategoryName { get; set; }
6 }

然后这样编写:

1 var displayData = from product in ctx.Products
2 select new ProductView {
3 ID = product.ID,
4 Name = product.Name,
5 CategoryName = product.Category.Name
6 };

这样将这个对象传递到视图中就没有问题了。

我自己也是刚刚发现这个方法,我曾总是假定这会失败。

所以这是一个很好的惊喜。

提示41. 怎样直接对数据库执行T-SQL

有时候你会发现你需要执行一个Entity Framework不支持的查询或命令。事实上这个问题对于大部分ORM很普遍,这也是其中大部分ORM给数据库留了一个后门的原因。

Entity Framework同样有一个后门…

.NET 3.5 SP1

在.NET 3.5 SP1中,你可以通过ObjectContext得到到底层数据的连接。

调用ObjectContext.Connection返回一个IdbConnection对象,但这不是我们需要的那一个,这是一个EntityConnection。而EntityConnection有一个StoreConnection属性可以返回我们需要的对象:

1 var entityConn = ctx.Connection as EntityConnection;
2 var dbConn = entityConn.StoreConnection as SqlConnection;

一旦你有了这个connection,你就可以自由的以一般ADO.NET的方式执行一个查询或命令:

 1 dbConn.Open();
2 var cmd = new SqlCommand("SELECT * FROM PRODUCTS", dbConn );
3 using (var reader = cmd.ExecuteReader())
4 {
5 while (reader.Read())
6 {
7 Console.WriteLine("Product: ID:{0} Name:{1} CategoryID:{2}",
8 reader[0].ToString(),
9 reader[1].ToString(),
10 reader[2].ToString()
11 );
12 }
13 }
14 dbConn.Close();

很容易吧?

.NET 4.0

在.NET 4.0中甚至更好。有2个新方法被直接加入到OjbectContext中。

ExecuteStoreCommand(..) 用于执行命令

ExecuteStoreQuery<T>(..) 用于执行查询

使用 ExecuteStoreQuery<T>(..) 

如果你使用 ExecuteStoreQuery<T>(..) ,EF会为你创建T的实例并填充。所以你可以这样写:

1 foreach(var product in ctx.ExecuteStoreQuery<Product>(sql))
2 {
3 Console.WriteLine("Product: ID:{0} Name:{1} CategoryID:{2}",
4 product.Id,
5 product.Name,
6 product.CategoryId
7 );
8 }

要使这段代码工作,查询返回的列名必须必须与类中的属性名称相匹配,并且类必须有一个默认的构造函数。但是类甚至不需要为一个Entity。

所以如你有一个这样的类:

1 public class ProductView
2 {
3 public int ID { get; set; }
4 public string Name { get; set; }
5 public string CategoryName { get; set; }
6 }

要查询得到这个类的实例,你只需编写返回ID,Name与CategoryName列的SQL即可。

如,像这样:

 1 string SQL = @"SELECT P.ID, P.Name, C.Name AS CategoryName
2 FROM Products P
3 JOIN Categories C
4 ON P.CategoryID = C.ID";
5 foreach (var pv in ctx.ExecuteStoreQuery<ProductView>(SQL))
6 {
7 Console.WriteLine("{0} {1} {2}",
8 pv.ID,
9 pv.Name,
10 pv.CategoryName
11 );
12 }

当然,这个例子只是出于演示目的,一般情况下查询会更复杂,如,一些LINQ to Entities原生不能处理的工作。

对于这个特殊的例子,你可以很容易的使用标准LINQ to Entities代码来完成,见提示40你将知道怎样做。

编辑 ExcuteStoreQuery<T>(..) 返回的实体

如果创建的类确实为一个实体,并且你想要编辑它们,你需要多提供一些信息:

1 var productToEdit = ctx.ExecuteStoreQuery<Product>(sql,
2 "Products",
3 MergeOption.PreserveChanges
4 ).Single();
5 productToEdit.CategoryId = 6;
6 ctx.SaveChanges();

第二个参数是Product所属的EntitySet的名称,第三个参数告诉EF怎样将这些Entity与可能已存在于ObjectContext中的副本合并。

如果你正确的完成了这些,调用 SaveChanges() 时会将对productToEdit做的更改提交回数据库。

使用 ExecuteStoreCommand() :

这个非常简单,你执行一些命令,例如一个批量更新方法,你将得到提供程序内部返回的任何数据,具有代表性的是受影响的行数。

1 // 10% inflation day!
2 ctx.ExecuteStoreCommand(
3 "UPDATE Products SET Price = Price * 1.1"
4 );

这是那样简单。

Enjoy。

(翻译)Entity Framework技巧系列之十 - Tip 37 - 41的更多相关文章

  1. Entity Framework技巧系列之十 - Tip 37 - 41

    提示37. 怎样进行按条件包含(Conditional Include) 问题 几天前有人在StackOverflow上询问怎样进行按条件包含. 他们打算查询一些实体(比方说Movies),并且希望预 ...

  2. Entity Framework技巧系列之十二 - Tip 46 - 50

    提示46. 怎样使用Code-Only排除一个属性  这次是一个真正简单的问题,由StackOverflow上这个问题引出.  问题:  当我们使用Code-Only把一个类的信息告诉Entity F ...

  3. Entity Framework技巧系列之十四 - Tip 56

    提示56. 使用反射提供程序编写一个OData Service 在TechEd我收到一大堆有关将数据作为OData暴露的问题. 到目前为止你大概知道可以使用数据服务与Entity Framework将 ...

  4. Entity Framework技巧系列之十三 - Tip 51 - 55

    提示51. 怎样由任意形式的流中加载EF元数据 在提示45中我展示了怎样在运行时生成一个连接字符串,这相当漂亮. 其问题在于它依赖于元数据文件(.csdl .ssdl .msl)存在于本地磁盘上. 但 ...

  5. Entity Framework技巧系列之十一 - Tip 42 - 45

    提示42. 怎样使用Code-Only创建一个动态模型 背景: 当我们给出使用Code-Only的例子,总是由创建一个继承自ObjectContext的强类型的Context开始.这个类用于引导模型. ...

  6. Entity Framework技巧系列之二 - Tip 6 - 8

    提示6. 如何及何时使用贪婪加载 什么时候你需要使用贪婪加载? 通常在你的程序中你知道对查询到的实体将要进行怎样的操作. 例如,如果你查询一个订单以便为一个客户重新打印,你知道没有组成订单的项目即产品 ...

  7. Entity Framework技巧系列之九 - Tip 35 - 36

    提示35. 怎样实现OfTypeOnly<TEntity>()这样的写法 如果你编写这样LINQ to Entities查询: 1 var results = from c in ctx. ...

  8. Entity Framework技巧系列之六 - Tip 20 – 25

    提示20. 怎样处理固定长度的主键 这是正在进行中的Entity Framework提示系列的第20篇. 固定长度字段填充: 如果你的数据库中有一个固定长度的列,例如像NCHAR(10)类型的列,当你 ...

  9. Entity Framework技巧系列之五 - Tip 16 – 19

    提示16. 当前如何模拟.NET 4.0的ObjectSet<T> 背景: 当前要成为一名EF的高级用户,你确实需要熟悉EntitySet.例如,你需要理解EntitySet以便使用 At ...

随机推荐

  1. phpstorm运行在浏览器中执行php文件报502错误

    原因是之前mac自带的php5.5版本被我升级到了5.6 通过phpinfo()查看到目前php5.6的安装目录 重新制定一些interpreter的路径 /usr/local/php5/bin 就可 ...

  2. google calendar api v3

    google api for .net nuget Install-Package Google.Apis.Calendar.v3 oauth2 for asp.net http://www.code ...

  3. Delphi XE5 android 黑屏的临时解决办法

            下载style 然后在deployment里添加进去 http://files.cnblogs.com/nywh2008/styles.rar 在AndroidManifest.tem ...

  4. 1036: [ZJOI2008]树的统计Count - BZOJ

    Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. Q ...

  5. 1041: [HAOI2008]圆上的整点 - BZOJ

    Description 求一个给定的圆(x^2+y^2=r^2),在圆周上有多少个点的坐标是整数.Input rOutput 整点个数Sample Input4Sample Output4HINT n ...

  6. wamp设置实现本机IP或者局域网访问 (转)

    <Directory /> Options FollowSymLinks AllowOverride None Order deny,allow Allow from all #以前是De ...

  7. 10+ 最流行的 jQuery Tree 菜单插件

    jstree – jQuery Tree Plugin With HTML & JSON Data jstree is a lightweight and flexible jQuery pl ...

  8. bzoj 3676: [Apio2014]回文串 回文自动机

    3676: [Apio2014]回文串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 844  Solved: 331[Submit][Status] ...

  9. js学习之函数表达式及闭包

    来自<javascript高级程序设计 第三版:作者Nicholas C. Zakas>的学习笔记(七) 直接切入主题~ 定义函数的方式有两种: 函数声明 function functio ...

  10. sequel 连接不上,命令行能连上

    Sequel pro won't connect anymore I'm running into some trouble right now. I worked yesterday on my d ...