注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

天涯倦客的博客

祝福你朋友永远快乐!

 
 
 

日志

 
 

Entity Framework 5.0系列之约定配置(1)  

2013-09-30 16:56:58|  分类: Entity Framework |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Code First之所以能够让开发人员以一种更加高效、灵活的方式进行数据操作有一个重要的原因在于它的约定配置。现在软件开发越来复杂,大家也都试图将软件设计的越来越灵活,很多内容我们都希望是可配置的,但是过多的配置也会带来很大的工作量,解决这个问题的方法就是约定。对于一些简单的,不太可能经常变化的内容我们以一种约定的方式进行设计。使用过其他ORM框架的朋友可能知道一般ORM都有对应的映射配置文件(一般是一个Xml文件),但是EF并没有。在EF中是以一种约定的方式进行表、列同实体类进行映射的,与此同时为了提高最大的灵活性EF中可以通过Fluent API和Data Annotations两种方式对映射进行灵活配置。

EF默认约定

我们先来看一下EF对于数据类(概念模型,或域模型)的默认约定:

  • 将数据类的类名复数形式作为数据表名称,并且使用“dbo”作为默认架构。

例如定义一个Person数据类,那么将会自动生成“dbo.People”表。

  • 将数据类中的“ID”属性或者“<类名>+ID”作为主键(不区分大小写),并且如果该列为数值类型或者GUID列将作为标识列。

例如在Order类中如果有ID或者OrderID属性将默认作为主键,二者均出现优先使用 “ID”属性。

  • 使用导航属性约束两个表之间的关系,在从表数据类中除了导航属性,推荐定义一个外键属性在从表数据类中(如果不指定将默认生成一个“<主表类名>+<主表类的主键名>”的外键列;此外在主表中推荐定义一个集合从表属性用户导航,当然这个属性不定义也可以正常生成外键关系但是不利于使用),具体规则:“<导航属性名>+<主表的主键属性名>”或者“<主表类名>+<主键属性名>”又或者“<主表的主键属性名>”,其属性名不区分大小写并且如果出现多种匹配按照先后顺序匹配;如果不存在外键属性则外键关系注册为可选的,否则注册为必选项并且此时将设置级联删除关系;如果在从表类中有多个导航属性对应同一个数据类那么需要使用fluent API或者Data Annotations进行手动配置。

例如有一个Order类,主键为OrderID,在OrderDetail类中有一个导航属性Order(Order类型),那么当你定义一个OrderID在OrderDetail中,那么在Order和OrderDetail直接将建立一个级联删除关系。

  • 当EF按照上述规则在数据类中没有找到主键属性时(或者通过fluent API、Data Annotations没有定义)将认为此类为“复杂类型”(对于不了解复杂类型的朋友请点击这里What is a Complex Type)。

例如在“Person”数据类中有一个“Name”属性,但是数据库中可能将“Name”分为FirstName和LastName存储,此时就可以定义一个Name类,在此类中不定义主键列定义“FirstName”和“LastName”属性,就会在表“dbo.People”中生成“Name_FirstName”和“Name_LastName”列。

定义约定

EF的默认约定不是一成不变的,我们可以选择移除和修改它,例如EF默认生成数据表时将数据类名的复数形式作为表名,下面的代码就可以移除这个规则:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Person> Person
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}

这些规则都在“System.Data.Entity.ModelConfiguration.Conventions”命名空间下,可以根据实际情况进行选择。

一般情况下我们是不需要移除默认约定的,我们更多的时候是要修改丰富这些约定,达到对生成规则的更多细节控制。在EF提供了两种方式进行映射配置:Data Annotations和Fluent API。

DataAnnotations

DataAnnotations是ASP.NET WEB中添加的一种验证方式,但是在EF中它又可以对映射关系进行控制,相比较Fluent API使用起来要简单一些。下面我们通过一个例子对DataAnnotations进行说明。在此我们假设有一个“Employee”类用于描述员工信息,一个“Customer”类用于描述客户信息,还有一个“Order”类用于描述订单信息,此外还有一个“Name”复杂类型表示人员姓名。在Order类中有一个属性“Customer”用于描述此订单的客户,它是“Customer”类型;还有一个“DeliverPerson”属性用于描述订单发货人,一个“CheckPerson”属性用户描述订单拣货人,它们都是“Employee”类型。下面是具体代码:

Employee类:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    [Table("People",Schema="Person")]
    public class Employee
    {
        [Key]
        public int No
        {
            get;
            set;
        }

        public Name Name
        {
            get;
            set;
        }

        [MinLength(5),MaxLength(30)]
        public string Title
        {
            get;
            set;
        }

        [Required]
        public DateTime BirthDate
        {
            get;
            set;
        }

        [ConcurrencyCheck]
        public string Address
        {
            get;
            set;
        }

        [Column("Notes",TypeName="ntext",Order=5)]
        public string Note
        {
            get;
            set;
        }

        [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
        public DateTime CreateDate
        {
            get;
            set;
        }

        [NotMapped]
        public string PhotoPath
        {
            get;
            set;
        }

        [Timestamp]
        public byte[] TimeStamp
        {
            get;
            set;
        }

        [InverseProperty("DeliverPerson")]
        public List<Order> DeliverOrder
        {
            get;
            set;
        }

        [InverseProperty("CheckPerson")]
        public List<Order> CheckOrder
        {
            get;
            set;
        }
    }
}

