博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
01-EF Core笔记之创建模型
阅读量:5054 次
发布时间:2019-06-12

本文共 9832 字,大约阅读时间需要 32 分钟。

使用EF Core的第一步是创建数据模型,模型建的好,下班走的早。EF Core本身已经设置了一系列约定来帮我们快速的创建模型,例如表名、主键字段等,毕竟约定大于配置嘛。如果你想改变默认值,很简单,EF Core提供了Fluent API或Data Annotations两种方式允许我们定制数据模型。

Fluent API 与 Data Annotations

FluentAPI方式和Data Annotations方式,FluentAPI是通过代码语句配置的,Data Annotations是通过特性标注配置的,FluentAPI的方式更加灵活,实现的功能也更多。优先级为:FluentAPI>Data Annotations>Conventions。

数据标注方式比较简单,在类或字段上添加特性标注即可,对实体类型有一定的入侵。

FluentAPI方式通过在OnModelCreating方法中添加代码逻辑来完成,也可以通过实现IEntityTypeConfiguration<T>类来完成,方式灵活,更能更加强大。

OnModelCreating方式:

modelBuilder.Entity
() .Property(m => m.RoleName) .IsRequired();

IEntityTypeConfiguration<T>方式:

先定义IEntityTypeConfiguration<T>的实现:

public class BookConfigration : IEntityTypeConfiguration
{ public void Configure(EntityTypeBuilder
builder) { builder.HasKey(c => c.Id); builder.Property(c => c.Name) .HasMaxLength(100) .IsRequired(); }}

然后再OnModelCreating中添加调用:

//加载单个ConfigurationmodelBuilder.ApplyConfiguration(new BookConfigration());//加载程序集中所有ConfigurationmodelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);

主键、备用键

主键与数据库概念相一致,表示作为数据行的唯一标识;备用键是与主键相对应的一个概念,备用键字段的值可以唯一标识一条数据,它对应数据库的唯一约束。

数据标识方式只能配置主键,使用Key特性,备用键只能通过FluentAPI进行配置。

FluentAPI方式配置的代码如下:

modelBuilder.Entity
() .HasKey(c=>c.Id) //主键 .HasAlternateKey(c => c.LicensePlate); //备用键

备用键可以是组合键,通过FluentAPI配置如下:

modelBuilder.Entity
() .HasAlternateKey(c => new { c.State, c.LicensePlate }); //组合备用键

必填和选填

映射到数据库的必填和可空,在约定情况下,CLR中可为null的属性将被映射为数据库可空字段,不能为null的属性映射为数据库的必填字段。注意:如果CLR中属性不能为null,则无论如何配置都将为必填。

也就是说,如果能为null,则默认都是可空字段,因此在配置时,只需要配置是否为必填即可。

数据标注方式使用Required特性进行标注。

FluentAPI方式代码如下:

modelBuilder.Entity
() .Property(b => b.Url) .IsRequired();

最大长度

最大长度设置了数据库字段的长度,针对string类型、byte[]类型有效,默认情况下,EF将控制权交给数据库提供程序来决定。

数据标注方式使用MaxLength(length)特性进行标注

FluentAPI方式代码如下:

builder.Property(c => c.Name)    .HasMaxLength(100)    .IsRequired();

排除/包含属性或类型

默认情况下,如果你的类型中包含一个字段,那么EF Core都会将它映射到数据库中,导航属性亦是如此。如果不想映射到数据库,需要进行配置。

数据标注方式,使用NotMapped特性进行标注;

FluentAPI方式使用Ignore方法,代码如下:

//忽略类型modelBuilder.Ignore
();//忽略属性modelBuilder.Entity
() .Ignore(b => b.LoadedFromDatabase);

如果一个属性或类型不在实体中,但是又想包含在数据库映射中时,我们只能通过Fluent API进行配置:

//包含类型modelBuilder.Entity
(); //包含属性,又叫做阴影属性,它会被映射到数据库中modelBuilder.Entity
() .Property
("LastUpdated");

阴影属性

阴影属性指的是在实体中未定义的属性,而在EF Core中模型中为该实体类型定义的属性,这些类型只能通过变更跟踪器进行维护。

阴影属性的定义:

modelBuilder.Entity
().Property
("LastUpdated");

为阴影属性赋值:

context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;

查询时使用阴影属性:

var blogs = context.Blogs    .OrderBy(b => EF.Property
(b, "LastUpdated"));

索引

索引是用来提高查询效率的,在EF Core中,索引的定义仅支持FluentAPI方式。

FluentAPI方式代码:

modelBuilder.Entity
() .HasIndex(b => b.Url);

可以配合唯一约束创建索引:

modelBuilder.Entity
() .HasIndex(b => b.Url) .IsUnique();

EF支持复合索引:

modelBuilder.Entity
() .HasIndex(p => new { p.FirstName, p.LastName });

并发控制

EF Core支持乐观的并发控制,何谓乐观的并发控制呢?原理大致是数据库中每行数据包含一个并发令牌字段,对改行数据的更新都会出发令牌的改变,在发生并行更新时,系统会判断令牌是否匹配,如果不匹配则认为数据已发生变更,此时会抛出异常,造成更新失败。使用乐观的并发控制可提高数据库性能。

