提示42. 怎样使用Code-Only创建一个动态模型
背景:
当我们给出使用Code-Only的例子,总是由创建一个继承自ObjectContext的强类型的Context开始。这个类用于引导模型。
例如这个类(处于简化问题考虑省略了属性体):
1 public class MyContext : ObjectContext 2 { 3 public ObjectSet<Category> Categories { get; } 4 public ObjectSet<Product> Products { get; } 5 }
这告诉CodeOnly使用2个EntitySet,一个称作Categories的Category实体的集合与一个称作Products的Product实体的集合,来引导一个模型。
然后如果需要你将来可以通过操作ContextBuilder来改进这个模型。
问题:
但是如果你没有一个强类型的Context类呢?
如果你决定在运行时需要一个模型,但没有一个适合的强类型Context类可以使用。
在今天早些时候一个客户恰好问我这个问题。
解决方案:
最终你可以直接使用ObjectContext。当你通过Code-Only做这个工作时对模型一无所知。但是这不是那么糟糕,所有你需要做的就是明确的告诉Code-Only通常可以由强类型context了解到的所有信息。
例如这个:
1 public ObjectSet<Person> People { get; }
可以被转换为这个:
1 var builder = new ContextBuilder<ObjectContext>(); 2 builder.RegisterSet<Person>(“People”);
有趣吗?
结尾的例子:
这个例子,演示完全不使用强类型ObjectContext下将一个Person对象(BillG)存储到数据库并再次检索出来:
首先是Person类(这是个POCO):
1 public class Person 2 { 3 public int ID { get; set; } 4 public string Firstname { get; set; } 5 public string Surname { get; set; } 6 }
接下来是建立ObjectContext的代码:
1 // Create the contextbuilder, and tell it about the People set. 2 var builder = new ContextBuilder<ObjectContext>(); 3 builder.RegisterSet<Person>("People"); 4 // Create a connection 5 string connstr = @"Data Source=.\SQLEXPRESS;Initial Catalog=PeopleDb;Integrated Security=True;Pooling=False;MultipleActiveResultSets=True"; 6 var conn = new SqlConnection(connstr); 7 8 // Create an ObjectContext from the builder 9 using (ObjectContext ctx = builder.Create(conn)) 10 { 11 // Create the database if it doesn’t already exist 12 if (!ctx.DatabaseExists()) 13 ctx.CreateDatabase(); 14 15 // Create Bill 16 Person p = new Person { 17 ID = 1, 18 Firstname = "Bill", 19 Surname = "Gates" 20 }; 21 22 // Add Bill to the context 23 // UPDATE: thanks to danny for the simplification 24 ctx.CreateObjectSet<Person>().AddObject(p); 25 using the general purpose 26 // AddObject method. 27 // The only tricky part is the EntitySet name with must 28 // be qualified with the the container name, 29 // in this case is ObjectContext. 30 ctx.AddObject("ObjectContext.People", p); 31 ctx.SaveChanges(); 32 // Issue a query against the People set. 33 var bill = (from person in ctx.CreateObjectSet<Person>() 34 where person.Firstname == "Bill" 35 select person).Single(); 36 // Make and Save a change. 37 bill.Firstname = "William"; 38 ctx.SaveChanges(); 39 }
就不是强类型的而论这非常简单了。
提示43. 怎样通过一个Data Service的验证
问题:
当我们编写访问一个Data Service的代码,如SharePoint,客户端程序必须提供一系列有效的凭据,否则你将收到一个”401 Unauthorized”的响应。
针对与DataServie宿主在同一网站上的Silverlight应用,通常会自动处理好这个问题。
但对于如WPF应用,这需要手动处理。
解决方案:
这个解决方案在你执行任何查询或更新之前在DataServieContext上设置凭证属性,像这样:
1 Uri uri = new Uri("http://mflasko-dev/_vti_bin/listdata.svc")); 2 TeamSiteDataContext ctx = new TeamSiteDataContext(uri); 3 ctx.Credentials = System.Net.CredentialCache.DefaultCredentials; 4 …
就是这样。
当然如果需要你可以提供另一套凭证信息,但是一般来说,DefaultCredentials就是你想要的。
提示44. 怎样导航到一个Odata兼容服务
最近我做了一个关于Data Service与Odata的速成课程。
在这个过程中我发现我的笔记可能对大家有用。
这里给出我的备忘单供你快速掌握Odata Url。
注意:Odata服务可能未必完全支持下面这些特性:但是如果它们支持,这就是你使用它们的方法。
服务:
所有服务由一个宿主于某一地方的Data Service开始:
http://server/service.svc
基本查询:
通过资源集(resource set)来访问Data Service实体,像这样:
http://server/service.svc/People
使用主键来请求一个指定的实体,像这样:
http://server/service.svc/People(16)
或通过使用一个到你知道的对象的关联引用:
http://server/service.svc/People(16)/Mother
这请求16号人的母亲。
一旦你可以识别出一个实体,你可以直接导航到它的属性:
http://server/service.svc/People(16)/Mother/Firstname
$value:
但是上一个查询将属性值包在XML中,如果你仅想要单纯的属性值,可以像这样在url后面附加一个$value:
http://server/service.svc/People(16)/Mother/Firstname/$value
$filter:
你可以使用$filter来过滤资源集:
http://server/service.svc/People?$filter=Firstname eq ‘Fred’
注意过滤条件字符串中使用单引号。
而数字不需要引号:
http://server/service.svc/Posts?$filter=AuthorId eq 1
要按时间过滤你需要在过滤条件中标识出时间,如下:
http://server/service.svc/Posts?$filter=CreatedDate eq DateTime‘2009-10-31‘
也可以通过关联引用来过滤:
http://server/service.svc/People?$filter=Mother/Firstname eq ‘Wendy‘
你可以在过滤条件中使用的基本运算符:
Operator |
Description |
C# equivalent |
eq |
equals |
== |
ne |
not equal |
!= |
gt |
greater than |
> |
ge |
greater than or equal |
>= |
lt |
less than |
< |
le |
less than or equal |
<= |
and |
and |
&& |
or |
or |
|| |
() |
grouping |
() |
如果需要还有一系列可以在过滤条件中使用的函数。
$expand:
如果你想在结果中包含相关项目,像这样使用$expand:
http://server/service.svc/Blogs?$expand=Posts
这将返回匹配的Blogs及每个Blog的post。
$select:
一些Data Service允许你将结果限制在仅你需要的属性 – 又称投影 – 例如如果你仅想要匹配Post的Id与Title,你需要如下这样的Url:
http://server/service.svc/Posts?$select=Id,Title
你甚至也可以映射到关联对象的属性,如这样:
http://server/service.svc/Posts?$expand=Blog&$select=Id,Title,Blog/Name
这仅映射到了每个Post的 Id,Title与Blog的名称。
$count:
如果你仅想知道将返回多少条记录,而不检索它们,你需要使用$count:
http://server/service.svc/Blogs/$count
注意$count成为URL的片段之一-它不是查询字符串的一部分-所以如果你想与另一个操作如$filter合并,你需要将$count放在前面,像这样:
http://server/service.svc/Posts/$count?$filter=AuthorId eq 6
这个查询返回作者是6号person的帖子的数量。
$orderby:
如果你需要结果是排序过的,可以使用$orderby:
http://server/service.svc/Blogs?$orderby=Name
返回的结果是升序排序,要进行降序排序你需要:
http://server/service.svc/Blogs?$orderby=Name%20desc
要首先按一个属性排序,然后按另一个属性排序,你需要:
http://server/service.svc/People?$orderby=Surname,Firstname
如果需要也可以在其中加入desc。
$top:
如果你仅想要前10条项目,可以像这样使用$top:
http://server/service.svc/People?$top=10
$skip
如果你仅对某一页数的数据感兴趣,你需要联合使用$top与$skip:
这告诉Data Service跳过前20条匹配结果并返回下10条数据。当每页有10个项而你需要显示第3页结果时这很有用。
注意:通常将$top&$skip与$orderby合并使用是一个好主意,以保证由底层数据源检索到的结果的顺序是一致的。
$inlinecount&$skiptoken:
使用$top与$skip允许客户端控制分页。
但是服务器也需要一种方法来控制分页-以最小化服务初级用户及那些恶意用户所需的负载-OData协议通过Server Driven Paging(服务器驱动的分页)来完成这个任务。
当Server Driven Paging开启后,即使客户端请求所有记录,但它们将只会得到一个页的结果。
正如你想象,这会给客户端应用开发人员带来一点麻烦。
如果客户端需要知道到底有多少结果,它们可以在查询后追加$inlinecount选项,如下:
http://server/service.svc/People?$inlinecount=allpages
结果将会“内联”包含一个总数,及一个服务器生成的用于获取下一页结果的url。
这个服务器生成的url包含一个$skiptoken,这与一个游标或书签等效,以通知服务器由哪重新开始:
http://server/service.svc/People?$skiptoken=4
$links
有时候你只需得到与实体关联的一个特殊实体的url,这时候就用到$links了:
http://server/service.svc/Blogs(1)/$links/Posts
这告诉Data Service返回Blog1相关的所有Posts的链接-即url。
$metadata
如果你需要知道一个Odata兼容的数据服务暴露的模型的信息,你可以通过在服务的根路径后面拼接一个$metadata选项,如下:
http://server/service.svc/$metadata
这将返回一个Data Service暴露的包含概念模型(又称EDM)的EDXM文件。
总结
这会帮助你早日上手。
我将更多的探索Data Service与Odata,我也将与你分享我的所学,请锁定我的博客。
提示45. 怎样在运行时置换EF元数据
背景:
默认情况下Entity Framework将它的元数据作为一个资源嵌入程序集中。
同时它也将一个引用了这些资源连接字符串放入App或Web配置文件中,像这样:
1 <add name="BloggingEntities" connectionString="metadata=res://*/Blogging.csdl|res://*/Blogging.ssdl|res://*/Blogging.msl;provider=System.Data.SqlClient; 2 provider connection string="Data Source=.\SQLEXPRESS;Initial Catalog=TipsDatabase;Integrated Security=True;MultipleActiveResultSets=True"" 3 providerName="System.Data.EntityClient" />
这使其可以很容易的开始:
1 using (BloggingEntities ctx = new BloggingEntities()) 2 {
注意:使用res://*告诉EF在程序集资源内部查找各种各样的元数据。
问题
但是将元数据嵌入作为资源同时意味着它本质上是不可改变的。
假如你需要在运行时更改它怎么办呢?
有很多原因是你想要在运行时更改元数据,但是也许最可能的原因是在生产环境你不得不配合有一套不同数据库设计想法的DBA。
一般情况下这意味着你不得不由于数据库的更改来更改一部分存储模型(SSDL)与映射(MSL)。
这样做完全可以,事实上这是使用Entity Framework的一大好处,只要概念模型或者称CSDL—开发的程序所依赖的东西—保持不变。
解决方案
所以怎样在运行时插入一个不同的MSL与SSDL呢?
下面是相关的步骤:
步骤1:
得到’元数据项目’:
1. 在设计器画布上右击,点击属性:
2. 将’元数据项目处理’设置为’ 复制到输出目录’:
3. 生成项目,查看bin\debug(或bin\release)目录:
步骤2:
现在你有了文件形式的CSDL/MSL/SSDL,很容易替换其中一个或多个(一般是SSDL与MSL)为与你环境匹配的版本。所以你可以有两套,其一用于开发环境,另一套用于生成环境。
然后你需要做的仅是修改你的连接字符串来指向正确的文件集,类似下面这样:
1 var connStr = 2 @"metadata=.\Blogging.csdl|.\Production.ssdl|.\Production.msl; 3 provider=System.Data.SqlClient; 4 provider connection string="" 5 Data Source=.\SQLEXPRESS; 6 Initial Catalog=TipsDatabase; 7 Integrated Security=True; 8 MultipleActiveResultSets=True 9 """; 10 11 using (BloggingEntities ctx = new BloggingEntities(connStr)) 12 {
注意现在使用的连接字符串不是res://而是.\,这告知EF在与应用程序相同的目录下查找元数据。
事情就是这样。