重庆熊猫 Loading

Entity Framework教程-Entity Framework-模型关系(Model Relationships)

更新记录
转载请注明出处:
2022年10月17日 发布。
2022年10月10日 从笔记迁移到博客。

配置实体关系的方式

使用数据特性
使用FluentAPI关系配置
使用关系属性

导航属性(navigation property)

导航属性说明

一个类型为主体实体或从属实体的属性

无论它是指它的父实体还是持有一个或多个从属实体

都被称为导航属性(navigation property)

导航属性分类

说明

集合型导航属性(Collection navigation property)

引用型导航属性(Reference navigation property)

反向引用导航属性(Inverse navigation property)

集合型导航属性(Collection navigation property)

集合型导航属性保存一个依赖实体的集合(或对依赖项列表的引用)

并且始终保存对多个项的引用

//博客实体
class Blog
{
    //主键
    [Key]
    public int Id { get; set; }
    public string Title { get; set; }
    //集合导航属性
    //评论属性
    public ICollection<Post> Posts { get; set; }
}

引用型导航属性(Reference navigation property)

引用型导航属性保存对其父实体的引用,并且始终保存对单个项的引用

//评论实体
public class Post
{
  //其他代码...
  public int BlogId { get; set; }
  
  //引用型导航属性
  //关联的blog实体
  public Blog Blog { get; set; }
}

反向引用导航属性(Inverse navigation property)

EF Core模型间关联(Modeling Relationships)

EF Core关联的默认约定(Conventions in a relationship)

定义两个实体之间的关系时,在两个实体内部都需要定义关联导航属性

并且从属实体应具有外键属性

如果这两个实体的导航属性相互指向,EF会将它们配置为反向导航属性

两实体都定义导航属性的实体关系叫做完全定义的关系(Fully-defined relationships)

//博客实体
public class Blog
{
    //定义集合导航属性
    public ICollection<Post> Posts { get; set; }    
}

//评论实体
public class Post
{
    //外键属性
    public int BlogId { get; set; }
    //反向导航属性
    public Blog Blog { get; set; }
}

如果使用主键属性名的主体实体名,EF将认为该属性为外键

public class Blog
{
    public int Id { get; set; }
}

public class Post
{
    public int BlogId { get; set; }
}

如果主体实体主键与依赖实体引用键匹配,EF将创建外键关系

public class Blog
{
    public int Id { get; set; }
}

public class Post
{
    public Blog SomeBlog { get; set; }
    public int SomeBlogId { get; set; }  //外键字段
}

注意:如果我们不创建外键字段,EF Core仍然会在底层自动创建

public class Blog
{
    public int Id { get; set; }
}

public class Post
{
    public Blog SomeBlog { get; set; }
}

注意:如果我们把外键关联属性也删除了,EF Core仍然会在底层自动创建外键字段

public class Blog
{
  public ICollection<Post> Posts { get; set; }    
}

public class Post
{
    //仍会创建外键字段
}

注意:如果实体间包含多个关联,还是需要自己设置反向关联

public class Blog
{
    [InverseProperty("SomeBlog")]
    public ICollection<Post> SomePosts { get; set; }
}

public class Post
{
  public int SomeBlogId { get; set; }
  public Blog SomeBlog { get; set; }
}

EF Core手动设置外键底层字段

除了使用EF默认的关联约定,还可以自己设置底层的外键字段

使用ForeignKey数据注解即可

public class Blog
{
  public ICollection<Post> Posts { get; set; }    
}

public class Post
{
  public int BlogSomeId { get; set; }
  //手动设置外键底层字段
  [ForeignKey("BlogSomeId")]
  public Blog Blog { get; set; }
}

Fluent API配置关系的方法

HasXXX(...).WithXXX(...);

有XXX、反之带有XXX。XXX可选值One、Many。

HasOne(...).WithMany(...);		//一对多
HasOne(...).WithOne (...);		//一对一
HasMany(...).WithMany(...);		//多对多

注意:不设置反向的属性的时候,Withxxxx()不设置参数即可。

技巧:

1.关系配置在任何一方都可以。

2.对于主从结构的“一对多”表关系,一般是声明双向导航属性。而对于其他的“一对多”表关系:如果表属于被很多表引用的基础表,则用单项导航属性,否则可以自由决定是否用双向导航属性。

3.对于一对一的关系,使用[ForeignKey]特性标注存储外键的关联属性,或者专门定义一个外键属性,或者使用FluentAPI。

builder.HasOne<Delivery>(o => o.Delivery).WithOne(d =>d.Order).HasForeignKey<Delivery>(d=>d.OrderId);

设置外键属性