Customer类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Customer
    {
        public int CustomerID
        {
            get;
            set;
        }

        public string CompanyName
        {
            get;
            set;
        }
    }
}

Name类:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    [ComplexType]//根据前面我们说的默认约定,不标记为ComplexType只有没有找到ID也会将Name作为一个复杂类型
    public class Name
    {
        public string FirstName
        {
            get;
            set;
        }

        public string LastName
        {
            get;
            set;
        }
    }
}

Order类:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Order
    {
        public int OrderID 
        { 
            get; 
            set; 
        }

        public string OrderTitle 
        { 
            get; 
            set; 
        }

        public string CustomerName 
        { 
            get; 
            set; 
        }

        public DateTime TransactionDate 
        { 
            get; 
            set; 
        }

        public int CustomerNo
        {
            get;
            set;
        }

        [ForeignKey("CustomerNo")]
        public Customer Customer
        {
            get;
            set;
        }

        public Employee DeliverPerson
        {
            get;
            set;
        }

        public Employee CheckPerson
        {
            get;
            set;
        }
    }
}

这是通过Data Annotations配置后EF生成的数据库表结构:

Entity Framework 5.0系列之约定配置(1) - 海里的贝壳 - apple的博客

下面解释每个配置的作用

Table:用于指定生成表的表名、架构信息。

Column:用于指定生成数据表的列信息,如列名、数据类型、顺序等。

Key:用于指定任何名称的属性作为主键列并且默认将此列作为标识列(如果不想默认生成标识可以指定“DatabaseGenerated”属性的值为“None”),如果不指定此标记属性,将根据EF默认约定创建主键。如上代码指定“No”为“Employee”的主键。

Required:用户指定非空列,如上面的“BirthDay”创建列之后为“not null”列。

MinLengthMaxLength:指定字段长度(此属性通常可以用户客户端验证),例如上面“Title”定义成了“nvarchar(30)”。

ComplexType:用于标记复杂类型,对于包含复杂类型数据属性的类在生成数据表时复杂类型中每个属性都将作为其中一列。

DatabaseGenerated:用于指定数据库字段生成列,此类EF将不会直接更新。可以指定为计算列、标识列和非数据库生成列(例如给主键列指定此属性为“None”则不会生成标识列)。需要注意的是如果使用Code First字段生成数据库,那么此属性仅仅可以用于byte、timestamp列上,否则请应用在已经存在数据库的情况下,因为Code First无法判定生成具体计算列的公式(至少目前Code First还不支持公式配置)。

NotMapped:用户指定非映射列,标记此属性的列将不会在数据库中生成相应的列,例如上面的“PhotoPath ”没有在数据库中生成具体列,实际使用中它可能是通过其他具体规则得到的。

ConcurrencyCheck:用于进行并发检查,当一个用户A获得实体后通常会与数据库断开,此时如果另一个用户B也获得了实体并进行了修改,那么当A再进行更新时如果进行了“ConcurrencyCheck”标记则会进行并发检查,并根据原始值判断该实体是否存在,如果不存在则抛出异常。

TimeStamp:用于指定时间戳列,一个实体只能有一个TimeStamp列。在EF中TimeStamp是另一种并发控制方式,当EF遇到TimeStamp列会自动配置 “ConcurrencyCheck”及“DatabaseGenerated.Computed”来控制并发(通常我们推荐使用此方法)。

ForeignKey:用于指定外键列,我们知道按照上面提到的默认约定第三条,当我们在“Order”中定义了“Customer”属性后,如果定义“CustomerID” 属性(当然还有其他形式,大家可以按照声明说的默认约定3进行测试),那么EF会在“Order”表中创建一个“CustomerID”列并建立与“Customer”表的外键关系。但是如果像上面定义“CustomerNo”属性并且不指定“ForeignKey”标记的话将达不到我们的预期,EF将默认创建一个“Customer_CustomerID”列并创建与“Customer”的外键约束,同时创建一个“CustomerNo”列。当然解决的方式大家已经看到了那就是给导航属性“Customer”指定“ForegnKey”标记并且指定外键列为“CustomerNo”(注意虽然在“Customer”中不定义“Order的导航属性”生成的表中也并没用任何问题,但是我们推荐您定义相应的导航属性)。

InverseProperty:用于定义多重外键关系约束。我们在EF中通过导航属性定义主外键关系,但是当主表中有两个外键约束时可能仅仅通过添加相应的导航属性就无法完成了,例如上面“Order”中“DeliverPerson”和“CheckPerson”均为“Employee”类型,按我们的预期当然是在生成“Order”表时生成两个外键列并创建与“Employee”外键约束关系,但是如果没有在“Employee”类的“DeliverOrder”和“CheckOrder”属性上标记 “InverseProperty”属性EF是无法识别这种关系的(具体结果可以看下图),当然解决方式就是在对应导航属性中标记“InverseProperty”并指定对于的列名。

