1.初始准备
开发软件:VisualStudio2022,EFCore6.0
.net Core版本:.Net6.0
数据库:mysql8.0
数据库管理软件:Navicat
按照前面的教程,安装相关的Nuget包,重新设计以下几个实体类
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string City { get; set; }
//导航属性
public int PersonId { get; set; }
public Person Person { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Sex { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
}
public class Student:Person
{
public string Hobby { get; set; }
}
public class Teacher:Person
{
public string Title { get; set; }
}
新建EFCoreLearnDbContext类,代码如下:
public class EFLearnDbContext:DbContext
{
public EFLearnDbContext()
{
this.Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=172.10.10.85;database=EFCoreLearn8;user=root;password=um-robotics", Microsoft.EntityFrameworkCore.ServerVersion.Parse("8.0.28-mysql"));
// optionsBuilder.UseLazyLoadingProxies();
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region Student基本配置
modelBuilder.Entity<Person>(entity =>
{
entity.Property(x => x.Id).ValueGeneratedOnAdd();//设置Id自增
entity.Property(x => x.Name).HasMaxLength(50).IsUnicode().IsRequired();//设置姓名最大长度为50,字符为unicode,不能为空
entity.Property(x => x.Sex).HasMaxLength(5).IsUnicode().IsRequired();//设置性别最大长度为5 字符为Unicode,不能为空
entity.HasOne(x => x.Address).WithOne(x => x.Person).HasForeignKey<Address>(ad => ad.PersonId);//一对一只需要配置一个类就行了,
// entity.Navigation(x => x.Courses).AutoInclude();
// entity.Navigation(x => x.Address).AutoInclude();
});
#endregion
#region Teacher配置
modelBuilder.Entity<Teacher>(entity =>
{
entity.Property(x => x.Name).HasMaxLength(50).IsUnicode();
entity.Property(x => x.Title).HasMaxLength(50).IsUnicode();
});
#endregion
#region 配置地址
modelBuilder.Entity<Address>(entity =>
{
entity.Property(x => x.City).HasMaxLength(100).IsRequired().IsUnicode();
entity.Property(x => x.Street).HasMaxLength(500).IsRequired().IsUnicode();
});
#endregion
base.OnModelCreating(modelBuilder);
}
public DbSet<Person> People { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Address> Addresses { get; set; }
}
在Program里添加下面的代码
using EFCoreLearn8;
using EFCoreLearn8.Models;
using (EFLearnDbContext dbContext = new EFLearnDbContext())
{
#region 【1】添加数据
Address Address1 = new Address()
{
AddressId = 37001,
Street = "北京朝阳区",
City = "北京",
};
Address Address2 = new Address()
{
AddressId = 37002,
Street = "上海徐汇区",
City = "上海",
};
Address Address3 = new Address()
{
AddressId = 37003,
Street = "广州白云区",
City = "广州",
};
Student student1 = new Student()
{
Id = 2022001,
Name = "王二小",
Age = 19,
Sex = "男",
Address = Address1,
Hobby = "打篮球",
};
Student student2 = new Student()
{
Id = 2022002,
Name = "张小五",
Age = 20,
Sex = "男",
Address = Address2,
Hobby = "打乒乓球",
};
Student student3 = new Student()
{
Id = 2022003,
Name = "刘小花",
Age = 20,
Sex = "女",
Address = Address3,
Hobby = "弹钢琴",
};
dbContext.Students.AddRange(student1, student2, student3);
Teacher teacher1 = new Teacher() { Id = 10001, Name = "张教授", Title = "教授", Sex="男",Age=50,Address=Address1};
Teacher teacher2 = new Teacher() { Id = 10002, Name = "王讲师", Title = "讲师",Sex="男", Age=48 ,Address=Address3};
dbContext.Teachers.AddRange(teacher1, teacher2);
dbContext.SaveChanges();
#endregion
}
2.存储继承关系
上述代码中,Student 和Teacher是继承Person类来的,运行准备好的代码后,翻开数据库查看如下,表只有两个People和Address
其中可以发现Discriminator的这个字段我们并没有设置,而是EFCore是主动给我们加上去的,这个字段的英文意思是鉴别器、辨别者,而EFCore通过这个字段来确定到底是哪个继承类。
下面我们查询下,看看EFCore是否能够准确查询数据,同时我们打开logger,看下EFCore是如何查询的。
var student= dbContext.Students.ToList();
查看logger可以知道,在查询的时候,EFCore会通过 Discriminator 作为一个筛选条件,这样就能够为我们选择正确的数据。
2.1 自由定义配置Discriminator
更改EFLearnDbContext类如下:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region Student基本配置
modelBuilder.Entity<Person>(entity =>
{
entity.HasDiscriminator<int>("person_type")
.HasValue<Person>(0)
.HasValue<Student>(1)
.HasValue<Teacher>(2);
entity.Property(x => x.Id).ValueGeneratedOnAdd();//设置Id自增
entity.Property(x => x.Name).HasMaxLength(50).IsUnicode().IsRequired();//设置姓名最大长度为50,字符为unicode,不能为空
entity.Property(x => x.Sex).HasMaxLength(5).IsUnicode().IsRequired();//设置性别最大长度为5 字符为Unicode,不能为空
entity.HasOne(x => x.Address).WithOne(x => x.Person).HasForeignKey<Address>(ad => ad.PersonId);//一对一只需要配置一个类就行了,
// entity.Navigation(x => x.Courses).AutoInclude();
// entity.Navigation(x => x.Address).AutoInclude();
});
然后删除DbSet 中的Student 和Teacher
public DbSet<Person> People { get; set; }
public DbSet<Address> Addresses { get; set; }
最后删除数据库。
在program类修改如下:
Address Address1 = new Address()
{
AddressId = 37001,
Street = "北京朝阳区",
City = "北京",
};
Address Address2 = new Address()
{
AddressId = 37002,
Street = "上海徐汇区",
City = "上海",
};
Address Address3 = new Address()
{
AddressId = 37003,
Street = "广州白云区",
City = "广州",
};
Student student1 = new Student()
{
Id = 2022001,
Name = "王二小",
Age = 19,
Sex = "男",
Address = Address1,
Hobby = "打篮球",
};
Student student2 = new Student()
{
Id = 2022002,
Name = "张小五",
Age = 20,
Sex = "男",
Address = Address2,
Hobby = "打乒乓球",
};
Student student3 = new Student()
{
Id = 2022003,
Name = "刘小花",
Age = 20,
Sex = "女",
Address = Address3,
Hobby = "弹钢琴",
};
dbContext.People.AddRange(student1, student2, student3);
Teacher teacher1 = new Teacher() { Id = 10001, Name = "张教授", Title = "教授", Sex = "男", Age = 50, Address = Address1 };
Teacher teacher2 = new Teacher() { Id = 10002, Name = "王讲师", Title = "讲师", Sex = "男", Age = 48, Address = Address3 };
dbContext.People.AddRange(teacher1, teacher2);
dbContext.SaveChanges();
运行代码后,查看数据库如下:
我们发现这个时候没有了Discriminator 而是我们自己定义的person_type,类型也不再是string类型,是我们定义区分的1,2数值类型。
2.2 继承相同,不同表区分
但是希望映射数据库时,不同的类型映射不同的表,使用 HasBaseType((Type)null) 从层次结构中删除实体类型。删除数据库,修改DbContext类如下
public class EFLearnDbContext:DbContext
{
public EFLearnDbContext()
{
this.Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=172.10.10.85;database=EFCoreLearn8;user=root;password=um-robotics", Microsoft.EntityFrameworkCore.ServerVersion.Parse("8.0.28-mysql"));
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region Student基本配置
modelBuilder.Entity<Student>(entity =>
{
entity.HasBaseType((Type)null).HasKey(x => x.Id);
entity.Property(x => x.Id).ValueGeneratedOnAdd();//设置Id自增
entity.Property(x => x.Name).HasMaxLength(50).IsUnicode().IsRequired();//设置姓名最大长度为50,字符为unicode,不能
entity.Property(x => x.Sex).HasMaxLength(5).IsUnicode().IsRequired();//设置性别最大长度为5 字符为Unicode,不能为空
});
modelBuilder.Entity<Teacher>(entity =>
{
entity.HasBaseType((Type)null).HasKey(x => x.Id);
entity.Property(x => x.Id).ValueGeneratedOnAdd();//设置Id自增
entity.Property(x => x.Name).HasMaxLength(50).IsUnicode().IsRequired();//设置姓名最大长度为50,字符为unicode
entity.Property(x => x.Sex).HasMaxLength(5).IsUnicode().IsRequired();//设置性别最大长度为5 字符为Unicode
});
#endregion
#region 配置地址
modelBuilder.Entity<Address>(entity =>
{
entity.Property(x => x.City).HasMaxLength(100).IsRequired().IsUnicode();
entity.Property(x => x.Street).HasMaxLength(500).IsRequired().IsUnicode();
});
#endregion
base.OnModelCreating(modelBuilder);
}
public DbSet<Student> Students { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Address> Addresses { get; set; }
}
在Program里修改数据如下:
dbContext.Students.AddRange(student1, student2, student3);
Teacher teacher1 = new Teacher() { Id = 10001, Name = "张教授", Title = "教授", Sex = "男", Age = 50, Address = Address1 };
Teacher teacher2 = new Teacher() { Id = 10002, Name = "王讲师", Title = "讲师", Sex = "男", Age = 48, Address = Address3 };
dbContext.Teachers.AddRange(teacher1, teacher2);
dbContext.SaveChanges();
打开数据库查看
3.级联操作
这里的级联操作主要指级联删除,级联操作主要是指子实体与父实体的关系已断开时自动删除该子实体,这通常称为“删除孤立项”。
EF Core 实现多种不同的删除行为,并允许配置各个关系的删除行为。 EF Core 还实现基于关系的需求为每个关系自动配置有用的默认删除行为的约定。
EFCore的删除行为在 DeleteBehavior 枚举器类型中定义,并且可以传递到 OnDelete Fluent API 来控制是主体/父实体的删除还是依赖实体/子实体关系的断开会对依赖实体/子实体产生副作用。删除主体/父实体或断开与子实体的关系时有三个 EF 可执行的操作:
- 可以删除子项/依赖项
- 子项的外键值可以设置为 null
- 子项保持不变
可选关系
对于可选关系(可以为 null 的外键),可以保存 null 外键值,从而产生以下影响:
行为名称 | 对内存中的依赖项/子项的影响 | 对数据库中依赖项/子项的影响 |
---|---|---|
Cascade | 删除实体 | 删除实体 |
ClientSetNull(默认) | 外键属性设置为null | None |
SetNull | 外键属性为null | 外键属性设置为null |
Restrict | None | None |
必选关系
对于必选关系(不可为 null 的外键),_不可以_保存 null 外键值,从而产生以下影响:
行为名称 | 对内存中的依赖项/子项的影响 | 对数据库中依赖项/子项的影响 |
---|---|---|
Cascade | 删除实体 | 删除实体 |
ClientSetNull(默认) | SaveChanges引发异常 | None |
SetNull | 引发SaveChanges异常 | 引发SaveChanges异常 |
Restrict | None | None |
在上表中,“无” 可能会造成约束冲突。 例如,如果已删除主体/子实体,但不执行任何操作来更改依赖项/子项的外键,则由于发生外键约束冲突,数据库将可能会引发 SaveChanges。
注意:
- 如果实体在没有父项时不能存在,且希望 EF 负责自动删除子项,则使用“Cascade” ,在没有父项时不能存在的实体通常使用必选关系,其中“Cascade” 是默认值。
- 如果实体可能有或可能没有父项,且希望 EF 负责为你将外键变为 null,则使用“ClientSetNull”
- 在没有父项时可以存在的实体通常使用可选关系,其中“ClientSetNull” 是默认值。 如果希望数据库即使在未加载子实体时也尝试将
null 值传播到子外键,则使用“SetNull” 但是,请注意,数据库必须支持此操作,并且如此配置数据库可能会导致其他限制,实际上这通常会使此选项不适用。 这就是SetNull不是默认值的原因。 - 如果不希望 EF Core 始终自动删除实体或自动将外键变为 null,则使用“Restrict” 。
请注意,这要求使用代码手动同步子实体及其外键值,否则将引发约束异常
恢复前面的选项,可以看到外键约束默认都是级联的。