1、在实体类中显式声明一个外键属性。
2、关系配置中通过HasForeignKey(c=>c.ArticleId)指定这个属性为外键。
3、除非必要,否则不用声明,因为会引入重复。

设置一对一(One-to-one relationships)-使用默认约定

设置方法

在两个实体对象中建立引用属性,然后添加外键属性即可

注意:务必将两个实体都加上[Key]主键修饰,否则无法设置一对一

本质是:在子表内设置外键字段,并将该字段设置为唯一

引用数据的实体(数据库中的子表)(引用数据方):

Public 被引用Model名 被引用Model名 { get; set; }

public int? 被引用Model名Id { get; set; }

被引用数据的实体模型(数据库中的母表)(被引用数据方):

public 引用Model名 引用Model名 { get; set; }

实例:一个人只能有一个身份证卡(一对一)(Fluent API)

IdCard.cs Id卡实体

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// Id卡
    /// </summary>
    public class IdCard
    {
        [Key]
        public Guid Id { get; set; }
        public string? Name { get; set; }

        [ForeignKey("PersonId")]
        public Person? Person { get; set; }
    }
}

IdCardEntityConfig.cs Id卡实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 人员实体配置
    /// </summary>
    public class IdCardEntityConfig : IEntityTypeConfiguration<IdCard>
    {
        public void Configure(EntityTypeBuilder<IdCard> builder)
        {
        }
    }
}

Person.cs 人员实体

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// 人员实体
    /// </summary>
    public class Person
    {
        [Key]
        public Guid Id { get; set; }
        public string? Name { get; set; }

        public IdCard? IdCard { get; set; }
    }
}

PersonEntityConfig.cs 人员实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 人员实体配置
    /// </summary>
    public class PersonEntityConfig : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            //建立一对一的关联
            builder.HasOne(i => i.IdCard).WithOne(i => i.Person);
        }
    }
}

TestDbContext.cs 数据库上下文

using Microsoft.EntityFrameworkCore;
using System.Diagnostics;

namespace PandaTest
{
    /// <summary>
    /// 数据库上下文
    /// </summary>
    public class TestDbContext: DbContext
    {
        public TestDbContext()
        {

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string connectionString = "Server=.;Database=Test;User Id=sa;Password=Password";
            optionsBuilder.UseSqlServer(connectionString);

            optionsBuilder.LogTo(x => { Debug.WriteLine(x); });
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //应用所有实体配置
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }

        public DbSet<Person> Persons { get; set; }
        public DbSet<IdCard> IdCards { get; set; }
    }
}

Program.cs 主程序

using PandaTest;
using Microsoft.EntityFrameworkCore;

Console.WriteLine("Begin");

//创建DbContext
using (TestDbContext db = new TestDbContext())
{
    var Panda = new Person
    {
        Name = "Panda",
        IdCard = new IdCard() { Name = "PandaIdCard" }
    };


    //插入数据
    db.Persons.Add(Panda);
    await db.SaveChangesAsync();
}

Console.WriteLine("Success");

实例:一个人只能有一个对象(一对一)(自引用)(Fluent API)

Person.cs 人员实体

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// 人员实体
    /// </summary>
    public class Person
    {
        [Key]
        public Guid Id { get; set; }
        public string? Name { get; set; }

        [ForeignKey("OtherHalfId")]
        public virtual Person? OtherHalf { get; set; }
    }
}

PersonEntityConfig.cs 人员实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 人员实体配置
    /// </summary>
    public class PersonEntityConfig : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
        }
    }
}

TestDbContext.cs 数据库上下文

using Microsoft.EntityFrameworkCore;
using System.Diagnostics;

