什么是Fluent API?
官方答案:EF 中内嵌的约定将 POCO 类映射到表。但是,有时您无法或不想遵守这些约定,需要将实体映射到约定指示外的其他对象,所以Fluent API和注解都是一种方法,这两种方法是用来配置EF,在映射属性时绕开约定。详情参考(https://msdn.microsoft.com/zh-cn/data/jj591617)
如何访问Fluent API?
通过自定义类(继承自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>();
EF CodeFirst系列(4)—FluentApi
FluentApi
总结
1.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
中没有发现这个。
Shadow Properties
阴影属性
This documentation is for EF Core. For EF6.x, see Entity Framework 6.
Shadow properties are properties that do not exist in your entity class. The value and state of these properties is maintained purely in the Change Tracker.
阴影属性是在你的实体类中不存在的属性。这些属性的值和状态仅在更改跟踪中进行维护。
Shadow property values can be obtained and changed through the ChangeTracker
API.
阴影属性值可以通过
ChangeTracker
API 进行获取和改变。
context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;
Shadow properties can be referenced in LINQ queries via the EF.Property
static method.
阴影属性在 LINQ 查询中可以通过
EF.Property
静态方法来引用。
var blogs = context.Blogs
.OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));
Conventions
约定
By convention, shadow properties are only created when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced. The shadow foreign key property will be named <navigation property name><principal key property name>
(the navigation on the dependent entity, which points to the principal entity, is used for the naming). If the principal key property name includes the name of the navigation property, then the name will just be <principal key property name>
. If there is no navigation property on the dependent entity, then the principal type name is used in its place.
按照约定,阴影属性仅在依赖实体类的关系被发现但无法找到外键属性时才会被创建。在这种情况下,阴影外键属性将被引入。
阴影外键属性将被命名为<navigation property name><principal key property name>
(依赖实体上的导航属性以及它指向的主要实体,是被命名使用的)。
如果主要实体键属性名称包含了导航属性的名称,那么这个名称正好是<principal key property name>
。如果在依赖实体上没有导航属性,那么主要类型名称将被在其位置中使用。
For example, the following code listing will result in a BlogId
shadow property being introduced to the Post
entity.
例如,下面的代码将导致
BlogId
阴影属性被引入到Post
实体。
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> 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; }
}
Data Annotations
数据注解
Shadow properties can not be created with data annotations.
阴影属性不可以通过数据注解来创建。
Fluent API
You can use the Fluent API to configure shadow properties. Once you have called the string overload of Property
you can chain any of the configuration calls you would for other properties.
你可以使用 Fluent API 来配置阴影属性。一旦你调用了
Property
方法的字符串重载,你将可以为想要的属性以链式方式调用任何的配置。
If the name supplied to theProperty
method matches the name of an existing property (a shadow property or one defined on the entity class), then the code will configure that existing property rather than introducing a new shadow property.
如果提供给
Property
方法的名称与已存在的属性一致(阴影属性或实体类上定义的属性),那么代码将配置已存在的属性而不是引入新的阴影属性。
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property<DateTime>("LastUpdated");
}
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}