ADO.NET Entity Framework 4.1 已在四月发布,包括一系列在现有 Entity Framework 4(在 Microsoft .NET Framework 4 和 Visual Studio 2010 中发布)功能之上构建的新功能。
msdn.microsoft.com/data/ee712906)、也有“EntityFramework”NuGet 程序包,而且在您安装 ASP.NET MVC 3.01 时也会安装它。
我们将快速了解代码优先的入门知识,然后深入了解一些更高级的功能。
常用功能在根级别即可获得,更高级的功能则随着您深入挖掘 API 而逐渐呈现。
其他的配置可以使用数据注释或通过 Fluent API 提供。
开始使用
图 1 是完整的代码列表,可帮助您建立并运行代码优先应用程序。
图 1 代码优先入门
- using System.Collections.Generic;
- using System.Data.Entity;
- using System.Linq;
- using System;
- namespace Blogging
- {
- class Program
- {
- static void Main(string[] args)
- {
- Database.SetInitializer<BlogContext>(new BlogInitializer());
- // TODO: Make this program do something!
- }
- }
- public class BlogContext : DbContext
- {
- public DbSet<Blog> Blogs { get; set; }
- public DbSet<Post> Posts { get; set; }
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- // TODO: Perform any fluent API configuration here!
- }
- }
- public class Blog
- {
- public int BlogId { get; set; }
- public string Name { get; set; }
- public string Abstract { get; set; }
- public virtual ICollection<Post> Posts { get; set; }
- }
- public class RssEnabledBlog : Blog
- {
- public string RssFeed { get; set; }
- }
- public class Post
- {
- public int PostId { get; set; }
- public string Title { get; set; }
- public string Content { get; set; }
- public byte[] Photo { get; set; }
- public virtual Blog Blog { get; set; }
- }
- public class BlogInitializer : DropCreateDatabaseIfModelChanges<BlogContext>
- {
- protected override void Seed(BlogContext context)
- {
- context.Blogs.Add(new RssEnabledBlog
- {
- Name = "blogs.msdn.com/data",
- RssFeed = "http://blogs.msdn.com/b/data/rss.aspx",
- Posts = new List<Post>
- {
- new Post { Title = "Introducing EF4.1" },
- new Post { Title = "Code First with EF4.1" },
- }
- });
- context.Blogs.Add(new Blog { Name = "romiller.com" });
- context.SaveChanges();
- }
- }
- }
您将看到,我使用数据库初始化表达式来断开和重建数据库,因为我们将在这篇文章中更改模型。
使用 Fluent API 进行映射
Fluent API 通过 DbModelBuilder 类型提供,最简便的访问方法是覆盖 DbContext 上的 OnModelCreating 方法。
每个表始终都需要主键,其实我可以包含它,但代码优先将自动为我添加。
图 2 实体拆分
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Post>()
- .Map(m =>
- {
- m.Properties(p => new { p.Title, p.Content });
- m.ToTable("Posts");
- })
- .Map(m =>
- {
- m.Properties(p => new { p.Photo });
- m.ToTable("PostPhotos");
- });
- }
鉴别器列巧妙地命名为“Discriminator”,每个类型的 CLR 类型名称将用作鉴别器值。
我在这里使用“HasRssFeed”列来存储 true/false 值,以区分“Blog”和“RssEnabledBlog”实例:
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Blog>()
- .Map(m => m.Requires("HasRssFeed").HasValue(false))
- .Map<RssEnabledBlog>(m => m.Requires("HasRssFeed").HasValue(true));
- }
如果该列具有非空值,则它必然是 RssEnabledBlog:
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Blog>()
- .Map<RssEnabledBlog>(m => m.Requires(b => b.RssFeed).HasValue());
- }
在下面的示例中,我将所有博客通用的数据存储到“Blogs”表,将启用了 RSS 的博客的数据存储到“RssBlogs”表:
- modelBuilder.Entity<Blog>()
- .Map(m => m.ToTable("Blogs"))
- .Map<RssEnabledBlog>(m => m.ToTable("RssBlogs"));
MapInheritedProperties 让代码优先把从基本类继承的所有属性重新映射到派生类的表中的新列:
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Entity<Blog>()
- .Map(m => m.ToTable("Blogs"))
- .Map<RssEnabledBlog>(m =>
- {
- m.MapInheritedProperties();
- m.ToTable("RssBlogs");
- });
- }
如果您打算映射到已经设置好、用于在多个表中生成唯一值的现有数据库,可以通过 Fluent API 的属性配置部分重新启用标识。
博客的数据拆分到“Blogs”和“BlogAbstracts”表中,启用了 RSS 的博客的数据存储到单独的“RssBlogs”表中。
图 3 综合使用实体拆分和 TPT 继承映射
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Map(m =>
{
m.Properties(b => new { b.Name });
m.ToTable("Blogs");
})
.Map(m =>
{
m.Properties(b => new { b.Abstract });
m.ToTable("BlogAbstracts");
})
.Map<RssEnabledBlog>(m =>
{
m.ToTable("RssBlogs");
});
}
更改跟踪器 API
我将直接深入探讨一些较为复杂的情况,如果您还不熟悉基本的数据访问方式,请花些时间通读前文提到的“代码优先演练”。
图 4 中的代码片段从数据库加载一个“Blog”,修改属性,然后向控制台输出每项属性的当前值和原始值。
图 4 获取实体的状态信息
- static void Main(string[] args)
- {
- Database.SetInitializer<BlogContext>(new BlogInitializer());
- using (var db = new BlogContext())
- {
- // Change the name of one blog
- var blog = db.Blogs.First();
- blog.Name = "ADO.NET Team Blog";
- // Print out original and current value for each property
- var propertyNames = db.Entry(blog).CurrentValues.PropertyNames;
- foreach (var property in propertyNames)
- {
- System.Console.WriteLine(
- "{0}\n Original Value: {1}\n Current Value: {2}",
- property,
- db.Entry(blog).OriginalValues[property],
- db.Entry(blog).CurrentValues[property]);
- }
- }
- Console.ReadKey();
- }
运行图 4 中的代码后,控制台输出如下所示:
Current Value:http://blogs.msdn.com/b/data/rss.aspx
图 5 中的代码演示了将所有博客加载到内存、修改其中一个博客的属性,然后输出每个被跟踪博客的状态。
图 5 使用 DbContext 访问多个实体的信息
- static void Main(string[] args)
- {
- Database.SetInitializer<BlogContext>(new BlogInitializer());
- using (var db = new BlogContext())
- {
- // Load all blogs into memory
- db.Blogs.Load();
- // Change the name of one blog
- var blog = db.Blogs.First();
- blog.Name = "ADO.NET Team Blog";
- // Print out state for each blog that is in memory
- foreach (var entry in db.ChangeTracker.Entries<Blog>())
- {
- Console.WriteLine("BlogId: {0}\n State: {1}\n",
- entry.Entity.BlogId,
- entry.State);
- }
- }
运行图 5 中的代码后,控制台输出如下所示:
State:Unchanged
图 6 中的代码将所有博客加载到内存中,然后对博客运行两个没有命中数据库的 LINQ 查询。
图 6 对内存中的数据运行 LINQ 查询
- static void Main(string[] args)
- {
- Database.SetInitializer<BlogContext>(new BlogInitializer());
- using (var db = new BlogContext())
- {
- // Load all blogs into memory
- db.Blogs.Load();
- // Query for blogs ordered by name
- var orderedBlogs = from b in db.Blogs.Local
- orderby b.Name
- select b;
- Console.WriteLine("All Blogs:");
- foreach (var blog in orderedBlogs)
- {
- Console.WriteLine(" - {0}", blog.Name);
- }
- // Query for all RSS enabled blogs
- var rssBlogs = from b in db.Blogs.Local
- where b is RssEnabledBlog
- select b;
- Console.WriteLine("\n Rss Blog Count: {0}", rssBlogs.Count());
- }
- Console.ReadKey();
- }
运行 图 6 中的代码后,控制台输出如下所示:
Rss Blog Count:1
这样一来,您可以规定或过滤要加入内存的项目,避免返回不必要的数据。
我可以编写如图 7 中所示的代码,但这段代码要用到延迟加载才能将所有相关帖子返回内存中,以便我掌握数量。
图 7 使用延迟加载获得数据库项目的数量
- static void Main(string[] args)
- {
- Database.SetInitializer<BlogContext>(new BlogInitializer());
- using (var db = new BlogContext())
- {
- // Load a single blog
- var blog = db.Blogs.First();
- // Print out the number of posts
- Console.WriteLine("Blog {0} has {1} posts.",
- blog.BlogId,
- blog.Posts.Count());
- }
- Console.ReadKey();
- }
我其实只需要一个整数值,而为此却要从数据库传输大量数据,占用大量内存。
因为 LINQ 可以改写,所以我可以连续使用“Count”运算符,整个查询被推入数据库,以便只返回一个整数结果(请参见图 8)。
图 8 使用 DbContext 优化查询代码并保存资源
- static void Main(string[] args)
- {
- Database.SetInitializer<BlogContext>(new BlogInitializer());
- using (var db = new BlogContext())
- {
- // Load a single blog
- var blog = db.Blogs.First();
- // Query for count
- var postCount = db.Entry(blog)
- .Collection(b => b.Posts)
- .Query()
- .Count();
- // Print out the number of posts
- Console.WriteLine("Blog {0} has {1} posts.",
- blog.BlogId,
- postCount);
- }
- Console.ReadKey();
- }
部署注意事项
现在让我们深入一步,了解一些在完善您的应用程序并作为产品发布时应考虑的事项。
以下示例显示的连接字符串可用来影响示例应用程序针对的数据库:
<connectionStrings>
<add
name="Blogging"
providerName="System.Data.SqlClient"
connectionString="Server=MyServer;Database=Blogging;
Integrated Security=True;MultipleActiveResultSets=True;" />
</connectionStrings>
下面是更新后的上下文代码:
- public class BlogContext : DbContext
- {
- public BlogContext()
- : base("name=Blogging")
- {}
- public DbSet<Blog> Blogs { get; set; }
- public DbSet<Post> Posts { get; set; }
- }
例如,如果要在查询某个博客的帖子的同时枚举所有博客,这就是必需的。
这绝对不是您部署到生产环境时希望发生的。
值可以是“Disabled”,也可以是初始化表达式类型名称,后跟定义它的程序集。
以下示例禁用了我在本文中使用的上下文的所有初始化表达式逻辑:
<appSettings>
<add
key="DatabaseInitializerForType Blogging.BlogContext, Blogging"
value="Disabled" />
</appSettings>
以下示例将初始化表达式改回默认功能,即仅在数据库不存在时创建它:
<appSettings>
<add
key="DatabaseInitializerForType Blogging.BlogContext, Blogging"
value="System.Data.Entity.CreateDatabaseIfNotExists EntityFramework" />
</appSettings>
因此我强烈建议应用程序以较低的权限运行,足够执行查询和保存数据集即可。
了解更多
最后,我介绍了一些在部署使用代码优先访问数据的应用程序时应该注意的事项。
bit.ly/166o1Z。