namespace PandaTest
{
    /// <summary>
    /// 数据库上下文
    /// </summary>
    public class TestDbContext: DbContext
    {
        public TestDbContext()
        {

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string connectionString = "Server=.;Database=Test;User Id=sa;Password=Password";
            optionsBuilder.UseSqlServer(connectionString);

            optionsBuilder.LogTo(x => { Debug.WriteLine(x); });
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //应用所有实体配置
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }

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

Program.cs 主程序

using PandaTest;
using Microsoft.EntityFrameworkCore;

Console.WriteLine("Begin");

//创建DbContext
using (TestDbContext db = new TestDbContext())
{
    var Panda = new Person
    {
        Name = "Panda",
    };

    var Donkey = new Person
    {
        Name = "Donkey",
        OtherHalf = Panda
    };

    //插入数据
    db.Persons.Add(Donkey);
    await db.SaveChangesAsync();

    var panda = db.Persons.Where(i => i.Name == "Panda").FirstOrDefault();
    panda.OtherHalf = Donkey;
    db.Persons.Update(panda);
    await db.SaveChangesAsync();
}


Console.WriteLine("Success");

实例:用户 和 户籍地址

一个用户 只有 一个户籍地址

一个户籍地址 只对应 一个用户

image

代码:

using System;
using System.ComponentModel.DataAnnotations;

namespace ConsoleApp3.Models
{
    public class Address
    {
        [Key]
        public int Id { get; set; }
        public string Description { get; set; }
        //外键
        public int UserId { get; set; }
        //关联用户模型
        public User User { get; set; }
    }
}
using System;
using System.ComponentModel.DataAnnotations;

namespace ConsoleApp3.Models
{
    public class User
    {
        [Key]
        public int Id { get; set; }
        public string Name { get; set; }
        //关联地址模型
        public Address Address { get; set; }
    }
}

实例:用户 和 颜色

一个用户 有且只有 一个颜色

一个颜色 有且只对应 一个用户

image

代码:

颜色实体Entity:

class Color
{
    [Key]
    public int ColorId { get; set; }
    public string ColorTitle { get; set; }
    public User User { get; set; }   //重点
}

用户实体Entity:

class User
{
    [Key]
    public int UserId { get; set; }
    public string UserName { get; set; }
    public Color Color { get; set; }
    public int? ColorId { get; set; }
}

设置一对一(One-to-one relationships)-使用FluentAPI

设置方法

本质是:在子表内设置外键字段,并将该字段设置为唯一

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //设置外键关联
    modelBuilder.Entity<User>()
        .HasOne(x => x.Address) //设置关联到另一个唯一的实体
        .WithOne(x => x.User)   //配置一对一的关系
        .HasForeignKey<Address>(x => x.UserId); //配置外键
}

实例:用户 和 户籍地址

一个用户 只有 一个户籍地址

一个户籍地址 只对应 一个用户

image

代码:

using System;
using System.ComponentModel.DataAnnotations;

namespace ConsoleApp3.Models
{
    public class Address
    {
        [Key]
        public int Id { get; set; }
        public string Description { get; set; }
        public User User { get; set; }
        public int UserId { get; set; }
    }
}
using System;
using System.ComponentModel.DataAnnotations;

namespace ConsoleApp3.Models
{
    public class User
    {
        [Key]
        public int Id { get; set; }
        public string UserName { get; set; }
        public int Age { get; set; }
        public Address Address { get; set; }
    }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //设置映射到底层数据库的表名
    modelBuilder.Entity<Address>().ToTable("AddressTable");

    //设置外键关联
    modelBuilder.Entity<User>()
        .ToTable("UserTable")  //设置映射到底层数据库的表名
        .HasOne(x => x.Address) //设置关联到另一个唯一的实体
        .WithOne(x => x.User)   //配置一对一的关系
        .HasForeignKey<Address>(x => x.UserId); //配置外键
}

实例:学生 和 学生地址

一个学生 对应一个 学生地址

一个学生地址 对应一个 学生

学生实体:

//学生实体
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    //导航属性
    public StudentAddress Address { get; set; }
}

学生地址实体:

//学生地址实体
public class StudentAddress
{
    public int StudentAddressId { get; set; }
    public string Address { get; set; }
    //外键导航属性
    public int AddressOfStudentId { get; set; }
    //导航属性
    public Student Student { get; set; }
}

配置一对一关系:

//配置一对一关系
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>()
        .HasOne(s => s.Address)
        .WithOne(d => d.Student)
        .HasForeignKey<StudentAddress>(d => d.StudentAddressId);
}

image

除了可以在Student实体上配置,还可以在StudentAddress实体上配置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<StudentAddress>()
        .HasOne<Student>(d => d.Student)
        .WithOne(s => s.Address)
        .HasForeignKey<StudentAddress>(d => d.AddressOfStudentId);
}

生成的数据库如同所示:

image

设置一对多(One-to-many relationships)-使用默认约定

设置方法1(EF约定1)- 单方设置引用属性

直接在单个实体模型中定义引用

//学生
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    //关联的年级
    public Grade Grade { get; set; }
}
//年级
public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }
}

这种方式最终在数据库中的表会生成一个外键字段

image

注意:

这种方式生成的外键字段GradeId是可以为null的

可以通过FluentAPI设置其禁止为NULL

设置方法2(EF约定2)- 多方设置引用

//学生实体
public class Student
{
    public int StudentId { get; set; }
    public string StudentName { get; set; }
}
//年级实体
public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }
    //关联多个学生
    public ICollection<Student> Students { get; set; } 
}

image

设置方法3(EF约定3)- 双方都建立引用属性

本质就是:约定1+约定2

