阴影(Shadow property)和索引器(Indexer property)属性
阴影属性,是指不在.net实体类中定义、但在EF核心模型中为该实体类型定义的属性。这些属性的值和状态纯粹在更改跟踪器中维护。当数据库中存在不应该在映射实体类型上公开的数据时,阴影属性非常有用。(相当于数据库表中的一些列,不需要在实体类中映射,但是可以在EF模型中定义、追踪、赋值等)
索引器属性,是实体类型属性,由.net实体类的索引器支持。可以在类型实例上使用索引器对其进行访问。还允许向实体类型添加其他属性,而无需更改CLR类。
本节中,出现了一些和“关系”相关的概念,比如导航。关系,定义了两个实体类之间的关系(一对一、一对多、多对多),它对应着关系型数据库中的外键的概念。具体,可参考下一节内容。
1、外键阴影属性 Foreign key shadow properties
阴影属性,最常用于外键属性,其中两个实体之间的关系由数据库中的外键值表示,但使用实体类型之间的导航属性在实体类型上管理关系。
约定:当发现关系存在但是在依赖实体类中找不到外键属性时,EF就会引入一个阴影属性。
该属性将被命名 <navigation property name><principal key property name>
(用在依赖实体上的、且指向主实体的导航来命名);如果主体键属性名称包含导航属性的名称,则该名称将只是 <principal key property name>
。 如果依赖实体上没有导航属性,则会在其位置使用主体类型名称。示例中,将导致将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 string BlogUrl { get; set; }
// 由于没有CLR属性(上一行)保存此关系的外键,因此将创建一个影子属性。
public Blog Blog { get; set; } }
2、配置阴影属性
使用Fluent API配置阴影属性。示例,一旦您调用了Property的字符串重载方法,您就可以为其他属性链接任何配置调用;由于Blog没有名为LastUpdated的CLR属性,因此创建了一个阴影属性:
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; } }
如果提供给Property方法的参数名与现有属性(影子属性或实体类上定义的属性)的名称匹配,则代码将配置该现有属性,而不是引入新的影子属性。
3、访问阴影属性
可以通过 API 获取和更改影子属性值 ChangeTracker
:
context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;
可以通过静态方法在 LINQ 查询中引用影子属性 EF.Property
:
var blogs = context.Blogs .OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));
在无跟踪查询后,不能访问阴影属性,因为更改跟踪器不跟踪返回的实体。
4、配置索引器属性
您可以使用Fluent API来配置索引器属性。一旦调用了方法IndexerProperty,就可以为其他属性链接任何配置调用。在下面的示例中,在Blog类型的LastUpdated属性上定义了一个索引器,它将用于创建索引器属性:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().IndexerProperty<DateTime>("LastUpdated"); }
如果提供给方法的名称 IndexerProperty
与现有索引器属性的名称匹配,则代码将配置该现有属性。 如果实体类型具有由实体类的属性支持的属性,则会引发异常,因为索引器属性只能通过索引器访问。
5、属性包实体类型 Property bag entity types
这是EF Core 5 的新特性。
仅包含索引器属性的实体类型称为 "属性包实体类型"。 这些实体类型没有影子属性,而 EF 将创建索引器属性。 目前仅 Dictionary<string, object>
支持作为属性包实体类型。 它必须配置为具有唯一名称的共享实体类型,并且 DbSet
必须使用调用来实现相应的属性 Set
:
class MyContext : DbContext { public DbSet<Dictionary<string, object>> Blogs => Set<Dictionary<string, object>>("Blog"); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Blog", bb => { bb.Property<int>("BlogId"); bb.Property<string>("Url"); bb.Property<DateTime>("LastUpdated"); }); } }