Entity Framework Code First主外键关系映射约定

  本篇随笔目录:

  1、外键列名默认约定

  2、一对多关系

  3、一对一关系

  4、多对多关系

  5、一对多自反关系

  6、多对多自反关系

  在关系数据库中,不同表之间往往不是全部都单独存在,而是相互存在关联的。两个不同表之间可以存在外键依赖关系,一个表自身也可以有自反关系(表中的一个字段引用主键,从而也是外键字段)。

  Entity Framework Code First默认多重关系的一些约定规则:

  一对多关系:两个类中分别包含一个引用和一个集合属性,也可以是一个类包含另一个类的引用属性,或一个类包含另一个类的集合属性。如在本篇接下来用到的例子Category类和Product类,要使得Category与Product之间具有一对多关系,Entity Framework Code First可以有3种体现方式:

  1>、在Category类中定义ICollection<Product> Products集合属性,同时在Product类中定义Category Category引用属性。

  2>、仅在Category类中定义ICollection<Product> Products集合属性。

  3>、仅在Product类中定义Category Category引用属性。

  多对多关系:两个类分别包含对方的一个集合属性。如在本篇接下来用到的例子User类和Role类,要使得User与Role之间具有多对多关系,即一个用户可以属于多个角色,一个角色可以有多个用户,则需要在User类中需要定义一个ICollection<Role> Roles集合属性,同时在Role类中需要定义一个ICollection<User> Users属性。

  一对一关系:两个类分别包含对方的一个引用属性。如在本篇接下来用到的例子User类和UserProfile类,要使得User与UserProfile之间具有一对一关系,则需要在User类中定义一个UserProfile UserProfile的引用属性,同时在UserProfile类中定义一个User User的引用属性。

  下面具体描述Entity Framework Code First生成外键的默认约定,并通过实例展示Entity Framework Code First处理一个表及多个表之间的关系。

  1、外键列名默认约定

  Entity Framework Code First在根据默认约定创建外键时,外键列的名称存在3种方式。在《Programming Entity Framework Code First》一书中,给出的3种外键列名的约定方式是:[Target Type Key Name], [Target Type Name] + [Target Type Key Name], or [Navigation Property Name] + [Target Type Key Name],对应的中文翻译为:[目标类型的键名],[目标类型名称]+[目标类型键名称],或[引用属性名称]+[目标类型键名称]。

  Entity Framework Code First外键默认约束生成的外键在分别满足3种不同的条件下,外键列名有3种不同的命名规则。且经过测试这3种不同的外键名称命名之间存在优先级:[目标类型的键名] > [引用属性名称]+[目标类型键名称] > [目标类型名称]+[目标类型键名称]。接下来以Product类及Category类为例,分别测试外键列名称的3中不同生成方式,Category与Product为一对多关系。

  1>、[目标类型的键名]

  这种方式为要求在Product表中外键列名与Category表中的主键列名相同,所以也就要求在Product类中有定义与Category类中作为主键的属性。如在Category类中主键属性为CategoryID,则需要在Product类中也定义一个CategoryID的属性。

  文件Category.cs:

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

namespace Portal.Entities
{
    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }
}

  文件Product.cs:

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