//学生实体
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    //引用属性
    public Grade Grade { get; set; }
}
//年级实体
public class Grade
{
    public int GradeID { get; set; }
    public string GradeName { get; set; }
    //集合引用属性
    public ICollection<Student> Students { get; set; }
}

image

设置方法4(EF约定4)- 设置额外的外键属性

在约定3的基础上再增加 外键字段属性

//学生实体
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    //外键字段属性
    public int GradeId { get; set; }
    //引用属性
    public Grade Grade { get; set; }
}
//年级实体
public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }
    //集合引用属性
    public ICollection<Student> Students { get; set; }
}

image

注意:

这种方式定义的数据库的表的外键字段是不可以为Null的

​ 如果需要字段是可以为NULL的,请使用:

public int? GradeId { get; set; } 

实例:用户 和 产品

一个用户 对应 多个产品

一个产品 对应 一个用户

image

产品Entity:

class Product
{
    [Key]
    public int ProdcutId { get; set; }
    public string ProductTitle { get; set; }
    public decimal Price { get; set; }
    public virtual User User { get; set; }
    [ForeignKey("UserId")]
    public int? UserId { get; set; }
}

用户实体Entity:

class User
{
    [Key]
    public int UserId { get; set; }
    public string UserTitle { get; set; }
    public int age { get; set; }
    public virtual ICollection<Product> Products { get; set; } = new List<Product>();
}

实例:用户 和 车

一个用户 对应 多台车

一台车 只属于 一个用户

image

用户实体Entity:

class User
{
    public int UserId { get; set; }
    [StringLength(100)]
    public string UserName { get; set; }
    public ICollection<Car> Cars { get; set; } = new List<Car>();
}

车实体Entity:

class Car
{
    public int CarId { get; set; }
    public string CarBrand { get; set; }
    public virtual User User { get; set; }  //重点
    [ForeignKey("UserId")]
    public int? UserId { get; set; }        //重点
}

实例:用户 和 用户自定义配置信息

一个用户 有多个 自定义配置信息

多个自定义配置信息 对应 一个用户

image

用户实体:

public class User
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime JoinDateTime { get; set; }
    public ICollection<Configuration> Configurations { get; set; }
}

配置项实体:

public class Configuration
{
    [Key]
    public int Id { get; set; }
    public string Code { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public virtual User BelongUser { get; set; }
}

实例:博客文章 和 评论

一篇博客 可以有 多条评论

多条评论 对应 一篇博客

image

代码:

评论实体模型:

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

namespace ConsoleApp3.Models
{
    public class Post
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; }
        public string Context { get; set; }
        public Blog Blog { get; set; }
        public int BlogId { get; set; }
    }
}

博客文章实体模型:

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

namespace ConsoleApp3.Models
{
    public class Blog
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public ICollection<Post> Posts { get; set; }
    }
}

设置一对多(One-to-many relationships)-使用FluentAPI

设置方法(EF约定)

创建实体模型:学生 和 年级

一个学生对应一个年级,一个年级有多个学生

学生实体:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    //外键导航属性
    public int CurrentGradeId { get; set; }
    //导航属性
    public Grade Grade { get; set; }
}

年级实体:

public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }
    //集合导航属性
    public ICollection<Student> Students { get; set; }
}

定义FluentAPI

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>()
        .HasOne<Grade>(s => s.Grade)    //(一)学生实体只有一个年级导航属性
        .WithMany(g => g.Students)      //(多)年级实体对应多个学生
        .HasForeignKey(s => s.CurrentGradeId);  //学生实体有一个外键导航属性
}

关系如图所示:

image

除了在学生实体上进行定义与年级实体的关联
还可以在年级实体上定义与学生实体的关联

protected override void OnModelCreating(ModelBuilder modelBuilder)
{

    modelBuilder.Entity<Grade>()
        .HasMany<Student>(g => g.Students)  //(多)年级实体对应多个学生
        .WithOne(s => s.Grade)              //(一)学生实体只有一个年级导航属性
        .HasForeignKey(s => s.CurrentGradeId);   //学生实体有一个外键导航属性
}

实例:父亲 和 儿子

一个父亲 可以有 多个儿子

一个儿子 只有一个 亲生父亲

父亲实体:

//父亲实体
public class Father
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    //集合导航属性
    public ICollection<Son> Sons { get; set; }
}

儿子实体:

//儿子实体
public class Son
{
    public int Id { get; set; }
    public string Name { get; set; }
    //外键导航属性
    public int FatherId { get; set; }
    //导航属性
    public Father Father { get; set; }
}

定义FluentAPI:

//定义FluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //定义一对多
    modelBuilder.Entity<Father>()
        .HasMany(f => f.Sons)
        .WithOne(s => s.Father)
        .HasForeignKey(s => s.FatherId);
}

