【WPF】 FluentAPI映射实体和数据库表 VS Attribute特性映射实体和数据库表
Attribute特性映射实体和数据库表
通过自定义类(继承自DbContext )的OnModelCreating方法访问。
属性映射
主要配置:主键、数值长度、配置为必须、不映射,外键等
配置主键:
modelBuilder.Entity<ClassA>().HasKey(t => t.ID); //配置ClassA的ID属性为主键
配置联合主键:
modelBuilder.Entity<ClassA>().HasKey(t => new { t.ID, t.Name }); //配置ClassA的ID和Name为主键
设置数据非数据库生成:
modelBuilder.Entity<ClassA>().Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); //ClassA的Id属性不用数据库控制生成
设置字段最大长度:
modelBuilder.Entity<ClassA>().Property(t => t.Name).HasMaxLength(100); //设置ClassA类的Name属性的最大长度为100,如果值长度100,会抛出 DbEntityValidationException异常
设置字段为必需:
modelBuilder.Entity<ClassA>().Property(t =>t.Id).IsRequired(); //设置ClassA类的Id属性为必需
属性不映射到数据库:
modelBuilder.Entity<ClassA>().Ignore(t => t.A); //调过ClassA类的A属性,让之不映射到数据库中
将属性映射到数据库中特定列名:
modelBuilder.Entity<ClassA>()
.Property(t => t.A)
.HasColumnName("A_a"); //将类ClassA的属性A映射到数据库中对应列名A_a
类中不指定外键,但在数据库中指定外键名:
modelBuilder.Entity<Staff>()
.HasRequired(c => c.Department)
.WithMany(t => t.Staffs)
.Map(m => m.MapKey("DepartmentID")); //指定员工表中DepartmentID为Staff到Department的外键
指定属性映射的字段为Unicode类型:
modelBuilder.Entity<ClassA>()
.Property(t => t.Name)
.IsUnicode(true);
设置属性映射的列的类型:
modelBuilder.Entity<Department>()
.Property(p => p.Name)
.HasColumnType("varchar"); //设置列为varchar类型
设置复杂类型的属性(何为复杂类型? 没指定主键的类型):
modelBuilder.ComplexType<Details>()
.Property(t => t.Location)
.HasMaxLength(20);
modelBuilder.Entity<OnsiteCourse>()
.Property(t => t.Details.Location)
.HasMaxLength(20);
显示设定为复杂类型:
modelBuilder.ComplexType<ClassA>();
将属性配置为用作乐观并发令牌:
方法1、用 ConcurrencyCheck 特性或 IsConcurrencyToken 方法
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsConcurrencyToken();
方法2、IsRowVersion
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsRowVersion();
忽略类型,不映射到数据库中:
modelBuilder.Ignore<OnlineCourse>();
FluentAPI映射实体和数据库表
EF中的FluentApi
作用是通过配置领域类来覆盖默认的约定。在EF中,我们通过DbModelBuilder
类来使用FluentApi
,它的功能比数据注释属性更强大。
使用FluentApi
时,我们在context
类的OnModelCreating()
方法中重写配置项,一个例子:
public class SchoolContext: DbContext { public DbSet<Student> Students { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //Write Fluent API configurations here } }
我们可以把FluentApi
和数据注释属性一起使用,当FluentApi
和数据注释属性都配置了同一个项时,采用FluentApi
中的配置。
在EF6中FluentApi可以配置领域类的以下几个方面,下表也列出了一些常用的FluentApi方法及其作用:
配置 | Fluent API 方法 | 作用 |
---|---|---|
架构相关配置 | HasDefaultSchema() | 数据库的默认架构 |
ComplexType() | 把一个类配置为复杂类型 | |
实体相关配置 | HasIndex() | 实体的的索引 |
HasKey() | 实体的主键(可其实现复合主键,[Key]在EF core中不能实现复合主键) | |
HasMany() | 1对多的或者 多对多关系 | |
HasOptional() | 一个可选的关系,这样配置会在数据库中生成一个可空的外键 | |
HasRequired() | 一个必有的关系,这样配置会在数据库中生成一个不能为空的外键 | |
Ignore() | 实体或者实体的属性不映射到数据库 | |
Map() | 设置一些优先的配置 | |
MapToStoredProcedures() | 实体的CUD操作使用存储过程 | |
ToTable() | 为实体设置表名 | |
属性相关配置 | HasColumnAnnotation() | 给属性设置注释 |
IsRequired() | 在调用SaveChanges()方法时,属性不能为空 | |
IsOptional() | 可选的,在数据库生成可空的列 | |
HasParameterName() | 配置用于该属性的存储过程的参数名 | |
HasDatabaseGeneratedOption() | 配置数据库中对应列的值怎样生成的,如计算,自增等 | |
HasColumnOrder() | 配置数据库中对应列的排列顺序 | |
HasColumnType() | 配置数据库中对应列的数据类型 | |
HasColumnName() | 配置数据库中对应列的列名 | |
IsConcurrencyToken() | 配置数据库中对应列用于乐观并发检测 |
2.实体相关配置
1)实体简单配置
直接上栗子:
我们新建一个EF6Demo的控制台应用程序,添加Student
和Grade
实体,以及上下文类SchoolContext
,代码如下:
//学生类 public class Student { public int StudentId { get; set; } public string StudentName { get; set; } public string StudentNo { get; set; } public virtual Grade Grade{get;set;} } //年级类 public class Grade { public int GradeId { get; set; } public string GradeName { get; set; } public virtual ICollection<Student> Students { get; set; } } //上下文类 public class SchoolContext:DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet <Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("Admin");//添加默认架构名 modelBuilder.Entity<Student>().ToTable("StudentInfo"); modelBuilder.Entity<Grade>().ToTable("GradeInfo","NewAdmin");//设置表名和架构 } }
在Main
函数中执行代码:
class Program { static void Main(string[] args) { using (SchoolContext context=new SchoolContext()) { context.Students.Add(new Student() { StudentId = 1, StudentName = "Jack" }); context.SaveChanges(); } } }
这时在内置的SqlServer
中生成数据库,如下图所示,我们看到Student
表名为StudentInfo
,架构是Admin
;Grade
表名是GradeInfo
,架构是NewAdmin
,覆盖了默认的约定(默认表名为dbo.Students
和dbo.Grades
)
2)实体映射到多张表
有时候我们希望一个实体的属性分在两种表中,那么该怎么配置呢?还用上边的栗子,我们把学生的姓名和Id存在一张表,学号和Id放在另一张表中,代码如下:
public class SchoolContext:DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet <Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Map(m => { //配置第一张表,包含学生Id和学生姓名 m.Properties(p => new { p.StudentId, p.StudentName }); m.ToTable("StudentInfo"); }).Map(m => { //配置第二张表,包含学生Id和学生学号 m.Properties(p => new { p.StudentId, p.StudentNo }); m.ToTable("StudentInfo2"); }); //配置年级表名 modelBuilder.Entity<Grade>().ToTable("GradeInfo"); } }
运行一下Main
函数,生成了新的数据库,如下所示:
我们看到,通过Map()
方法,我们把Student
实体的属性被分在了两个表中。modelBuilder.Entity<T>()
方法返回的是一个EntityTypeConfiguration<T>
类型,Map()
方法的参数是一个委托类型,委托的输入参数是EntityMappingConfiguration
的实例。我们可以自定义一个委托来实现配置,下边的代码运行后生成的数据库和和上边一样:
public class SchoolContext : DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet<Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //先定义一个Action委托备用,委托的输入参数是一个实体映射配置(EntityMappingConfiguration)的实例 Action<EntityMappingConfiguration<Student>> studentMapping = m => { m.Properties(p => new { p.StudentId, p.StudentNo }); m.ToTable("StudentInfo2"); }; modelBuilder.Entity<Student>() //第一张表Map()方法参数是delegate形式委托 .Map(delegate (EntityMappingConfiguration<Student> studentConfig) { //map参数是lambda表达式 studentConfig.Properties(p => new { p.StudentId, p.StudentName }); studentConfig.ToTable("StudentInfo"); }) //第二张表Map()方法参数是Action委托 .Map(studentMapping); modelBuilder.Entity<Grade>().ToTable("GradeInfo"); } }
3.属性相关配置
属性的配置比较简单,这里简单总结了主键,列基本属性,是否可空,数据长度,高并发的配置。
一个栗子:
public class Student { public int StudentKey { get; set; }//主键 public string StudentName { get; set; }//姓名 public DateTime DateOfBirth { get; set; }//生日 public byte[] Photo { get; set; }//照片 public decimal Height { get; set; }//身高 public float Weight { get; set; }//体重 public Grade Grade{ get; set; }//年级 } public class Grade { public int GradeKey { get; set; }//主键 public string GradeName { get; set; }//年级名 public ICollection<Student> Students { get; set; } }
使用FluentApi
对领域类做了以下配置:
public class SchoolContext : DbContext { public SchoolContext() : base() { } public DbSet<Student> Students { get; set; } public DbSet<Grade> Grades { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //设置默认架构 modelBuilder.HasDefaultSchema("Admin"); //设置主键 modelBuilder.Entity<Student>().HasKey<int>(s => s.StudentKey); //设置不映射的属性 modelBuilder.Entity<Student>().Ignore(s => s.Height); //设置DateOfBirth modelBuilder.Entity<Student>().Property(p => p.DateOfBirth) .HasColumnName("birthday") //列名为birthday .HasColumnType("datetime2") //数据类型是datetime类型 .HasColumnOrder(3) //顺序编号是3 .IsOptional(); //可以为null //设置姓名 modelBuilder.Entity<Student>().Property(s => s.StudentName) .HasMaxLength(20) //最长20 .IsRequired() //不能为null .IsConcurrencyToken(); //用于乐观并发检测,delete或者update时,这个属性添加到where上判断是否并发 } }
执行程序后生成的数据库如下:
.Net Core 之 Entity Framework Core – Code Frist 数据注解及Fluent API
只有学习,内心才能踏实。
今天来总结一下,EF Core 中Code Frist 的数据注解及 Fluent API。其实这个次总结是为了巩固一下以前的知识,如果比较懂EF ,这部知识可以快速过。但是!但是!EF Core 和 EF 还是有很大区别的,比如说:默认值和索引等用数据注解的方式在.Net Core 无效,只能用Fluent API
. 本文记录两种方式来创建模型,分别是数据注解方式和Fluent API
在这里有很多东西可以扩展,但是由于本人能力有限,无法求证。文末会总结遗留的问题,有大神看到可以帮忙回答一下。EF Core 的版本为2.1
1.表映射 [Table(string name, Properties:[Schema = string])
[Table("DataTest", Schema = "admin")] //注释:[Table(string name, Properties:[Schema = string]) public class DataAnnotationsAttribute { [Key] [Column(Order = 1)] public int Id { get; set; } }
2.列映射 [Column (string name, Properties:[Order = int],[TypeName = string])
[Table("DataTest", Schema = "admin")] //注释:[Table(string name, Properties:[Schema = string]) public class DataAnnotationsAttribute { [Key] [Column(Order = 1)] public int Id { get; set; } [Column(Order = 3, TypeName = "varchar(50)")] public string Name { get; set; } [Column("FullName", Order = 2, TypeName = "varchar(60)")] // [Column (string name, Properties:[Order = int],[TypeName = string]) public string FullName { get; set; } // [ForeignKey] 参考 UserRole [DefaultValue(3)] public int DefaultValue { get; set; } }
Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<DataAnnotationsAttribute>(eb => { eb.Property(b => b.Name).HasColumnType("varchar(50)"); eb.Property(b => b.FullName).HasColumnType("varchar(60)"); }); }
数据类型:这点,有时间的话,可以从网上查看一下啊,EF 的数据类型 对应数据库的数据类型
3. 主键 [key]
数据注解方式:
[Key] [Column(Order = 1)] public int Id { get; set; }
Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasKey(b => b.BlogId).HasName("PrimaryKey_BlogId"); }
数据库图:略
4. 复合主键
Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>().HasKey(c => new { c.LicensePlate, c.State }); }
5. 计算列(列计算或拼接):数据注解中无法实现,只能在Fluent API中实现
class MyContext : DbContext { public DbSet<Person> People { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Person>() .Property(p => p.DisplayName) .HasComputedColumnSql("[LastName] + ', ' + [FirstName]"); } } public class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string DisplayName { get; set; } }
数据库图:略
6.序列:数据注解中无法实现,只能在Fluent API中实现
你可以配置它如其实值从1000 开始:StartsAt(1000);
每次增5:IncrementsBy(5)
也可以从模型中取值,比如下面 NEXT VALUE FOR shared.OrderNumbers
class MyContext : DbContext { public DbSet<Order> Orders { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") .StartsAt(1000) .IncrementsBy(5); modelBuilder.Entity<Order>() .Property(o => o.OrderNo) .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers"); } } public class Order { public int OrderId { get; set; } public int OrderNo { get; set; } public string Url { get; set; } }
7.默认值:数据注解中无法实现(跟EF 不一样,即使提供,但没有效果),只能在Fluent API
中实现
字段上加[DefaultValue(3)]
是没有效果的
class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Rating) .HasDefaultValue(3); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public int Rating { get; set; } }
8.索引:数据注解中无法实现(跟EF 不一样,即使提供,但没有效果),只能在Fluent API
中实现
是否唯一:IsUnique()
IsClustered
在.net core 没有发现本人不敢确认有没有
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 唯一索引 modelBuilder.Entity<Blog>().HasIndex(b => b.Url).IsUnique(); // 非唯一 modelBuilder.Entity<Blog>().HasIndex(b => new { b.RegistrationNumber1, b.RegistrationNumber2 }); }
9:外键约束
一对多:
// 实体 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; } } // Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>() .HasOne<Grade>(s => s.Grade) .WithMany(g => g.Students) .HasForeignKey(s => s.CurrentGradeId); } public DbSet<Grade> Grades { get; set; } public DbSet<Student> Students { get; set; }
一对一:
// 实体 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 string City { get; set; } public string State { get; set; } public string Country { get; set; } public int AddressOfStudentId { get; set; } public Student Student { get; set; } } //Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>() .HasOne<StudentAddress>(s => s.Address) .WithOne(ad => ad.Student) .HasForeignKey<StudentAddress>(ad => ad.AddressOfStudentId); } public DbSet<Student> Students { get; set; } public DbSet<StudentAddress> StudentAddresses { 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 class Student { public int StudentId { get; set; } public string Name { get; set; } public IList<StudentCourse> StudentCourses { get; set; } } public class Course { public int CourseId { get; set; } public string CourseName { get; set; } public string Description { get; set; } public IList<StudentCourse> StudentCourses { get; set; } } // Fluent API modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.SId, sc.CId }); 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);
10:排除实体和属性–NotMapped
// 实体 //排除entity public class Blog { public int BlogId { get; set; } public string Url { get; set; } public BlogMetadata Metadata { get; set; } } [NotMapped] public class BlogMetadata { public DateTime LoadedFromDatabase { get; set; } } //排除属性 public class Blog { public int BlogId { get; set; } public string Url { get; set; } [NotMapped] public DateTime LoadedFromDatabase { get; set; } } //Fluent API // 排除entity protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BlogMetadata>(); } // 排除属性 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Ignore(b => b.LoadedFromDatabase); }
11:最大长度–MaxLength
(Fluent API
中没有MinLength
//实体 public class Blog { public int BlogId { get; set; } [MaxLength(500)] public string Url { get; set; } } //Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasMaxLength(500); }
12:防并发:Timestamp
与ConcurrencyCheck
//实体 public class Person { public int PersonId { get; set; } [ConcurrencyCheck] public string LastName { get; set; } //10 .Timestamp 时间戳必须是byte[]类型的,防止并发,EF 的并发都是乐观的。例如同时改一条数据,别人在你之前提交 [Timestamp] public byte[] Timestamp { get; set; } } //Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(p => p.Timestamp) .IsRowVersion(); modelBuilder.Entity<Blog>().Property(b => b.Timestamp).IsRowVersion(); }
13: 值转换 (.net core 2.1 新增)
和14 一起举例在进行数据库迁移时,EF会往数据库中插入一些数据
14:Data Seeding
(.net core 2.1 新增)这个是用来初始化数据用。
// 值类型转换 public class Blog { public int BlogId { get; set; } public string Url { get; set; } public EquineBeast Mount { get; set; } } public enum EquineBeast { Donkey, Mule, Horse, Unicorn } // fluent api protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder .Entity<Rider>() .Property(e => e.Mount) .HasConversion( v => v.ToString(), v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v)); // DATA SEEDING modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" }); }
数据迁移后的结果是:
除了基本的枚举转字符串以外,EF Core还提供如下的转换类:
上面的这些对象的使用方式如下:
类名称 | 说明 |
---|---|
BoolToZeroOneConverter | 将布尔值转换为0或1 |
BoolToStringConverter | 将布尔值转换为字符串(Y或N) |
BoolToTwoValuesConverter | 将布尔值转换为指定的两个值(没搞明白干嘛用的) |
BytesToStringConverter | 将字节数组转换为Base64编码的字符串 |
CastingConverter | 从一种类型转换到另一种类型(可以被C#互相转换的类型) |
CharToStringConverter | char转为string |
DateTimeOffsetToBinaryConverter | DateTimeOffset转为二进制的64位的值 |
DateTimeOffsetToBytesConverter | DateTimeOffset转为字节数组 |
DateTimeOffsetToStringConverter | DateTimeOffset转为字符串 |
DateTimeToBinaryConverter | DateTime转为带有DateTimeKind的64位的值 |
DateTimeToStringConverter | DateTime转为字符串 |
DateTimeToTicksConverter | DateTime转为ticks |
EnumToNumberConverter | 枚举转数字 |
EnumToStringConverter | 枚举转字符串 |
GuidToBytesConverter | Guid转字节数组 |
GuidToStringConverter | Guid转字符串 |
NumberToBytesConverter | 数字转字节数组 |
NumberToStringConverter | 数字转字符串 |
StringToBytesConverter | 字符串转字节数组 |
TimeSpanToStringConverter | TimeSpan转字符串 |
TimeSpanToTicksConverter | TimeSpan转ticks |
上面的这些对象的使用方式如下:
var converter = new EnumToStringConverter<Person>(); builder.Property(p => p.Gender).HasConversion(converter);
除了这种方式外,EF Core也支持直接指定类型,如:
builder.Property(p => p.Gender).HasConversion(string);
需要注意的是,不能将null
进行转换,一个属性只能对应一个列做转换。
15:查询类型-Query Types
(.net core 2.1 新增)
ToView
等
这就不多写了,我感觉比较重要,怕误导大家,所以粘出官方文档连接
https://docs.microsoft.com/zh-cn/ef/core/modeling/query-types
16:实体构造函数 (.net core 2.1 新增)
https://docs.microsoft.com/zh-cn/ef/core/modeling/constructors
17:固有实体类型(.net core 2.0 新增)
https://docs.microsoft.com/zh-cn/ef/core/modeling/owned-entities
总结
1:文章写到最后,还是没有全部坚持下来,比如说第15,16,17 没有写例子。其实我感觉他们都还挺重要。有时间了,我还会把这些内容补回来。
2:.net ef core
跟 .net ef
还是有很多差别的地方,有很多无法使用数据注解的方式解决,只能使用 fluent api
。但是例如StringLength
注解,加上就是非空的。但是在fluent api
中没有发现这个。