namespace Portal.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }
        public int CategoryID { get; set; }

        public virtual Category Category { get; set; }
    }
}

  说明:在Category类及Product类中的引用属性及集合属性前加virtual修饰,为的是Entity Framework Code First的延迟加载功能。不使用virtual修饰,在Category类的一个实例要查询包含的Product实例时,将不会启用延迟加载。当然Entity Framework Code First延迟加载并不是必须的,所以virtual修饰符也可以不加。

  在定义以上两个类之后,不再添加任何的Entity Framework Code First与数据库的映射配置,运行之后,生成的数据表结构为:

  从生成的Categories与Products表结构可以看出,在Products表中的外键CategoryID与引用的表Categories主键名称相同。跟踪Entity Framework Code First生成数据表的执行脚本可以看到具体生成外键的SQL语句。

ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryID] FOREIGN KEY ([CategoryID]) REFERENCES [dbo].[Categories] ([CategoryID]) ON DELETE CASCADE

  同时,从生成外键的脚本还能看出一点,Entity Framework Code First生成外键是启用级联删除功能的。即当删除Categories表中一条记录时,数据库会自动联带删除Products表中属于该类别的记录。

  在数据库中生成的Products表,查看外键FK_dbo.Products_dbo.Categories_CategoryID属性,其的确有启用级联删除功能。

  2>、[目标类型名称]+[目标类型键名称]

  这种方式要求在Product表中外键列名为Category类名+Category类中键名称,即在Products表中生成的外键名称为Category_CategoryID。示例:在Category类中添加ICollection<Product> Products的集合属性,而在Product类中不做任何与Category关联的代码,也不定义CategoryID属性。

  文件Category.cs:

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

namespace Portal.Entities
{
    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }
}

  文件Product.cs:

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

namespace Portal.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }
    }
}

  在定义以上两个类之后,不再添加任何的Entity Framework Code First与数据库的映射配置,运行之后,生成的数据表结构为:

  3>、[引用属性名称]+[目标类型键名称]

  这种方式为要求在Product表中外键列名为在Product类中引用Category的属性名称 + Category类的主键名称。如:在Product类中定义一个Category属性Cat,则生成的外键名称为Cat_CategoryID。

  文件Category.cs:

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

namespace Portal.Entities
{
    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }
    }
}

  文件Product.cs:

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

namespace Portal.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }

        /// <summary>
        /// 这里为演示,使用Cat作为Category的缩写。
        /// </summary>
        public virtual Category Cat { get; set; }
    }
}

  在定义以上两个类之后,不再添加任何的Entity Framework Code First与数据库的映射配置,运行之后,生成的数据表结构为:

  关于3种不同的外键名称命名之间存在优先级:[目标类型的键名] > [引用属性名称]+[目标类型键名称] > [目标类型名称]+[目标类型键名称]的测试方法:

  [目标类型的键名]的最高优先级:只要在Product类中定义了CategoryID的属性,在Products表中生成的外键列名都只会为CategoryID。

  [引用属性名称]+[目标类型键名称] > [目标类型名称]+[目标类型键名称]:只要在Product类中定义Cat属性,不管Category类中是否定义Products属性,生成的Products表中外键都只会是Cat_CategoryID。

  2、一对多关系

  Entity Framework Code First在根据定义的类生成数据表时,数据表之间的外键关系及所生成的外键列名有默认的约定。但这种约定同样可以进行修改,如将不满足默认外键约定的属性来作为生成表的外键。示例:Category类与Product类,在Product类中定义一个属性CatID,要将CatID属性作为Product的引用Category的外键,而按照Entity Framework Code First的默认约定是不会的。要做到需要的CatID作为外键,同样可以使用Data Annotations和Fluent API两种方式实现。

  1>、Data Annotations方式

  文件类Category.cs:

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

namespace Portal.Entities
{
    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }
}

  文件类Product.cs:

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

using System.ComponentModel.DataAnnotations.Schema;

namespace Portal.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }

        public int CatID { get; set; }

        [ForeignKey("CatID")]
        public virtual Category Category { get; set; }
    }
}

  在定义以上两个类之后,不再添加任何的Entity Framework Code First与数据库的映射配置,运行之后,生成的数据表结构为:

  查看Products表的外键咧CatID引用关系:

  其中,在Product类中,为设置CatID属性为外键的代码为:

public int CatID { get; set; }
[ForeignKey("CatID")]
public virtual Category Category { get; set; }

  该段实现方式还可以改为:

[ForeignKey("Category")]
public int CatID { get; set; }
public virtual Category Category { get; set; }

  2>、Fluent API方式

  文件类Category.cs:

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

namespace Portal.Entities
{
    public class Category
    {
        public int CategoryID { get; set; }
        public string CategoryName { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }
}