或者定义FluentAPI:

//定义FluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //定义多对一
    modelBuilder.Entity<Son>()
        .HasOne(s => s.Father)
        .WithMany(f => f.Sons)
        .HasForeignKey(s => s.FatherId);
}

实例:一个人对应多台车(一对多)(Fluent API)

Car.cs 汽车实体

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// 汽车实体
    /// </summary>
    public class Car
    {
        public Guid Id { get; set; }
        public string CarName { get; set; }

        public Person BelongPerson { get; set; }
    }
}

Person.cs 人员实体

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// 人员实体
    /// </summary>
    public class Person
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public List<Car> Cars { get; set; }
    }
}

PersonEntityConfig.cs 人员实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 人员实体配置
    /// </summary>
    public class PersonEntityConfig : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            //配置关联(一个人有多台车,一个车只属于一个人)
            builder.HasMany(p => p.Cars).WithOne(c => c.BelongPerson);
        }
    }
}

CarEntityConfig.cs 汽车实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 汽车实体配置
    /// </summary>
    public class CarEntityConfig : IEntityTypeConfiguration<Car>
    {
        public void Configure(EntityTypeBuilder<Car> builder)
        {
            
        }
    }
}

TestDbContext.cs 数据库上下文

using Microsoft.EntityFrameworkCore;

namespace PandaTest
{
    /// <summary>
    /// 数据库上下文
    /// </summary>
    public class TestDbContext: DbContext
    {
        public TestDbContext()
        {

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string connectionString = "Server=.;Database=Test;User Id=sa;Password=Password";
            optionsBuilder.UseSqlServer(connectionString);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //应用所有实体配置
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }

        public DbSet<Car> Cars { get; set; }
        public DbSet<Person> Persons { get; set; }
    }
}

Program.cs 主程序

using PandaTest;

Console.WriteLine("Begin");

//创建DbContext
using (TestDbContext db = new TestDbContext())
{
    //插入数据
    db.Persons.Add(
        new Person{
            Name = "Panda",
            Cars = new List<Car>{
                new Car()
                {
                    Id = Guid.NewGuid(),
                    CarName = "PandaCar1"
                },
                new Car()
                {
                    Id = Guid.NewGuid(),
                    CarName = "PandaCar2"
                }
            }
        });
    await db.SaveChangesAsync();
}

Console.WriteLine("Success");

设置多对多(Many-to-many relationships)-使用默认约定

设置方法1(使用中间表)

折中方式,采用中间实体的形式实现,建立2个实体与中间实体的一对多的约定

多对多在数据库内部通过中间表的方式实现,所以需要添加一个中间Model

本质是:通过中间Model对两端Model的一对多的关系完成的

image

中间表Entity:

注意:还可以将CompanyId和 ConsumerId设置为Key,形成唯一pair

class ConsumerAndCompany
{
    [Key]
    public int ConsumerAndCompanyId { get; set; }
    [Required]
    public int CompanyId { get; set; }
    public virtual Company Company { get; set; }
    [Required]
    public int ConsumerId { get; set; }
    public virtual Consumer Consumer { get; set; }
    //建立客户关系的时间
    public DateTime ConnectionDateTime { get; set; }
    //建立客户关系的备注
    public String Note { get; set; }
}

Consumer Entity:

class Consumer
{
    [Key]
    public int ConsumerId { get; set; }
    public string ConsumerName { get; set; }
    public virtual List<ConsumerAndCompany> ConsumerAndCompanys { get; set; } = new List<ConsumerAndCompany>();
}

Company Entity:

class Company
{
    [Key]
    public int CompanyId { get; set; }
    public string CompanyName { get; set; }
    public virtual List<ConsumerAndCompany> CompanyAndConsumers { get; set; } = new List<ConsumerAndCompany>();
}

设置方法2(使用默认约定)

两个实体之间都建立引用对方的集合导航属性即可

注意:EF2.0中不支持多对多的默认约定,只可以采用中间实体的方式折中表达

提示:这样会自动生成中间表,无需手动创建

学生实体:

//学生实体
public class Student
{
    [Key]
    public int StudentId { get; set; }
    public string Name { get; set; }
    //集合导航属性
    public IList<Course> Courses { get; set; }
}

课程实体:

//课程实体
public class Course
{
    [Key]
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    //集合导航属性
    public IList<Student> Students { get; set; }
}

实例:文章 和 标签

一篇文章 可以有 多个标签

一个标签 可以对应 多篇文章

image

代码:

文章实体:

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

namespace ConsoleApp3.Models
{
    public class Blog
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public ICollection<TagsAndBlogs> TagsAndBlogs { get; set; }
    }
}

标签实体:

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