clip_image004

Entity Framework 5.0系列之约定配置(1) - 海里的贝壳 - apple的博客注意:DataAnnotations可以同时在同一个类后者属性上使用多个标记属性,上面的例子中对于每个类或属性只使用了一个单独的标记属性是为了说明起来更加简单;另外声明的例子中同时使用“ConcurrencyCheck”和“TimeStamp”指定了不同的列只是为了演示,一般情况下我们通过其中一种方式即可。

Fluent API

Fluent API一般配置

尽管我们可以通过Data Annotations进行映射关系约定,但是相比较而言Fluent API的功能更加强大,从功能上而言Data Annotations是Fluent API的一个子集, Data Annotations可以实现的功能Fluent API都能实现。下面我们先看一个例子,在这个例子中我们通过Fluent API约定方式实现上面Data Annotations的功能并且包含更多控制:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Order> Orders
        {
            get;
            set;
        }

        public DbSet<Employee> Employees
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Employee>().ToTable("People", "Person");//指定“Employee”对应表名及架构

            modelBuilder.Entity<Employee>().HasKey(emp => emp.No);//定义主键为“No”
            //modelBuilder.Entity<Employee>().HasKey(emp => new { emp.No, emp.Title });//指定"No"和“Title”作为复合主键,使用Data Annotations无法做到

            modelBuilder.Entity<Employee>().Property(emp => emp.No).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);//去掉标识列,前面我们也提到过通过Data Annotations通用可以去掉主键默认标示属性

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).HasMaxLength(30);//指定“Title”最大长度为30

            modelBuilder.Entity<Employee>().Property(emp => emp.BirthDate).IsRequired();//指定“BirthDate”为不可为空

            modelBuilder.Entity<Employee>().Property(emp => emp.Note).HasColumnName("Notes").HasColumnType("ntext");//指定“Note”对应列名为“Notes”,并指定数据类型

            modelBuilder.Entity<Employee>().Ignore(emp => emp.PhotoPath);//指定“PhotoPath”为非映射列

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).IsUnicode(true);//“Title”列是否支持Unicode编码

            modelBuilder.ComplexType<Name>().Property(n=>n.FirstName).HasMaxLength(50);//指定Name为复杂数据类型,并指定复杂类型中“FirstName”长度
            //modelBuilder.Entity<Employee>().Property(emp => emp.Name.LastName).HasMaxLength(20);//还可以通过这种方式指定复杂类型“Name”的“LastName”列的长度

            //modelBuilder.Entity<Employee>().Property(emp => emp.Address).IsConcurrencyToken();//指定“Address”进行并发控制,通常这一列我们知道为“TimeStamp”列而不是“Addree”这里只是为了说明可以标记其他列
            modelBuilder.Entity<Employee>().Property(emp => emp.TimeStamp).IsRowVersion();//通过指定“TimeStamp”进行并发版本控制
        }
    }
}

从上面的代码中可以看到基本上在Data Annotations中实现的功能使用Fluent API都实现了,并且在上面的代码注释中我也提到了一些Data Annotations无法实现的功能,具体代码基本上都已经注释了在此也不再解释了。

Fluent API关系配置

下面让看一下EF中关系配置的实现,看一下Fluent API如何进行实体关系约束,这里假设每个公司员工“Employee”在企业内部都有一个通讯账户“MessagingAcount”,这二者之间是一对一的关系;同时添加一个产品类“Product”,它与“Order”的关系是多对多的关系,具体定义如下:

MessageAcount类:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class MessagingAccount
    {
        [Key()]
        public int EmployeeNo
        {
            get;
            set;
        }

        public Employee Employee
        {
            get;
            set;
        }

        public string UserName
        {
            get;
            set;
        }

        public string Password
        {
            get;
            set;
        }
        
    }
}

Product类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Product
    {
        public int ProductID
        {
            get;
            set;
        }

        public string ProductName
        {
            get;
            set;
        }

        public double UnitPrice
        {
            get;
            set;
        }

        public int OrderID
        {
            get;
            set;
        }

        public List<Order> Orders
        {
            get;
            set;
        }

    }
}

Employee类(添加了对应的属性):

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Employee
    {
        public int No
        {
            get;
            set;
        }

        public Name Name
        {
            get;
            set;
        }

        public string Title
        {
            get;
            set;
        }

        public DateTime BirthDate
        {
            get;
            set;
        }

        public string Address
        {
            get;
            set;
        }

        public string Note
        {
            get;
            set;
        }

        public DateTime CreateDate
        {
            get;
            set;
        }

        public string PhotoPath
        {
            get;
            set;
        }

        public byte[] TimeStamp
        {
            get;
            set;
        }

        public List<Order> DeliverOrder
        {
            get;
            set;
        }

        public List<Order> CheckOrder
        {
            get;
            set;
        }

        public MessagingAccount Acount
        {
            get;
            set;
        }
    }
}
  评论这张
 
阅读(854)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017