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

问题

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

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

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

解决方案

但也有一种变通方法。

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

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

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

你可以这样做:

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

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

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

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

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

所以这段代码:

  1. 1 foreach(var movie in movies)
  2. 2 {
  3. 3 foreach(var review in movie.Reviews)
  4. 4 Assert(review.Rating == 5);
  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. 1 protected override BloggingService CreateDataSource()
  2. 2 {
  3. 3 //Code-Only code goes here:
  4. 4 var contextBuilder = GetConfiguredContextBuilder();
  5. 5 var connection = GetSqlConnection();
  6. 6 return contextBuilder.Create(connection);
  7. 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. 1 public class Division
  2. 2 {
  3. 3 public int DivisionID {get;set} // Primary Key
  4. 4 public string Name {get;set;}
  5. 5 public virtual List<Lawyer> Lawyers {get;set;}
  6. 6 public virtual List<Unit> Units {get;set;}
  7. 7 }
  8. 8 public class Lawyer
  9. 9 {
  10. 10 public int LawyerID {get;set;} // Primary Key
  11. 11 public int DivisionID {get;set;} // Primary Key + FK to Division
  12. 12 public string Name {get;set;}
  13. 13 public virtual Division Division {get;set;}
  14. 14 public virtual List<Unit> Units {get;set;}
  15. 15 }
  16. 16 public class ProductTeam
  17. 17 {
  18. 18 public int ProductID {get;set;} // Primary Key
  19. 19 public int? DivisionID {get;set;} // FK to Division & Lawyer
  20. 20 public int? LawyerID {get;set;} // FK to Lawyer
  21. 21 public string Name {get;set;}
  22. 22 public virtual Division Division {get;set;}
  23. 23 public virtual Lawyer Lawyer {get;set;}
  24. 24 }

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

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

如果你进行这样的操作:

  1. 1 var team = (from t in ctx.ProductTeams
  2. 2 where t.Lawyer.Name == Fred Bloggs
  3. 3 select t).FirstOrDefault();
  4. 4 team.Lawyer = null;
  5. 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. 1 var team = (from t in ctx.ProductTeams
  2. 2 where t.Lawyer.Name == Fred Bloggs
  3. 3 select t).FirstOrDefault();
  4. 4 team.LawyerID = null;
  5. 5 ctx.SaveChanges();

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

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

问题:

想象你有这些实体:

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

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

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

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

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

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

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

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

解决方案:

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

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

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

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

然后这样编写:

  1. 1 var displayData = from product in ctx.Products
  2. 2 select new ProductView {
  3. 3 ID = product.ID,
  4. 4 Name = product.Name,
  5. 5 CategoryName = product.Category.Name
  6. 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. 1 var entityConn = ctx.Connection as EntityConnection;
  2. 2 var dbConn = entityConn.StoreConnection as SqlConnection;

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

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

很容易吧?

.NET 4.0

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

ExecuteStoreCommand(..) 用于执行命令

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

使用 ExecuteStoreQuery<T>(..) 

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

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

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

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

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

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

如,像这样:

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

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

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

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

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

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

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

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

使用 ExecuteStoreCommand() :

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

  1. 1 // 10% inflation day!
  2. 2 ctx.ExecuteStoreCommand(
  3. 3 "UPDATE Products SET Price = Price * 1.1"
  4. 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. EasyUI Layout Full - Not Correct in IE8

    EasyUI Full布局在IE10,IE9下正常,IE8无效果,标记一下有知道的可以留个言! 如图 IE 10 IE 8

  2. UIImageView加抖动效果(转)

    CGAffineTransform moveRight = CGAffineTransformTranslate(CGAffineTransformIdentity, 20, 0); CGAffine ...

  3. js添加创建节点和合并节点

    var _div = document.createElement("div"), //创建节点 txt1 = document.createTextNode("123& ...

  4. uva 11461

    简单 打个表 case数不超过200 数据比较水  木有超时的风险~~ /*************************************************************** ...

  5. C++11多线程教学(二)

    C++11多线程教学II 从我最近发布的C++11线程教学文章里,我们已经知道C++11线程写法与POSIX的pthreads写法相比,更为简洁.只需很少几个简单概念,我们就能搭建相当复杂的处理图片程 ...

  6. 程序自动生成Dump文件

    前言:通过drwtsn32.NTSD.CDB等调试工具生成Dump文件, drwtsn32存在的缺点虽然NTSD.CDB可以完全解决,但并不是所有的操作系统中都安装了NTSD.CDB等调试工具.了解了 ...

  7. 发现一个可以在线运行JS代码的网站

    平时可以在这里玩 http://jsbin.com/

  8. 使用JS动态创建含有1000行的表格

    function addTable(){ createTable1(1000); //createTable2(1000); //createTable3(1000); //createTable4( ...

  9. [mock]12月27日

    一开始介绍项目,最后的反馈是,还是说得不清楚,需要再准备准备. 然后两道题,第一题是有个数组,有2*n个数字,从1~n.比如n=3的数组,{1,2,2,3,1,3}.然后两两相同的数字删除,每次删除得 ...

  10. HeadFirst设计模式之状态模式

    一. 1. 2.The State Pattern allows an object to alter its behavior when its internal state changes. Th ...