namespace ConsoleApp3.Models
{
    public class Tag
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public ICollection<TagsAndBlogs> TagsAndBlogs { get; set; }
    }
}

中间表实体:

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

namespace ConsoleApp3.Models
{
    public class TagsAndBlogs
    {
        public int Id { get; set; }
        public DateTime AddDateTime { get; set; }
        //对博客的外键
        public int BlogId { get; set; }
        public Blog Blog { get; set; }
        //对标签的外键
        public int TagId { get; set; }
        public Tag Tag { get; set; }

    }
}

实例:消费者 和 公司

一个公司 对应 多个消费者

一个消费者 对应 多个公司

image

中间表Entity:

注意:还可以将CompanyId和 ConsumerId设置为Key,形成唯一pair

class ConsumerAndCompany
{
    [Key]
    public int ConsumerAndCompanyId { get; set; }
    [Required]
    public int CompanyId { get; set; }
    public virtual Company Company { get; set; }
    [Required]
    public int ConsumerId { get; set; }
    public virtual Consumer Consumer { get; set; }
    //建立客户关系的时间
    public DateTime ConnectionDateTime { get; set; }
    //建立客户关系的备注
    public String Note { get; set; }
}

Consumer Entity:

class Consumer
{
    [Key]
    public int ConsumerId { get; set; }
    public string ConsumerName { get; set; }
    public virtual List<ConsumerAndCompany> ConsumerAndCompanys { get; set; } = new List<ConsumerAndCompany>();
}

Company Entity:

class Company
{
    [Key]
    public int CompanyId { get; set; }
    public string CompanyName { get; set; }
    public virtual List<ConsumerAndCompany> CompanyAndConsumers { get; set; } = new List<ConsumerAndCompany>();
}

设置多对多(Many-to-many relationships)-使用FluentAPI

设置方法1(使用中间表)

折中方式,采用中间实体的形式实现,建立2个实体与中间实体的一对多的约定

多对多在数据库内部通过中间表的方式实现,所以需要添加一个中间Model

本质是:通过中间Model对两端Model的一对多的关系完成的

学生实体

//学生实体
public class Student
{
    [Key]
    public int StudentId { get; set; }
    public string Name { get; set; }
    //集合导航属性
    public IList<StudentCourse> StudentCourses { get; set; }
}

课程实体:

//课程实体
public class Course
{
    [Key]
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    public string Description { get; set; }
    //集合导航属性
    public IList<StudentCourse> StudentCourses { get; set; }
}

学生课程关联关系中间实体:

//学生课程关联关系中间实体
public class StudentCourse
{
    //外键导航属性
    public int StudentId { get; set; }
    //导航属性
    public Student Student { get; set; }
    //外键导航属性
    public int CourseId { get; set; }
    //导航属性
    public Course Course { get; set; }
    //添加日期
    public DateTime AddDateTime { get; set; }
}

建立2个实体到中间实体的一对多的关系:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //设置StudentCourse实体的主键为StudentId+CourseId
    modelBuilder.Entity<StudentCourse>()
                    .HasKey(sc => new { sc.StudentId, sc.CourseId });

    //建立2个实体到中间实体的一对多的关系
    modelBuilder.Entity<StudentCourse>()
        .HasOne<Student>(sc => sc.Student)
        .WithMany(s => s.StudentCourses)
        .HasForeignKey(sc => sc.SId);

    modelBuilder.Entity<StudentCourse>()
        .HasOne<Course>(sc => sc.Course)
        .WithMany(s => s.StudentCourses)
        .HasForeignKey(sc => sc.CId);
}

设置方式2(使用HasMany、WithMany FluentAPI)

提示:这样会自动生成中间表,无需手动创建

学生实体:

//学生实体
public class Student
{
    [Key]
    public int StudentId { get; set; }
    public string Name { get; set; }
    //集合导航属性
    public IList<Course> Courses { get; set; }
}

课程实体:

//课程实体
public class Course
{
    [Key]
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    //集合导航属性
    public IList<Student> Students { get; set; }
}

在Student实体上配置多对多:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //建立多对多
    modelBuilder.Entity<Student>()
        .HasMany(s => s.Courses)
        .WithMany(c => c.Students);
}

也可以在Course实体上配置多对多:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //建立多对多
    modelBuilder.Entity<Course>()
        .HasMany(c => c.Students)
        .WithMany(S => S.Courses);
}

实例:一个人有多个公司,一个公司可以属于多个人(多对多)(Fluent API)

Company.cs 公司实体

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// 公司实体
    /// </summary>
    public class Company
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public List<Person> Persons { get; set; }
    }
}

Person.cs 人员实体

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.Design;