  文件类Product.cs:

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

namespace Portal.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }

        public int CatID { get; set; }

        public virtual Category Category { get; set; }
    }
}

  文件类PortalContext.cs:

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

using System.Data.Entity;

using Portal.Entities;

namespace Portal
{
    public class PortalContext : DbContext
    {
        static PortalContext()
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<PortalContext>());
        }

        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Category>()
                .HasMany(t => t.Products)
                .WithRequired(t => t.Category)
                .HasForeignKey(d => d.CatID);
            modelBuilder.Entity<Product>()
                .HasRequired(t => t.Category)
                .WithMany(t => t.Products)
                .HasForeignKey(d => d.CatID);
        }
    }
}

  说明:在PortalContext.cs的OnModelCreating方法中,对两个实体类Category及Product均添加了Fluent API形式的关系配置。对于Entity Framework Code First而言,两个实体类之间的关系,可以两个类中均添加关系映射配置,也可以只对其中任意一个实体类添加关系映射配置。即在PortalContext.cs的OnModelCreating方法中可以只包含Category或只包含Product类的关系映射配置。这里从Entity Framework Code First的使用经验及技巧,建议将实体类之间关系映射配置在包含外键的类中。即OnModelCreating中只添加对Product实体类的关系映射配置,这样做有一个好处,当Category有多个表引用它时,可以将外键均配置在引用它的实体类中,从而降低Category类的复杂度,同时也有益于代码的维护。

  即在PortalContext.cs的OnModelCreating方法只需下面的定义即可:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasRequired(t => t.Category)
        .WithMany(t => t.Products)
        .HasForeignKey(d => d.CatID);
}

  Entity Framework Code First根据一对多关系关系生成的外键引用约束默认是有级联删除的,可以通过以下方式禁用Category与Product之间的级联删除。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasRequired(t => t.Category)
        .WithMany(t => t.Products)
        .HasForeignKey(d => d.CatID)
        .WillCascadeOnDelete(false);
}

  也可以在Entity Framework Code First生成的全部表中都统一设置禁用一对多级联删除。

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

  虽然Entity Framework Code First是可以支持外键列名自定义的,但在实际的项目中,更多的外键列名称还是与所引用表的主键列名相同。即在Category表中主键为CategoryID,在Product表中外键列名称还是为CategoryID。

  Entity Framework Code First的Fluent API配置实体类与表的映射关系,还可以将所有的实体类与表的映射全部写在一个类中,这样可以方便代码的维护。

  文件类Category.cs:

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

namespace Portal.Entities
{
    public partial class Category
    {
        public Category()
        {
            this.Products = new List<Product>();
        }

        public int CategoryID { get; set; }
        public string CategoryName { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }
}

  文件类CategoryMap.cs,用于描述Category类与生成的表之间的映射关系:

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

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class CategoryMap : EntityTypeConfiguration<Category>
    {
        public CategoryMap()
        {
            // Primary Key
            this.HasKey(t => t.CategoryID);

            // Properties
            this.Property(t => t.CategoryName)
                .IsRequired()
                .HasMaxLength(50);

            // Table & Column Mappings
            this.ToTable("Category");
            this.Property(t => t.CategoryID).HasColumnName("CategoryID");
            this.Property(t => t.CategoryName).HasColumnName("CategoryName");
        }
    }
}

  文件类Product.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public partial class Product
    {
        public int ProductID { get; set; }
        public int CategoryID { get; set; }
        public string ProductName { get; set; }
        public Nullable<decimal> UnitPrice { get; set; }
        public Nullable<int> Quantity { get; set; }

        public virtual Category Category { get; set; }
    }
}

  文件类ProductMap.cs,用于描述Product类与生成的表之间的映射关系:

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

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class ProductMap : EntityTypeConfiguration<Product>
    {
        public ProductMap()
        {
            // Primary Key
            this.HasKey(t => t.ProductID);

            // Properties
            this.Property(t => t.ProductName)
                .IsRequired()
                .HasMaxLength(100);

            // Table & Column Mappings
            this.ToTable("Product");
            this.Property(t => t.ProductID).HasColumnName("ProductID");
            this.Property(t => t.CategoryID).HasColumnName("CategoryID");
            this.Property(t => t.ProductName).HasColumnName("ProductName");
            this.Property(t => t.UnitPrice).HasColumnName("UnitPrice");
            this.Property(t => t.Quantity).HasColumnName("Quantity");

            // Relationships
            this.HasRequired(t => t.Category)
                .WithMany(t => t.Products)
                .HasForeignKey(d => d.CategoryID);
        }
    }
}

  PortalContext.cs的OnModelCreating方法:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new CategoryMap());
    modelBuilder.Configurations.Add(new ProductMap());
}

  3、一对一关系

  在一对一关系中,两个表均有各自的主键,但要看哪个表的主键同时作为外键引用另一个表的主键。示例以User类与UserProfile类作为两个具有一对一关系的类,其中User类包含作为主键的UserID属性,UserProfile类包含作为主键的ProfileID的属性。

  1>、Data Annotations方式

  文件类User.cs:

using System;
using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Portal.Entities
{
    public partial class User
    {
        [Key]
        public int UserID { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public Nullable<bool> IsValid { get; set; }

        public virtual UserProfile UserProfile { get; set; }
    }
}

  文件类UserProfile.cs:

using System;
using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Portal.Entities
{
    public partial class UserProfile
    {
        [Key]
        [ForeignKey("User")]
        public int ProfileID { get; set; }
        public string Name { get; set; }
        public Nullable<bool> Sex { get; set; }
        public Nullable<DateTime> Birthday { get; set; }
        public string Email { get; set; }
        public string Telephone { get; set; }
        public string Mobilephone { get; set; }
        public string Address { get; set; }
        public Nullable<DateTime> CreateDate { get; set; }

        public virtual User User { get; set; }
    }
}

  在定义以上两个类之后,不再添加任何的Entity Framework Code First与数据库的映射配置,运行之后,生成的数据表结构为:

  在生成的数据表中,UserProfile表中的主键ProfileID同时也作为外键引用User表中的主键UserID。

  修改文件类User.cs:

using System;
using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Portal.Entities
{
    public partial class User
    {
        [Key]
        [ForeignKey("UserProfile")]
        public int UserID { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public Nullable<bool> IsValid { get; set; }

        public virtual UserProfile UserProfile { get; set; }
    }
}

  修改文件类UserProfile.cs:

using System;
using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Portal.Entities
{
    public partial class UserProfile
    {
        [Key]
        public int ProfileID { get; set; }
        public string Name { get; set; }
        public Nullable<bool> Sex { get; set; }
        public Nullable<DateTime> Birthday { get; set; }
        public string Email { get; set; }
        public string Telephone { get; set; }
        public string Mobilephone { get; set; }
        public string Address { get; set; }
        public Nullable<DateTime> CreateDate { get; set; }

        public virtual User User { get; set; }
    }
}

  则实体类执行之后生成的数据表User主键UserID将同时作为外键引用UserProfile表的主键ProfileID。

  2>、Fluent API方式

  Fluent API设置实体类生成的表引用与被引用通过WithRequiredPrincipal、WithRequiredDependent及WithOptionalPrincipal、WithOptionalDependent来设置,使用Principal属性的实体类将被另外的实体类生成的表引用,使用Dependent属性的实体类将引用另外的实体类。

  文件类User.cs:

using System;
using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Portal.Entities
{
    public partial class User
    {
        public int UserID { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public Nullable<bool> IsValid { get; set; }

        public virtual UserProfile UserProfile { get; set; }
    }
}

  映射文件类UserMap.cs:

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

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class UserMap : EntityTypeConfiguration<User>
    {
        public UserMap()
        {
            // Primary Key
            this.HasKey(t => t.UserID);

            // Properties
            this.Property(t => t.UserName)
                .HasMaxLength(50);

            this.Property(t => t.Password)
                .HasMaxLength(100);

            // Table & Column Mappings
            this.ToTable("User");
            this.Property(t => t.UserID).HasColumnName("UserID");
            this.Property(t => t.UserName).HasColumnName("UserName");
            this.Property(t => t.Password).HasColumnName("Password");
            this.Property(t => t.IsValid).HasColumnName("IsValid");
        }
    }
}

  文件类UserProfile.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public partial class UserProfile
    {
        public int UserID { get; set; }
        public string Name { get; set; }
        public Nullable<bool> Sex { get; set; }
        public Nullable<DateTime> Birthday { get; set; }
        public string Email { get; set; }
        public string Telephone { get; set; }
        public string Mobilephone { get; set; }
        public string Address { get; set; }
        public Nullable<DateTime> CreateDate { get; set; }

        public virtual User User { get; set; }
    }
}

  映射文件类UserProfileMap.cs:

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

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class UserProfileMap : EntityTypeConfiguration<UserProfile>
    {
        public UserProfileMap()
        {
            // Primary Key
            this.HasKey(t => t.UserID);

            // Properties
            this.Property(t => t.UserID)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

            this.Property(t => t.Name)
                .IsRequired()
                .HasMaxLength(50);

            this.Property(t => t.Email)
                .IsRequired()
                .HasMaxLength(100);

            this.Property(t => t.Telephone)
                .HasMaxLength(50);

            this.Property(t => t.Mobilephone)
                .HasMaxLength(20);

            this.Property(t => t.Address)
                .HasMaxLength(200);

            // Table & Column Mappings
            this.ToTable("UserProfile");
            this.Property(t => t.UserID).HasColumnName("UserID");
            this.Property(t => t.Name).HasColumnName("Name");
            this.Property(t => t.Sex).HasColumnName("Sex");
            this.Property(t => t.Birthday).HasColumnName("Birthday");
            this.Property(t => t.Email).HasColumnName("Email");
            this.Property(t => t.Telephone).HasColumnName("Telephone");
            this.Property(t => t.Mobilephone).HasColumnName("Mobilephone");
            this.Property(t => t.Address).HasColumnName("Address");
            this.Property(t => t.CreateDate).HasColumnName("CreateDate");

            // Relationships
            this.HasRequired(t => t.User)
                .WithRequiredDependent(t => t.UserProfile);
        }
    }
}

  在以上实体类及实体映射类执行以后,生成的数据表结构如下:

  在生成的表结构中,UserProfile表中的主键UserID同时也作为外键引用User表的主键UserID。若修改UserProfileMap.cs如下,则生成的表结构User表的主键UserID将作为你外键引用UserProfile表的主键UserID。

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

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class UserProfileMap : EntityTypeConfiguration<UserProfile>
    {
        public UserProfileMap()
        {
            // Primary Key
            this.HasKey(t => t.UserID);

            // Properties
            this.Property(t => t.UserID)
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

            this.Property(t => t.Name)
                .IsRequired()
                .HasMaxLength(50);

            this.Property(t => t.Email)
                .IsRequired()
                .HasMaxLength(100);

            this.Property(t => t.Telephone)
                .HasMaxLength(50);

            this.Property(t => t.Mobilephone)
                .HasMaxLength(20);

            this.Property(t => t.Address)
                .HasMaxLength(200);

            // Table & Column Mappings
            this.ToTable("UserProfile");
            this.Property(t => t.UserID).HasColumnName("UserID");
            this.Property(t => t.Name).HasColumnName("Name");
            this.Property(t => t.Sex).HasColumnName("Sex");
            this.Property(t => t.Birthday).HasColumnName("Birthday");
            this.Property(t => t.Email).HasColumnName("Email");
            this.Property(t => t.Telephone).HasColumnName("Telephone");
            this.Property(t => t.Mobilephone).HasColumnName("Mobilephone");
            this.Property(t => t.Address).HasColumnName("Address");
            this.Property(t => t.CreateDate).HasColumnName("CreateDate");

            // Relationships
            this.HasRequired(t => t.User)
                .WithRequiredPrincipal(t => t.UserProfile);
        }
    }
}

  4、多对多关系

  Entity Framework Code First在根据定义的多对多关系的类生成数据表时,除了生成实体类定义的属性表之外,还会生成一个中间表。用于体现两个实体表之间的多对多的关系。示例实体类User与Role为多对多关系,一个用户可以属于多个角色,一个角色可以包含多个用户。

  文件类User.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public partial class User
    {
        public int UserID { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public Nullable<bool> IsValid { get; set; }

        public virtual ICollection<Role> Roles { get; set; }
    }
}

  文件类Role.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public partial class Role
    {
        public Role()
        {
            this.Users = new List<User>();
        }

        public int RoleID { get; set; }
        public string RoleName { get; set; }

        public virtual ICollection<User> Users { get; set; }
    }
}

  在定义以上两个类之后,不再添加任何的Entity Framework Code First与数据库的映射配置,运行之后,生成的数据表结构为:

  从以上的表结构中,可以看出,实体类运行之后,除了生成Users表和Roles表之外,还生成了RoleUsers表作为中介表,体现Users表和Roles表之间的多对多关联关系。中介表RoleUsers的字段生成规则按照 [目标类型名称]+[目标类型键名称] 的约定。

  Entity Framework Code First根据默认约定生成的多对多关联关系的表时,默认启用多对多的数据级联删除,可以添加代码进行禁用。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // 禁用多对多关系表的级联删除
    modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}

  FluentAPI实现方式:

  文件类User.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public partial class User
    {
        public int UserID { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public Nullable<bool> IsValid { get; set; }

        public virtual ICollection<Role> Roles { get; set; }
    }
}

  映射文件类UserMap.cs:

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class UserMap : EntityTypeConfiguration<User>
    {
        public UserMap()
        {
            // Primary Key
            this.HasKey(t => t.UserID);

            // Properties
            this.Property(t => t.UserName)
                .HasMaxLength(50);

            this.Property(t => t.Password)
                .HasMaxLength(100);

            // Table & Column Mappings
            this.ToTable("User");
            this.Property(t => t.UserID).HasColumnName("UserID");
            this.Property(t => t.UserName).HasColumnName("UserName");
            this.Property(t => t.Password).HasColumnName("Password");
            this.Property(t => t.IsValid).HasColumnName("IsValid");
        }
    }
}

  文件类Role.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public partial class Role
    {
        public int RoleID { get; set; }
        public string RoleName { get; set; }

        public virtual ICollection<User> Users { get; set; }
    }
}

  映射文件类RoleMap.cs:

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class RoleMap : EntityTypeConfiguration<Role>
    {
        public RoleMap()
        {
            // Primary Key
            this.HasKey(t => t.RoleID);

            // Properties
            this.Property(t => t.RoleName)
                .HasMaxLength(50);

            // Table & Column Mappings
            this.ToTable("Role");
            this.Property(t => t.RoleID).HasColumnName("RoleID");
            this.Property(t => t.RoleName).HasColumnName("RoleName");

            // Relationships
            this.HasMany(t => t.Users)
                .WithMany(t => t.Roles)
                .Map(m =>
                    {
                        m.ToTable("UserRole");
                        m.MapLeftKey("RoleID");
                        m.MapRightKey("UserID");
                    });
        }
    }
}

  运行之后生成的表结构:

  5、一对多自反关系

  一对多自反关系,即一个表存在一个外键列引用自身的主键。在项目中,最常见的一对多自反关系为分类表,分类表通过一个ParentID列保存引用主键,已实现无限级递归。

  Fluent API实现方式:

  文件类Category.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    public class Category
    {
        public int CategoryID { get; set; }
        public int CategoryNo { get; set; }
        public string CategoryName { get; set; }
        public Nullable<int> ParentID { get; set; }
        public virtual Category Parent { get; set; }
        public virtual ICollection<Category> Children { get; set; }
    }
}

  映射文件类CategoryMap.cs:

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class CategoryMap : EntityTypeConfiguration<Category>
    {
        public CategoryMap()
        {
            // Primary Key
            this.HasKey(t => t.CategoryID);

            // Properties
            this.Property(t => t.CategoryName)
                .IsRequired()
                .HasMaxLength(50);

            // Table & Column Mappings
            this.ToTable("Category");
            this.Property(t => t.CategoryID).HasColumnName("CategoryID");
            this.Property(t => t.CategoryNo).HasColumnName("CategoryNo");
            this.Property(t => t.CategoryName).HasColumnName("CategoryName");
            this.Property(t => t.ParentID).HasColumnName("ParentID");

            // Relationships
            this.HasOptional(t => t.Parent)
                .WithMany(t => t.Children)
                .HasForeignKey(d => d.ParentID);
        }
    }
}

  以上代码在运行之后,生成的数据表:

  6、多对多自反关系

  多对多关系示例:Family中一条记录可能有多个Parents,也可能有多个Children。

  文件类Family.cs:

using System;
using System.Collections.Generic;

namespace Portal.Entities
{
    /// <summary>
    /// Family表多对多自反关系
    /// </summary>
    public partial class Family
    {
        public Family()
        {
            this.Parents = new List<Family>();
            this.Children = new List<Family>();
        }

        public int FamilyID { get; set; }
        public string Name { get; set; }
        public Nullable<bool> Sex { get; set; }
        public Nullable<System.DateTime> Birthday { get; set; }
        public virtual ICollection<Family> Parents { get; set; }
        public virtual ICollection<Family> Children { get; set; }
    }
}

  映射文件类FamilyMap.cs:

using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;

using Portal.Entities;

namespace Portal.Mapping
{
    public class FamilyMap : EntityTypeConfiguration<Family>
    {
        public FamilyMap()
        {
            // Primary Key
            this.HasKey(t => t.FamilyID);

            // Properties
            this.Property(t => t.Name)
                .HasMaxLength(50);

            // Table & Column Mappings
            this.ToTable("Family");
            this.Property(t => t.FamilyID).HasColumnName("FamilyID");
            this.Property(t => t.Name).HasColumnName("Name");
            this.Property(t => t.Sex).HasColumnName("Sex");
            this.Property(t => t.Birthday).HasColumnName("Birthday");

            // Relationships
            this.HasMany(t => t.Parents)
                .WithMany(t => t.Children)
                .Map(m =>
                    {
                        m.ToTable("FamilyRelationship");
                        m.MapLeftKey("ParentID");
                        m.MapRightKey("ChildID");
                    });
        }
    }
}

  Family类及映射配置类,在运行之后生成的数据表结构:

  在上面的表结构中,指定Family之间的中介表为FamilyRelationship,其中FamilyRelationship的两个字段ParentID及ChildID均引用Familyi表中的FamilyID作为外键。可能在实际的项目过程中,出现这种多对多自反引用关系的情况比较少见。