按照约定,EF Core不会设置任何并发控制的令牌字段,但是我们可以通过Fluent API或数据标注进行配置。

数据标注使用ConcurrencyCheck特性标注。除此之外,将数据库字段标记为Timestamp,则会被认为是RowVersion,也能起到并发控制的功能。

public class Blog{    public int BlogId { get; set; }    [ConcurrencyCheck]    public string Url { get; set; }        [Timestamp]    public byte[] Timestamp { get; set; }}

FluentAPI 方式代码如下:

//并发控制令牌modelBuilder.Entity
() .Property(p => p.LastName) .IsConcurrencyToken();//行版本号modelBuilder.Entity
() .Property(p => p.Timestamp) .IsRowVersion();

实体之间的关系

实体之间的关系,可以参照数据库设计的关系来理解。EF是实体框架,它的实体会映射到关系型数据库中。所以通过关系型数据库的表之间的关系更容易理解实体的关系。

在数据库中,数据表之间的关系可以分为一对一、一对多、多对多三种,在实体之间同样有这三种关系,但是EF Core仅支持一对一、一对多关系,如果要实现多对多关系,则需要通过关系实体进行关联。

一对一的关系

以下面的实体关系为例:

public class Blog{    public int BlogId { get; set; }    public string Url { get; set; }    public BlogImage BlogImage { get; set; }}public class BlogImage{    public int BlogImageId { get; set; }    public byte[] Image { get; set; }    public string Caption { get; set; }    public int BlogForeignKey { get; set; }    public Blog Blog { get; set; }}

每一个Blog对应一个BlogImage,通过Blog可以加载到对应的BlogImage对象,对应的数据库配置如下:

protected override void OnModelCreating(ModelBuilder modelBuilder){    modelBuilder.Entity
() .HasOne(p => p.BlogImage) .WithOne(i => i.Blog) .HasForeignKey
(b => b.BlogForeignKey);}

一对多的关系

以下面的实体对象为例:

public class Blog{    public int BlogId { get; set; }    public string Url { get; set; }    public List
Posts { get; set; }}public class Post{ public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; }}

每个Blog对应多个Post,而每个Post对应一个Blog,对应的数据库配置如下:

protected override void OnModelCreating(ModelBuilder modelBuilder){    modelBuilder.Entity
() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .IsRequired();}

多对多的关系

多对多的关系需要我们定义一个关系表来完成。例如下面的实体对象:

public class Post{    public int PostId { get; set; }    public string Title { get; set; }    public string Content { get; set; }    public List
PostTags { get; set; }}public class Tag{ public string TagId { get; set; } public List
PostTags { get; set; }}public class PostTag{ public int PostId { get; set; } public Post Post { get; set; } public string TagId { get; set; } public Tag Tag { get; set; }}

Blog和Tag是多对多的关系,显然无论在Blog或Tag中定义外键都不合适,此时就需要一张关系表来进行关联,这张表就是BlogTag表。对应的关系配置如下:

protected override void OnModelCreating(ModelBuilder modelBuilder){    modelBuilder.Entity
() .HasKey(pt => new { pt.PostId, pt.TagId }); modelBuilder.Entity
() .HasOne(pt => pt.Post) .WithMany(p => p.PostTags) .HasForeignKey(pt => pt.PostId); modelBuilder.Entity
() .HasOne(pt => pt.Tag) .WithMany(t => t.PostTags) .HasForeignKey(pt => pt.TagId);}

生成的值

这个功能我没有试验成功。按照官方文档,定义如下实体:

public class Book{    [Key]    public Guid Id { get; set; }    [MaxLength(100)]    public string Name { get; set; }    public decimal Price { get; set; }    public DateTime CreateTime { get; set; }}

然后定义DateTime值生成器:

public class DateTimeGenerator : ValueGenerator
{ public override bool GeneratesTemporaryValues => true; public override DateTime Next(EntityEntry entry) => DateTime.Now;}

最后在FluentAPI中进行配置:

builder.Property(c => c.CreateTime)    .HasValueGenerator
() .ValueGeneratedOnAddOrUpdate();

按照我的理解应该可以在添加和更新时设置CreateTime的值,并自动保存到数据库,但是值仅在Context中生成,无法保存到数据库中。或许是我理解的不对,后续再进行研究。

继承

关于继承关系如何在数据库中呈现,目前有三种常见的模式:

  • TPH(table-per-hierarchy):一张表存放基类和子类的所有列,使用discriminator列区分类型,目前EF Core仅支持该模式
  • TPT(table-per-type ):基类和子类不在同一个表中,子类对应的表中仅包含基类表的主键和基类扩展的字段,目前EF Core不支持该模式
  • TPC(table-per-concrete-type):基类和子类不在同一个表中,子类中包含基类的所有字段,目前EF Core不支持该模式

EF Core仅支持TPH模式,基类和子类数据将存储在同一个表中。当发现有继承关系时,EF Core会自动维护一个名为Discriminator的阴影属性,我们可以设置该字段的属性:

modelBuilder.Entity
() .Property("Discriminator") .HasMaxLength(200);

EF Core允许我们通过FluentAPI的方式自定义鉴别器的列名和每个类对应的值:

modelBuilder.Entity
() .HasDiscriminator
("blog_type") .HasValue
("blog_base") .HasValue
("blog_rss");

查询类型

查询类型很有用,EF Core不会对它进行跟踪,也不允许新增、修改和删除操作,但是在映射到视图、查询对象、Sql语句查询、只读库的表等情况下用到。

例如创建视图:

db.Database.ExecuteSqlCommand(    @"CREATE VIEW View_BlogPostCounts AS         SELECT b.Name, Count(p.PostId) as PostCount         FROM Blogs b        JOIN Posts p on p.BlogId = b.BlogId        GROUP BY b.Name");

对应的查询视图:

public class BlogPostsCount{    public string BlogName { get; set; }    public int PostCount { get; set; }}

使用FluentAPI配置查询视图:

modelBuilder    .Query
().ToView("View_BlogPostCounts") .Property(v => v.BlogName).HasColumnName("Name");

值转换

值转换允许在写入或读取数据时,将数据进行转换(既可以是同类型转换,例如字符串加密解密,也可以是不同类型转换,例如枚举转换为int或string等)。

这里介绍两个概念

  • ModelClrType:模型实体的类型
  • ProviderClrType:数据库提供程序支持的类型

举个例子,string类型,对应数据库提供程序也是string类型,而枚举类型,对数据库提供程序来说没有与它对应的类型,则需要进行转换,至于如何转换、转换成什么类型,则有值转换器(Value Converter)进行处理。

值转换器包含两个Func表达式,用以提供ModelClrType和ProviderClrType的互相转换,例如:

protected override void OnModelCreating(ModelBuilder modelBuilder){    modelBuilder        .Entity
() .Property(e => e.Mount) .HasConversion( v => v.ToString(), v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));}

该示例代码将的值转化器提供了枚举类型到字符串的互转。这里只是为了演示,真实场景中,EF Core已经提供了枚举到字符串的转换器,我们只需要直接使用即可。

除了使用Func表达式,我们还可以构造值转换器实例,例如:

var converter = new ValueConverter
( v => v.ToString(), v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));modelBuilder .Entity
() .Property(e => e.Mount) .HasConversion(converter);

EF Core已经内置了常用的值转换器,例如字符串和枚举的转换器,我们可以直接使用:

var converter = new EnumToStringConverter
();modelBuilder .Entity
() .Property(e => e.Mount) .HasConversion(converter);

所有内置的值转换器都是无状态(stateless)的,所以只需要实例化一次,并在多个模型中进行使用。

值转换器还有另外一个用法,即无需实例化转换器,只需要告诉EF Core需要使用的转换器类型即可,例如:

modelBuilder    .Entity
() .Property(e => e.Mount) .HasConversion
();

值转换器的一些限制:

  • null值无法进行转换
  • 到目前位置还不支持一个字段到多列的转换
  • 会影响构造查询参数,如果造成了影响将会生成警告日志

实体构造函数

EF Core支持实体具有有参的构造函数,默认情况下,EF Core使用无参构造函数来实例化实体对象,如果发现实体类型具有有参的构造函数,则优先使用有参的构造函数。

使用有参构造函数需要注意:

  • 参数名应与属性的名字、类型相匹配
  • 如果参数中不具有所有字段,则在调用构造函数完成后,对未包含字段进行赋值
  • 使用懒加载时,构造函数需要能够被代理类访问到,因此需要构造函数为public或protected
  • 暂不支持在构造函数中使用导航属性

使用构造函数时,比较好玩的是支持依赖注入,我们可以在构造函数中注入DbContextIEntityTypeILazyLoaderAction<object, string> 这几个类型。

以上便是常用的构建模型的知识点,更多内容在用到时再进行学习。

转载于:https://www.cnblogs.com/youring2/p/11182780.html

你可能感兴趣的文章
linux挂载磁盘以及扩容主分区
查看>>
[转]Python模块学习:threading 多线程控制和处理
查看>>
PHP链接sqlserver出现中文乱码
查看>>
[计算机]Alan Perlis人物简介
查看>>
Android-----第三方 ImageLoader 的简单配置和使用
查看>>
零基础入门Python3-详解分支
查看>>
js数组去重
查看>>
A. E-mail
查看>>
C# 反射机制以及方法
查看>>
C# Socket服务端与客户端通信(包含大文件的断点传输)
查看>>
理解SQL SERVER中的逻辑读,预读和物理读
查看>>
输入N,打印如图所看到的的三角形(例:N=3,N=4,N=5)1&lt;=N&lt;=26
查看>>
发展城市 BZOJ 3700
查看>>
Yii Framework处理网站前后台文件的方法
查看>>
jQuery事件委托
查看>>
移动端元素拖拽事件
查看>>
HDOJ:1058
查看>>
swiper隐藏再显示出现点击不了情况
查看>>
js input radio点击事件
查看>>
okhttp post form表单
查看>>