namespace PandaTest
{
    /// <summary>
    /// 人员实体
    /// </summary>
    public class Person
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public List<Company> Companies { get; set; }
    }
}

CompanyEntityConfig.cs 公司实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 公司实体配置
    /// </summary>
    public class CompanyEntityConfig : IEntityTypeConfiguration<Company>
    {
        public void Configure(EntityTypeBuilder<Company> builder)
        {
            //配置多对多
            builder.HasMany(i => i.Persons).WithMany(i => i.Companies);
        }
    }
}

PersonEntityConfig.cs 人员实体配置

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace PandaTest
{
    /// <summary>
    /// 人员实体配置
    /// </summary>
    public class PersonEntityConfig : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
        }
    }
}

TestDbContext.cs 数据库上下文

using Microsoft.EntityFrameworkCore;

namespace PandaTest
{
    /// <summary>
    /// 数据库上下文
    /// </summary>
    public class TestDbContext: DbContext
    {
        public TestDbContext()
        {

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string connectionString = "Server=.;Database=Test;User Id=sa;Password=Password";
            optionsBuilder.UseSqlServer(connectionString);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //应用所有实体配置
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }

        public DbSet<Company> Companies { get; set; }
        public DbSet<Person> Persons { get; set; }
    }
}

Program.cs 主程序

using PandaTest;

Console.WriteLine("Begin");

//创建DbContext
using (TestDbContext db = new TestDbContext())
{
    //插入数据
    db.Persons.Add(
        new Person{
            Name = "Panda6",
            Companies = new List<Company>
            {
                new Company(){ Name = "公司1"},
                new Company(){ Name = "公司2"},
            }
        });
    //插入数据
    db.Companies.Add(new Company
    {
        Name = "公司3",
        Persons = new List<Person>
        {
            new Person(){ Name = "Cat"},
        }
    });
    await db.SaveChangesAsync();
}



Console.WriteLine("Success");

实例:文章 和 标签

一篇文章 可以有 多个标签

一个标签 可以对应 多篇文章

image

代码:

文章实体:

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

namespace ConsoleApp3.Models
{
    public class Blog
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public ICollection<TagsAndBlogs> TagsAndBlogs { get; set; }
    }
}

标签实体:

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

namespace ConsoleApp3.Models
{
    public class Tag
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public ICollection<TagsAndBlogs> TagsAndBlogs { get; set; }
    }
}

中间表实体:

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

namespace ConsoleApp3.Models
{
    public class TagsAndBlogs
    {
        public int Id { get; set; }
        public DateTime AddDateTime { get; set; }
        //对博客的外键
        public int BlogId { get; set; }
        public Blog Blog { get; set; }
        //对标签的外键
        public int TagId { get; set; }
        public Tag Tag { get; set; }

    }
}

FluentAPI:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //设置对应到底层数据的数据库的表名
    modelBuilder.Entity<Blog>().ToTable("Blog");
    modelBuilder.Entity<Tag>().ToTable("Tag");

    //设置中间表到标签的一对多关系
    modelBuilder.Entity<TagsAndBlogs>()
                            .ToTable("TagsAndBlogs")
                            .HasOne(x => x.Tag)
                            .WithMany(x => x.TagsAndBlogs)
                            .HasForeignKey(x => x.TagId);
    
    //设置中间表到博客的一对多关系
    modelBuilder.Entity<TagsAndBlogs>()
                        .ToTable("TagsAndBlogs")
                        .HasOne(x => x.Blog)
                        .WithMany(x => x.TagsAndBlogs)
                        .HasForeignKey(x => x.BlogId);
}

实例:学生 和 课程

一个学生 可以学习 多门课程

一门课程 可以被 多名学生学习

学生实体:

//学生实体
public class Student
{
    [Key]
    public int StudentId { get; set; }
    public string Name { get; set; }
    //集合导航属性
    public IList<StudentCourse> StudentCourses { get; set; }
}

课程实体:

//课程实体
public class Course
{
    [Key]
    public int CourseId { get; set; }
    public string CourseName { get; set; }
    public string Description { get; set; }
    //集合导航属性
    public IList<StudentCourse> StudentCourses { get; set; }
}

学生课程关联关系中间实体:

//学生课程关联关系中间实体
public class StudentCourse
{
    //外键导航属性
    public int StudentId { get; set; }
    //导航属性
    public Student Student { get; set; }
    //外键导航属性
    public int CourseId { get; set; }
    //导航属性
    public Course Course { get; set; }
    //添加日期
    public DateTime AddDateTime { get; set; }
}