时间: 2024-10-19 15:46:39

Entity Framework Code First主外键关系映射约定的相关文章

SQL SERVER中获取表间主外键关系

sql server 2008中的主外键关系获取方式: 转自:http://www.cnblogs.com/ke10/archive/2012/06/11/2544655.html SELECT OBJECT_NAME(con.constid) '关系名' ,OBJECT_NAME(sf.fkeyid) '主键表' ,fcol.name '主键' ,OBJECT_NAME(sf.rkeyid) '外键表' ,rcol.name '外键',st.name'数据类型'FROM sysforeignk

Hinernate进行mysql数据库的逆向工程无法生成主外键关系

(1)我们在进行SSH开发大※的项目的时候,使用myeclipse自带的hibernate的逆向工程工具时,发现竟然无法生成one-to-one  one-to-many等的主外键关系,明明的数据库建时已经有主外键关系了.为啥逆向生成时就不行了呢? (2)首先说一下,我使用的是PHP开发环境Appserv自带的MySQL数据库,不是单独安装的: (3)这是因为数据库引擎的问题, 我们可以在sql命令行中输入命令:show engines; 可以看到default默认的是MyISAM: (3)"M

Sql Server有主外键关系时添加、删除数据

当表之间有主外键关系时删除数据会被约束,添加.删除失败 解决办法,我们可以先把主外键关系的检查约束给关掉 → 然后删除数据 → 之后再把约束打开 查询出关掉所有外键约束的语句 SELECT 'ALTER TABLE ' + O.NAME + ' NOCHECK CONSTRAINT [' + F.NAME + '];' AS COMMAND FROM SYS.FOREIGN_KEYS F INNER JOIN SYS.ALL_OBJECTS O ON F.PARENT_OBJECT_ID = O

