[VS2010] ADO.NET Entity Framework 新功能:永续保存无知对象 (Persistence-Ignorant Object) Overview

ADO.NET Entity Framework 的新功能:永续保存无知对象。可以说是 Entity Framework 划时代的新功能,颠覆一般的数据组件/DAL 与数据库间的交互方式。


前一篇文章介绍了 ADO.NET Entity Framework 的模型优先设计 (Model First Design) 功能,有没有觉得 .NET Framework 4.0 中的 ADO.NET Entity Framework 进步了很多呢?如果你这样就满足了,那接下来的东西你看了可能会像某啤酒广告的女主角们那样的尖叫~

[VS2010] ADO.NET Entity Framework 新功能:永续保存无知对象 (Persistence-Ignorant Object) Overview


一般在设计数据类的时候,大多都是以一个纯类 (轻量级类,只声明必要的属性代表字段,并没有和其他对象交互),包含几个只针对数据做处理的属性或方法等等,若要使用它和数据库连结时,只能在里面做一些 Property Mapping,并且另外撰写数据存取的 ADO.NET 程序,这个动作只要是当过应用程序开发人员,就会觉得如呼吸般自然 … 长久以来都是如此,没有一次例外。但 ADO.NET Entity Framework 2.0 试图将这个局面颠覆过来,开发人员可以直接使用纯类对象来生成数据结构,并且由 Entity Framework 代为建立数据库以及表格,这个方式可是以前 Visual Studio 开发人员前所未见的。这个方法在 Java 阵营中已经有一些实践品,而在 Java 阵营中对轻量级的数据对象一个特别的名词:POJO (Plain-old Java Object),在 .NET 阵营也有一个相对的名词,叫 POCO (Plain-old CLR Object),用途和 POJO 差不多,但微软给了一个笔者觉得很难翻的名词: Persistence-Ignorant Object,若直接翻译会变成 “保存无知型对象”,蛮拗口的,在官方翻译未出来前,就暂时先翻成 “无差别保存对象” (编按:在 "软件构筑美学" 一书中,将此词翻译为 "永续保存无知",笔者认为这个词翻的还不错,所以引用好了,因为 Persistence-Ignorant Object 具有不必事先在 DBMS 中建立实例数据库,就可以利用 Entity Framework 的 DDL Generation 功能将 POCO 对象结构转换成实例数据结构以存入数据库中的能力。

笔者认为永续保存无知对象在 ADO.NET Entity Framework 乃至于 ADO.NET 甚至是整个 .NET Framework 的 Data Access 机制而言,都是一项划时代的创新,它将数据存取组件设计的刻板印象完全的改变了,没想到只使用几个属性的声明就可以生成一个完成的数据结构,这可是许多开发人员梦寐以求的功能啊,笔者一开始看到这个功能时也是眼睛一亮,试过以后更觉得它是超级好物啊~

也许这样看起来还是有点抽象,实际走一次例子就知道了。不过在做例子前,请先看看环境是否符合:

A. 已安装 Visual Studio 2010

B. 下载并安装 ADO.NET Entity Framework Feature CTP2 : http://www.microsoft.com/downloads/details.aspx?FamilyID=13FDFCE4-7F92-438F-8058-B5B4041D0F01&displayLang=en

C. 已安装 SQL Server (最好是非 Express 版本,但 Express 版也无妨)

前置作业符合后,请依下列步骤执行:

1. 在 Visual Studio 中先建立一个 AdoEF_PocoExampleLibrary 类库项目,并且加入下列类声明程序 (可同在一个类库文件或是分成个别的文件):

public class Blog {
   public Blog() { }
   public int ID { get; set; }
   public string Name { get; set; }
   public string Url { get; set; }
   public User Owner { get; set; }
   public ICollection Posts { get; set; }
}

public class Comment
{
   public Comment() { }
   public int ID { get; set; }
   public string Title { get; set; }
   public string Body { get; set; }
   public Person Author { get; set; }
   public Post Post { get; set; }
   public DateTime Created { get; set; }
   public DateTime? Posted { get; set; }    
}

public class Person
{
   public int ID { get; set; }
   public string Firstname { get; set; }
   public string Surname { get; set; }
   public string EmailAddress { get; set; }
   public ICollection Comments { get; set; }
}

public class Post
{
   public Post() { }
   public int ID { get; set; }
   public string Title { get; set; }
   public string Body { get; set; }
   public string PermaUrl { get; set; }
   public DateTime Created { get; set; }
   public DateTime? Posted { get; set; }
   public User Author { get; set; }
   public User Poster { get; set; }
   public Blog Blog { get; set; }
   public ICollection Comments { get; set; }
   public int BlogID { get; set; }
}

public class User : Person
{
   public string Password { get; set; }
   public ICollection Blogs { get; set; }
   public ICollection AuthoredPosts { get; set; }
   public ICollection PostedPosts { get; set; }
}

2. 在 Visual Studio 中建立一个 AdoEF_PocoExampleClient 主控台项目,先新增一个 BloggingModel 类,并将下列程序加入:

using System.Data.Entity;
using System.Data.EntityClient;
using System.Data.EntityModel;
using System.Data.Objects;

public class BloggingModel : ObjectContext
{
   public BloggingModel(EntityConnection connection)
       : base(connection)
   {
       DefaultContainerName = "BloggingModel";
   }

    public IObjectSet Blogs
   {
       get { return base.CreateObjectSet(); }
   }

    public IObjectSet People
   {
       get { return base.CreateObjectSet(); }
   }

    public IObjectSet Comments
   {
       get { return base.CreateObjectSet(); }
   }

    public IObjectSet Posts
   {
       get { return base.CreateObjectSet(); }
   }
}

3. 在 AdoEF_PocoExampleClient 项目中加入一个 BlogDemo 类,并将下列程序加入(其中红字为数据库连线字符串,但 initial catalog 可以指定成目前还没建的数据库名称,若你已经有 Blogs 数据库,请将 initial catalog 的名称改一下,否则执行以后,你原有的 Blogs 数据库会被删掉):

using System.Data.Entity;
using System.Data.EntityClient;
using System.Data.EntityModel;
using System.Data.Objects;
using Microsoft.Data.Objects;

class BlogDemo
{
   public static void Run()
   {
       var builder = new ContextBuilder();
       RegisterConfigurations(builder);
       var connection = new SqlConnection("initial catalog=Blogs; integrated security=SSPI");

        using (var ctx = builder.Create(connection))
       {
           if (ctx.DatabaseExists())
               ctx.DeleteDatabase();
           ctx.CreateDatabase();
           var EfDesign =
               new AdoEF_PocoExampleLibrary.Blog
               {
                   Name = "EF Design",
                   Url = "http://blogs.msdn.com/efdesign/",
                   Owner = new AdoEF_PocoExampleLibrary.User
                   {
                       ID = 1,
                       Firstname = "Johnny",
                       Surname = "Miller",
                       EmailAddress = "johnnyM@hotmail.com",
                       Password = "Viking"
                   }
               };

            ctx.Blogs.AddObject(EfDesign);
           var post = new AdoEF_PocoExampleLibrary.Post
           {
               Title = "Hello",
               Blog = EfDesign,
               PermaUrl = EfDesign.Url + "/2009/08/Hello",
               Body = "....",
               Author = EfDesign.Owner,
               Poster = EfDesign.Owner,
               Created = DateTime.Today,
               Posted = DateTime.Today,
           };

            ctx.Posts.AddObject(post);
           var comment = new AdoEF_PocoExampleLibrary.Comment
           {
               Title = "RE:" + post.Title,
               Body = "Welcome to the world of blogging Johnny...",
               Created = DateTime.Now,
               Posted = DateTime.Now,
               Post = post,
               Author = new AdoEF_PocoExampleLibrary.Person
               {
                   ID = 2,
                   Firstname = "Vincent",
                   Surname = "Chase",
                   EmailAddress = "vinny@hotmail.com",
               }
           };

            ctx.Comments.AddObject(comment);
           ctx.SaveChanges();

            AdoEF_PocoExampleLibrary.Blog blog = ctx.Blogs.Single();

            foreach (var entry in blog.Posts)
           {
               Console.WriteLine(entry.Title);
               Console.WriteLine(entry.Author.Firstname);
           }
       }
   }

    static void RegisterConfigurations(ContextBuilder builder)
   {
       builder.Configurations.Add(new CommentConfiguration());
       builder.Configurations.Add(new BlogConfiguration());
       builder.Configurations.Add(new PostConfiguration());
       builder.Configurations.Add(new PersonConfiguration());
       builder.Configurations.Add(new UserConfiguration());
   }
}

4. 在前一步的程序的下方,加入下列的程序:

public class CommentConfiguration : EntityConfiguration
{
   public CommentConfiguration()
   {
       Property(c => c.ID).IsIdentity();
       Property(c => c.Title).HasMaxLength(103).IsRequired();
       Property(c => c.Body).IsRequired();
       // 1 to * relationships           
       Relationship(c => c.Author).IsRequired();
       Relationship(c => c.Post).IsRequired();
       //Register some inverses           
       Relationship(c => c.Post).FromProperty(p => p.Comments);
       Relationship(c => c.Author).FromProperty(u => u.Comments);
   }
}

public class BlogConfiguration : EntityConfiguration
{
   public BlogConfiguration()
   {
       Property(b => b.ID).IsIdentity();
       Property(b => b.Name).HasMaxLength(100).IsRequired();
       Relationship(b => b.Owner).IsRequired();
       //Register some inverses           
       Relationship(b => b.Owner).FromProperty(u => u.Blogs);
       Relationship(b => b.Posts).FromProperty(p => p.Blog);
   }
}

public class PostConfiguration : EntityConfiguration
{
   public PostConfiguration()
   {
       // Make the PK store generated           
       Property(p => p.ID).IsIdentity();
       // Convert some '0..1 to *' relationships into '1 to *'           
       Relationship(p => p.Author).IsRequired();
       Relationship(p => p.Blog).IsRequired();
       Relationship(p => p.Poster).IsRequired();
       // Setup some facets           
       Property(p => p.Body).IsRequired();
       Property(p => p.PermaUrl).HasMaxLength(200);
       Property(p => p.Title).HasMaxLength(100);
       // Register some Inverses           
       Relationship(p => p.Author).FromProperty(u => u.AuthoredPosts);
       Relationship(p => p.Comments).FromProperty(c => c.Post);
       Relationship(p => p.Poster).FromProperty(p => p.PostedPosts);
       //BlogID is a FK property and Blog is a navigation property backed by this FK    
       Relationship(p => p.Blog).FromProperty(b => b.Posts).HasConstraint((p, b) => p.BlogID == b.ID);
   }
}

public class PersonConfiguration : EntityConfiguration
{
   public PersonConfiguration()
   {
       Property(p => p.ID).IsIdentity();
       Property(p => p.Firstname).HasMaxLength(100);
       Property(p => p.Surname).HasMaxLength(100);
       Property(p => p.EmailAddress).HasMaxLength(200);
       MapHierarchy(
           p => new
           {
               pid = p.ID,
               email = p.EmailAddress,
               fn = p.Firstname,
               ln = p.Surname,
           }
       ).ToTable("People");
   }
}

public class UserConfiguration : EntityConfiguration
{
   public UserConfiguration()
   {
       Property(u => u.Password).HasMaxLength(15).IsRequired();
       Relationship(u => u.AuthoredPosts).FromProperty(p => p.Author);
       Relationship(u => u.PostedPosts).FromProperty(p => p.Poster);
       MapHierarchy(
           u => EntityMap.Row(
               EntityMap.Column(u.ID, " u i d"),
               EntityMap.Column(u.Password)
               )
       ).ToTable("Users");
   }
}

5. 在 AdoEF_PocoExampleClient 的 Program.cs 中,加入下列的程序:

class Program
{
   static void Main(string[] args)
   {
       BlogDemo.Run();
   }
}

6. 若你所使用的数据库是 SQL Server (非 Express 或 Compact Edition),可将 SQL Profiler 先启动并进入监听模式。

7. 按 F5 以调试器执行或按 CTRL+F5 直接执行,正常情况下窗口会出现以后消失。此时请打开 SQL Server Management Studio 并连到本机服务器中,即可以看到新的 Blogs 数据库。

[VS2010] ADO.NET Entity Framework 新功能:永续保存无知对象 (Persistence-Ignorant Object) Overview

8. 若在前面有启动 SQL Profiler 监听,则可以将它停止后,看看 Entity Framework 做了什么事:

[VS2010] ADO.NET Entity Framework 新功能:永续保存无知对象 (Persistence-Ignorant Object) Overview

如何,是不是有想要尖叫的感觉呢?有了这样的机制,Entity Framework 更向成熟的 ORM Framework 迈进一大步了。

由于这个功能太大,一回文章介绍不完,因此笔者下回再继续深入介绍这个神奇的功能。

参考数据:

Updated Feature CTP Walkthrough: Code Only for Entity Framework

http://blogs.msdn.com/adonet/archive/2009/11/12/updated-feature-ctp-walkthrough-code-only-for-entity-framework.aspx

范例程序下载:

http://blogs.msdn.com/adonet/attachment/9921786.ashx

原文:大专栏  [VS2010] ADO.NET Entity Framework 新功能:永续保存无知对象 (Persistence-Ignorant Object) Overview


上一篇:翻译:《实用的Python编程》02_00_Overview


下一篇:翻译:《实用的Python编程》04_00_Overview