(翻译)Entity Framework技巧系列之十二 - Tip 46 - 50
提示46. 怎样使用Code-Only排除一个属性
这次是一个真正简单的问题,由StackOverflow上这个问题 引出。
问题:
当我们使用Code-Only把一个类的信息告诉Entity Framework,默认情况下每个属性会成为Entity的一部分,并作为一个存储于数据库中的结果。
通常这是你想要的结果。
但是也有例外,考虑这个类:
1 public class Person{ 2 public int ID {get;set;} 3 public string Firstname {get;set;} 4 public string Surname {get;set;} 5 public string Fullname {get;set;} 6 }
这里Fullname实际上仅是Firstname与Surname联接在一起,所以将Fullname单独存储于数据库中会有冗余。你会想要忽略Fullname。
该怎样做呢?
解决方案1 – 移除Setter – 亦'一般不做为选择'
要成为'Entity'的一个属性,Entity Framework必须可以读写这个属性,所以如果你移除setter,这个CLR的中属性将不再成为'Entity'的属性。
不幸的是不总是有可能做这类事,更重要的是做这种*只*可以工作于Code-Only中的东西与Persistence Ignorance相反。
我们需要另一种解决方案…
解决方案2 – 显示映射你想要的所有属性
如果你进行显示映射,你由映射中排除的属性会被忽略,而不会允许Code-Only按照惯例来映射。
所以以下这个映射:
1 builder.Entity<Person>().MapSingleType(p => new { 2 p.ID, 3 p.Firstname, 4 p.Surname 5 });
伴随着指定映射带来的副作用是需要告诉Code-Only忽略FullName属性。
长期解决方案?
显然需要强制映射每一个属性而*只*是为了排除一个属性不是一种理想的解决方案,尤其当其它一切属性都是"按惯例的"。
像这样的东西会更好:
1 builder.Entity<Person>().Exclude(p => p.Fullname);
当前用于Beta2的CTP2版中还不支持这种写法,但这会被考虑加入将来版本的Code-Only中…
提示47. 为什么fix-up会使更改关联变得困难
问题:
看这段代码:
1 Category oldCategory = ctx.Categories 2 .Include("Products") 3 .First(c => c.Name == "Drink"); 4 5 Category newCategory = new Category {Name = "Beverage"}; 6 7 foreach(Product product in oldCategory.Products) 8 { 9 newCategory.Products.Add(product); 10 }
在这个例子中整个解决方案可能仅是为了将oldCategory重命名为”Beverages” – 如果是实际中的例子这会变得越来越困难,你在微软泡沫中工作的时间会更长:)。
关键是你试图将Product由一个Category移动到另一个,或者更抽象的说我们正将实体由一个集合关系移动到另一个。
不幸的是上面的代码会失败:
下层的集合被改变了?
什么?
我们没有改变它!
对吗?!
错。
相反我们触发了一个更改。
如果一个Product只可以属于一个Category,给Product指定一个不同的Category会导致EF将其由旧的Category(即oldCategory.Products)中自动移除。
所以添加到一个新Category并由旧Category移除会导致迭代无法继续,因为‘集合被修改’。
注意:错误发生于你试图由oldCategory.Products集合中获取第二个产品时。
解决方案:
一旦你理解了这个问题,解决方案就不值一提了。
在你更改任何关系前迭代旧的集合,使用ToArray()或一个类似的方法。
1 var products = oldCategory.Products.ToArray(); 2 foreach(Product product in products) 3 { 4 newCategory.Products.Add(product); 5 }
正如你看到的,变通方案并不是完全是难事。
另一方面,了解你为什么需要变通方案更重要;)
提示48. 怎样WCF中宿主一个数据服务
许多人想知道是否可以在WCF中宿主ADO.NET Data Service。
答案是肯定的,事实上只要你设置好引用就可以,很简单。
步骤1 – 建立你的项目
我做的项目如下这样:
这个例子中我使用的是VS2010 beta2,在VS 2008 SP1中这同样有效。
如你所见项目中有一个控制台应用程序,一个名为ProductsContext的EF实体数据模型与一个名为ProductsCatalog.svc数据服务来暴露ProductsContext。
实际上将ProductsCatalog.svc加入控制台应用需要一点技巧-如果你尝试使用添加新项是找不到添加数据服务这项的,因为这个对话框是项目类型敏感的,所以它会过滤掉它认为不像一个控制台应用的选项-我通过创建一个数据服务类型的临时Web应用并将其拷贝到我的控制台应用项目来实现这个目的。
你需要添加到适当程序集System.Data.Entity(EF), System.Data.Services&System.Data.Services.Client(Astoria)与System.ServiceModel&System.ServiceModel.Web(WCF)。
现在我们把项目准备好了,下一步…
步骤2 – 暴露你的数据服务
现在,Phani提供了这些代码,这就是暴露数据服务所需的全部:
1 string uriBaseAddress = "http://localhost:998"; 2 Uri[] uriArray = { new Uri(uriBaseAddress) }; 3 Type serviceType = typeof(ProductsCatalog); 4 using(WebServiceHost host = new WebServiceHost(serviceType, uriArray)) 5 { 6 try 7 { 8 host.Open(); 9 Console.ReadKey(); 10 } 11 catch (Exception ex) 12 { 13 Console.WriteLine("An exception occurred:"); 14 Console.WriteLine(ex.ToString()); 15 host.Abort(); 16 } 17 Console.WriteLine("Aborting"); 18 Console.ReadKey(); 19 }
如你所见我们简单的为数据服务类创建了一个WebServiceHost,并提供你希望服务绑定到的URL。
现在你应该可以完成如图所示的操作了:
所有一切都是通过一个小控制台应用程序来暴露的。
有意思吧?
提示49. 怎样找到数据服务的bug
最近一段时间我一直在尝试创建一个自定义的数据服务提供程序,又称DSP(注:Data Service Provider的缩写)。
到目前为止我已设法设置好所有的元数据,所以浏览$metadata可以很好的工作。我也完成了一些简单的查询工作。
基本上一切很好。
至少直到我尝试这个URL:
我不能准确的知道发生了什么,并且我也不能由Fiddler得到任何帮助,因为出现问题的服务运行在内置的VS开发服务器上。
并且因为我的数据服务提供程序估计设计为松散类型,我需要辨别出这个错误的:
与这个正确的:
之间的不同。
你能看出问题吗?
如果能这很好,你的眼力比我强…
结果你不需要好眼力,你只需了解DataService<>的HandleException,你可以像这样重写这个方法:
一旦我进行了这步,可以很容易辨别出我的一个字典对象中有一个以ID而不是以Id为key的项。
修复这个问题后,查询就可以很好的工作了:
当开发一个数据服务提供程序时了解HandleException绝对至关重要,但它也有更一般的用途,例如审核,错误记录等你能叫得上名字的功能。
噢,你注意到我的数据服务提供程序暴露了来自字典对象列表的数据了吗?
那是另一天的故事J
提示50. 怎样使用JQuery查询数据服务
最近我花了一些时间尝试JQuery。
因为数据服务可以以JSON格式暴露数据,我认为可以使用JQuery由数据服务获取一些数据。
结果这相当容易。
这个例子不会赢得任何奖项,但是它会展示给你基础并帮助你早日上手。
HTML:
首先我制作了一个页面:
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title></title> 4 </head> 5 <script src="Scripts/jquery-1.3.2.js" type="text/javascript"> 6 </script> 7 <script type="text/javascript"> 8 // JSON CODE IS GOING TO GO HERE 9 </script> 10 <body> 11 <a href='#' id="aShowProducts">Show Products</a><br /> 12 <a href='#' id="aShowCategories">Show Categories</a><br /> 13 <div id="divResults" /> 14 </body> 15 </html>
如你所见这很简单。
JQuery Code
下一步是JQuery代码。
使用JQuery的第一步总是编写订阅Document加载完成的事件的代码:
1 var divResults; 2 var aShowProducts; 3 var aShowCategories; 4 var ajaxRequest; 5 6 $(document).ready(function () { 7 divResults = $('#divResults'); 8 aShowCategories = $('#aShowCategories'); 9 aShowProducts = $('#aShowProducts'); 10 aShowProducts.click(function () { 11 GetData('Products'); 12 }); 13 aShowCategories.click(function () { 14 GetData('Categories'); 15 }); 16 });
当文档加载完成,首先我设置一些全局变量:
l divResults (如 <div id="divResults" /> ): 承载查询的结果。
l aShowProducts (如 <a href="#" id="aShowProducts" …</a> ): 'Show Products'链接
l aShowCategory: 'Show Categories'链接
l ajaxRequest: 是一个承载当前请求的变量,所以如果需要我们可以终止它。
接下来我给两个超链接的点击事件订阅GetData方法,并给方法传入适当的Data Service资源集名称。
获取结果
GetData函数如下所示:
1 function GetData(set) { 2 var materializer; 3 switch (set) { 4 case 'Categories': 5 materializer = GetRowForCategory; 6 break; 7 case 'Products': 8 materializer = GetRowForProduct; 9 break; 10 default: 11 alert('problems'); 12 } 13 14 if (ajaxRequest!= null) 15 ajaxRequest.abort(); 16 17 ajaxRequest= $.getJSON( 18 "ProductsService.svc/" + set, 19 function (data) { 20 var array = []; 21 array.push("<table>"); 22 $.each(data.d, function (i, item) { 23 materializer(array, item) 24 }); 25 array.push("</table>"); 26 divResults.html(array.join("")); 27 } 28 ); 29 }
这个操作取决于'set'参数,它选择一个函数用来取得查询结果,这个函数将为相应的资源类型生成一个<tr>。
所以对于'Categories'集我使用这个函数:
1 function GetRowForCategory(array, item) 2 { 3 array.push("<tr><td>"); 4 array.push(item.Id); 5 array.push("</td><td>"); 6 array.push(item.Name); 7 array.push("</td></tr>"); 8 }
对于'Products'集函数如下:
1 function GetRowForProduct(array,item) 2 { 3 array.push("<tr><td>"); 4 array.push(item.Id); 5 array.push("</td><td>"); 6 array.push(item.Name); 7 array.push("</td></tr>"); 8 }
一旦我们选择了正确的实体化器,下一步我们取消一切进行中的AJAX请求。
接着使用 $.getJSON(…) 初始化一个新的AJAX请求来获取由"ProductsService.svc/Products"或"ProductsService.svc/Categories"获得JSON。
结果的JSON(即数据)被传入回调函数用于构建一个html表格,通过:
l 使用 array.push(…) 这个方法来生成html字符串。
l 接着使用 $.each(data.d) 遍历实体集合,并调用特定的实体化器来为每一个实体创建一个新的 <tr />
自己尝试
完成后的页面在这里,你可以另存为查看。
你需要做的就是把这个html文件放到与你的DataServie相同的网站中,更改url来指向你的DataServie并修改实体化器选择器的代码与实体化器本身来匹配你的资源集与资源类型。
不是太难。
下一步需要有更多的经验以构建更多有趣的查询,见提示44获取些灵感。
BTW,我是Jquery新手,如果你发现一个菜鸟级错误请告诉我。