MySQL创建数据表并建立主外键关系

为mysql数据表建立主外键需要注意以下几点: 需要建立主外键关系的两个表的存储引擎必须是InnoDB. 外键列和参照列必须具有相似的数据类型,即可以隐式转换的数据类型. 外键列和参照列必须创建索引,如果外键列不存在索引,mysql将自动创建索引. 一.SQL语句创建数据表并设置主外键关系 create table demo.ChineseCharInfo ( ID int not null auto_increment, Hanzi varchar(10) not null, primary

通过SQL脚本来查询SQLServer 中主外键关系

在SQLServer中主外键是什么,以及主外键如何创建,在这里就不说了,不懂的可以点击这里,这篇文章也是博客园的博友写的,我觉得总结的很好: 此篇文章主要介绍通过SQL脚本来查看Sqlserver中主外键关系: SELECT f.name AS '关系名称', OBJECT_NAME(f.parent_object_id) AS '表名称', COL_NAME(fc.parent_object_id,fc.parent_column_id) AS '字段名称', OBJECT_NAME (f.r

EF之Code First设置主外键关系(一)

指定类外键有注释(DataAnnotation)和FluentAPI两种方式,下面我们主要使用DataAnnotation指定外键关系 第一种方式 //1-指定导航属性,会自动生成外键,命名规则为:“对象名称_主键名” public class TUsers { [Key] public int UserId { get; set; } public string Account { get; set; } public string Password { get; set; } public

关于表的主外键关系练习 师生 分数表

--创建三个表 --表一:学生表 student--学号:code int (主键)从1开始--姓名:name varchar(50)--性别:sex char(10)--班级:banji char(10)--语文教师编号:yujiao int --数学教师编号:yujiao int --英语教师编号:yujiao int --表二:教师表 teacher--教师名字:name --教师编号:code int (主键) 从1001开始--负责课程:lesson char(10)(语文.数学.英语

SQL Server数据库中导入导出数据及结构时主外键关系的处理

2015-01-26 软件开发中,经常涉及到不同数据库(包括不同产品的不同版本)之间的数据结构与数据的导入导出.处理过程中会遇到很多问题,尤为突出重要的一个问题就是主从表之间,从表有外检约束,从而导致部分数据无法导入. 情景一.同一数据库产品,相同版本 此种情况下源数据库与目标数据库的数据结构与数据的导入导出非常简单. 方法1:备份源数据库,恢复到目标数据库即完成. 方法2:使用SQL Sever数据库自带的[复制数据库]功能或者[导入数据]功能按照向导操作即可. 情景二.同一数据库产品,不同版

mysql|中主外键关系(转)

http://my.oschina.net/liting/blog/356150 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为空,用来保证数据完整性 外键:是另一表的主键, 外键可以有重复的, 可以是空值,用来和其他表建立联系用的.所以说,如果谈到了外键,一定是至少涉及到两张表.例如下面这两张表: 上面有两张表:部门表(dept).员工表(emp).Id=Dept_id,而Dept_id就