• 热门专题

Entity Framework映射的总结

作者:子龙blogs  发布日期:2015-09-18 21:16:17
Tag标签:Entity  Framework映射的总结  
  • EF是一个ORM工具,映射永远是最核心的部分。所以接下来详细介绍Code First模式下EF的映射配置。

    通过Code First来实现映射模型有两种方式Data Annotation和Fluent API。

    Data Annotation需要在实体类的属性上以Attribute的方式表示主键、外键等映射信息。这种方式不符合解耦合的要求所以一般不建议使用。

    第二种方式就是要重点介绍的Fluent API。Fluent API的配置方式将实体类与映射配置进行解耦合,有利于项目的扩展和维护。

    Fluent API方式中的核心对象是DbModelBuilder。

    在重写的DbContext的OnModelCreating方法中,我们可以这样配置一个实体的映射:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>().HasKey(t => t.Id); 
    base.OnModelCreating(modelBuilder);
    }

    1.配置类对应于数据库中的表名

    modelBuilder.Entity<Product>().ToTable("Product","dbo");
    

     配置类对应于数据库中的表名,并指定表的所有者:

    如果不指定表的所有者可以这样写
    modelBuilder.Entity<Product>().ToTable("Product");

    2.重新指定类属性与列名之间的映射关系

    在默认约定的情况下,Entity Framework Code First创建的列名与类的属性名相同,可以根据需要进行重新指定类属性与列名之间的映射关系。
    modelBuilder.Entity<Product>().Property(t => t.ProductID).HasColumnName("ProductId");

    将ProductID改为ProductId.
     modelBuilder.Entity<Product>().Property(t => t.ProductName).IsRequired()
            .HasColumnName("ProductName")
         .HasMaxLength(100);


    ProductName是必须的,映射到数据库的名字为ProductName,长度为100.

    3.为属性指定对应的SQL SERVER数据类型


    在默认情况下,int类型的属性生成的列名对应SQL SERVER列int类型;而String类型的属性则对应SQL SERVER列的NVARCHAR类型。若类的字符串类型属性未设置MaxLength,则生成对应的列类型为NVARCHAR(MAX)。
    为属性指定对应的SQL SERVER数据类型:
    modelBuilder.Entity<Product>().Property(t => t.UnitPrice)
        .HasColumnName("UnitPrice")
        .HasColumnType("MONEY");

    4.对主键的进行重写

    Entity Framework Code First的默认主键约束:属性名为[ID]或[类名 + ID]。如在Product类中,Entity Framework Code First会根据默认约定将类中名称为ID或ProductID的属性设置为主键。Entity Framework Code First主键的默认约定也一样可以进行重写,重新根据需要进行设置。
    modelBuilder.Entity<Product>().HasKey(t => t.ProductID);

    若一个表有多个主键时:
    modelBuilder.Entity<Product>().HasKey(t => new { t.KeyID, t.CandidateID });

    Entity Framework Code First对于int类型的主键,会自动的设置其为自动增长列。但有时我们确实不需是自动增长的,可以通过以下方式进行取消自动增长。
    modelBuilder.Entity<Product>().HasKey(t => t.ProductID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);//取消自动增长
    modelBuilder.Entity<Category>().HasKey(t => t.ProductID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);//将ProductId设置为自动增长
    

     5.设置类型的的精度 

    在Product类中,UnitPrice表示单价,对于价格类的字段,我们通常会希望其保留2为小数。这时可以使用Fluent API进行设置
    modelBuilder.Entity<Product>().Property(t => t.UnitPrice)
            .HasColumnName("UnitPrice")
            .HasPrecision(18, 2);
    

      

    6、非数据库字段属性

      在类中,如果有一些属性不需要映射到对应生成的数据表中,可以通过以下方式设置。

    modelBuilder.Entity<Product>().Ignore(t => t.Remark);
    

      

    7. Fluent API配置Configuration映射类

      在使用Fluent API进行Entity Framework Code First数据库映射时,除了以上的在重写OnModelCreating方法中直接对Entity进行配置之外,也可以对Configurations进行配置。这时可以先写一个单独的类,将数据表的全部映射要求都写在构造函数中。

    类要继承

    EntityTypeConfiguration<T>,然后再构造函数中添加映射,最后
     modelBuilder.Configurations.Add(new T());
    
    

     8.拓展

    关联 1-1关联

    Fluent API设置实体类生成的表引用与被引用通过WithRequiredPrincipal、WithRequiredDependent及WithOptionalPrincipal、WithOptionalDependent来设置,使用Principal属性的实体类将被另外的实体类生成的表引用,使用Dependent属性的实体类将引用另外的实体类。
    这里说明一下
    WithRequiredDependent 和 WithOptional(i => i.Product)是等价的;
    //第一组(两条效果完全相同)        
     HasRequired(p => p.WarrantyCard).WithRequiredDependent(i => i.Product);
     HasRequired(p => p.WarrantyCard).WithOptional(i => i.Product);
    

      

    外键指定在Product表的Id列上,Product的主键Id不作为标识列。 WithRequiredPrincipal 和 WithRequired是等价的 //第二组(两条效果完全相同)       
            HasRequired(p => p.WarrantyCard).WithRequiredPrincipal(i => i.Product);
            HasOptional(p => p.WarrantyCard).WithRequired(i => i.Product);
    

      

    外键添加到WarrantyCard表的主键ProductId上,而且这个键也不做标识列使用了。 对于当前场景这两组配置应该选择那一组呢。对于产品和保修卡,肯定是先有产品后有保修卡,保修卡应该依赖于产品而存在。所以第二组配置把外键设置到WarrantyCard的主键更为合适,让WarrantyCard依赖Product符合当前场景。即Product作为Principal而WarrantyCard作为Dependent,其实这么多代码也无非就是明确两个关联对象Principal和Dependent的地位而已。 单向1 - *关联(可为空)

    这里新登场角色是和发票发票有自己的编号,有些产品有发票,有些产品没有发票。我们希望通过产品找到发票而又不需要由发票关联到产品。

    public class Invoice
    {
        public int Id { get; set; }
        public string InvoiceNo { get; set; }  
        public DateTime CreateDate { get; set; }
    }

    产品类新增的属性如下:

    public virtual Invoice Invoice { get; set; }
    public int? InvoiceId { get; set; }
     

    可以使用如下代码创建Product到Invoice的关联

      

    public class ProductMap : EntityTypeConfiguration<Product>
    {
        public ProductMap()
        {
            ToTable("Product");
            HasKey(p => p.Id);
            HasOptional(p => p.Invoice).WithMany().HasForeignKey(p => p.InvoiceId);
        }
    }
    

      

    HasOptional表示一个产品可能会有发票,WithMany的参数为空表示我们不需要由发票关联到产品,HasForeignKey用来指定Product表中的外键列。

    还可以通过WillCascadeOnDelete()配置是否级联删除,这个大家都知道,就不多说了。

    运行迁移后,数据库生成的Product表外键可为空(注意实体类中表示外键的属性一定要为Nullable类型,不然迁移代码不能生成)。

    单向1 - *关联(不可为空)

    为了演示这个关联,请出一个新对象合格证合格证有自己的编号,而且一个产品是必须有合格证。

    public class Certification
    
    {
        public int Id { get; set; }
        public string Inspector { get; set; }
    }
    

      

    我们给Product添加关联合格证的属性:
    public virtual Certification Certification { get; set; }
    public int CertificationId { get; set; }
    

      

    配置Product到Certification映射的代码与之前的类似,就是把HasOptional换成了HasRequired: HasRequired(p => p.Certification).WithMany().HasForeignKey(p=>p.CertificationId); 生成的迁移代码,外键列不能为空。创建对象时Product必须和Certification一起创建。生成的查询语句除了把LEFT OUTER JOIN换成INNER JOIN外其他都一样,不再赘述。 双向1 - *关联 这是比较常见的场景,如一个产品可以对应多张照片,每张照片关联一个产品。先来看看新增的照片类
    public class ProductPhoto
    {
        public int Id { get; set; }
        public string FileName { get; set; }
        public float FileSize { get; set; }
        public virtual Product Product { get; set; }
        public int ProductId { get; set; }
    }

    给Product增加ProductPhoto集合:
    public virtual ICollection<ProductPhoto> Photos { get; set; }
    

      

    然后是映射配置:
    public class ProductMap : EntityTypeConfiguration<Product>
    {
        public ProductMap()
        {
            ToTable("Product");
            HasKey(p => p.Id);
            HasMany(p => p.Photos).WithRequired(pp => pp.Product).HasForeignKey(pp => pp.ProductId);
        }
    }

    代码很容易理解,HasMany表示Product中有多个ProductPhoto,WithRequired表示ProductPhoto一定会关联到一个Product。 我们来看另一种等价的写法(在ProductPhoto中配置关联):
    public class ProductPhotoMap : EntityTypeConfiguration<ProductPhoto>
    {
        public ProductPhotoMap()
        {
            ToTable("ProductPhoto");
            HasKey(pp => pp.Id);
            HasRequired(pp => pp.Product).WithMany(p => p.Photos).HasForeignKey(pp => pp.ProductId);
        }
    }
    有没有感觉和之前单向1 - *的配置很像?其实就是WithMany多了参数而已。随着例子越来越多,大家应该对这几个配置理解的越来越深了。 * - *关联 一个产品可以有多个标签,一个标签也可对应多个产品:
    public class Tag
    {
        public int Id { get; set; }
        public string Text { get; set; }
        public virtual ICollection<Product> Products { get; set; }
    }
    

      

    给Product增加标签集合:
    public virtual ICollection<Tag> Tags { get; set; }
    public class ProductMap : EntityTypeConfiguration<Product>
    {
        public ProductMap()
        {
            ToTable("Product");
            HasKey(p => p.Id);
            HasMany(p => p.Tags).WithMany(t => t.Products).Map(m => m.ToTable("Product_Tag_Mapping"));
        }
    }
       
    				
About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规