建立2个实体到中间实体的一对多的关系:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //设置StudentCourse实体的主键为StudentId+CourseId
    modelBuilder.Entity<StudentCourse>()
                    .HasKey(sc => new { sc.StudentId, sc.CourseId });

    //建立2个实体到中间实体的一对多的关系
    modelBuilder.Entity<StudentCourse>()
        .HasOne<Student>(sc => sc.Student)
        .WithMany(s => s.StudentCourses)
        .HasForeignKey(sc => sc.SId);

    modelBuilder.Entity<StudentCourse>()
        .HasOne<Course>(sc => sc.Course)
        .WithMany(s => s.StudentCourses)
        .HasForeignKey(sc => sc.CId);
}

其他关联

枚举关联(Enum Mapping)

对于属性的值是枚举值的情况,可以直接使用枚举关联

实例:

//定义需要用到的枚举
public enum Direction
{
    Up = 0,
    Down = 1
}

public class CarState
{
    [Key]
    public int Id {get;set;}
    //枚举关联
    public Direction Direction {get;set;}
}

实体继承映射(Entity Inheritance Mapping)

说明

EF5支持2种类型的实体继承映射

一个继承一个表(table-per-hierarchy) 和 一个类型一个表(table-per-type)

默认是一个继承一个表(table-per-hierarchy)

一个继承一个表(table-per-hierarchy)

子类和父类的属性放在集中的一个表中

实例:使用Fluent API定义

//形状实体
public class Shape
{
    public int Width { get; set; }
    public int Height { get; set; }
}

//立体实体
public class Cube : Shape
{
    public int Length { get; set; }
}

使用层次结构表意味着,Shape、Cube所有字段会被放在1张数据库表中

不同实体类的字段还可以自定义映射区别字段:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Shape>()
        .HasDiscriminator<string>("ShapeType")
        .HasValue<Shape>("S")
        .HasValue<Cube>("C");
}

最终数据库中的表如下:

Width Height Length ShapeType
"S"
"C"

一个类型一个表(table-per-type)

一个类型对应到底下一个数据库表,继承的属性分开存储到单独的表

实例:使用数据注解定义

[Table("Shape")]
public class Shape
{
    public int Width { get; set; }
    public int Height { get; set; }
}

[Table("Cube")]
public class Cube : Shape
{
    public int Length { get; set; }
}

实例:使用Fluent API定义

public class Shape
{
    public int Width { get; set; }
    public int Height { get; set; }
}

public class Cube : Shape
{
    public int Length { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Shape>().ToTable("Shape");
    modelBuilder.Entity<Cube>().ToTable("Cube");
}

EF模型间关联(Modeling Relationships)

https://docs.microsoft.com/en-us/ef/core/modeling/relationships

设置一对一(One-to-one relationships)

设置一对多(One-to-many relationships)

实体对象的关系图如下:

image

注意:其中Product表的Category字段是外键,关联Category表的Id字段

EF代码结构:

Product.cs中除了正常声明字段外,还要用以下代码声明关联关系:

public virtual Category Category1 { get; set; }

Product.cs完整代码:

[Table("Product")]
public partial class Product
{
    public int Id { get; set; }
    [Required]
    [StringLength(100)]
    public string Name { get; set; }
    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }
}

Category.cs中需要使用以下代码表示有外键关联到自身

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

Category.cs完整代码:

public partial class Category
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Category()
    {
        Product = new HashSet<Product>();
    }

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string NAME { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Product> Product { get; set; }
}

设置多对多(Many-to-many relationships)

多对多需要使用中间表方式实现,实体关系图如下:

image

Product表示产品、Order表示订单、OrderDetail表示订单详细的产品内容信息

Product.cs中有一个字段被其他表引用,所以需要声明

public partial class Product
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Product()
    {
        OrderDetail = new HashSet<OrderDetail>();
    }

    public int ProductId { get; set; }

    [Required]
    [StringLength(200)]
    public string ProductName { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<OrderDetail> OrderDetail { get; set; }
}

Order.cs中有一个字段被其他表引用,所以需要在类中声明关联关系

public partial class Order
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Order()
    {
        OrderDetail = new HashSet<OrderDetail>();
    }

    public int OrderId { get; set; }

    public DateTime OrderCreateDateTime { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<OrderDetail> OrderDetail { get; set; }
}

OrderDetail.cs中有2个字段是外键,所以需要在类中声明关联信息

[Table("OrderDetail")]
public partial class OrderDetail
{
    public int OrderDetailId { get; set; }

    public int OrderId { get; set; }

    public int ProductId { get; set; }

    public virtual Order Order { get; set; }

    public virtual Product Product { get; set; }
}

SQL Server数据类型 与 .NET数据类型对应关系

image

posted @ 2022-10-17 08:54  重庆熊猫  阅读(157)  评论(0编辑  收藏  举报