阴影和索引器属性
阴影属性不是在 .NET 实体类中定义的,但在 EF Core 模型中是为该实体类型定义的。 这些属性的值和状态全部在更改跟踪器中维护。 当数据库中存在不应在映射的实体类型上公开的数据时,阴影属性非常有用。
索引器属性是实体类型属性,由 .NET 实体类中的 索引器器提供支持。 可以使用 .NET 类实例上的索引器访问它们。 它还允许向实体类型添加其他属性,而无需更改 CLR 类。
外键阴影属性
阴影属性最常用于外键属性,在这种用法中,当约定未找到外键属性或未显式配置外键属性时,约定会将其添加到模型中。 关系由导航属性表示,但在数据库中由外键约束强制执行,外键列的值存储在相应的阴影属性中。
属性将命名为 <navigation property name><principal key property name>
(指向主体实体的依赖实体上的导航用于命名)。 如果主体键属性名称以导航属性的名称开始,则该名称即为 <principal key property name>
。 如果具有依赖性的实体上没有导航属性,则会改用与主键或备用键属性名称串接的主体实体类型名称 <principal type name><principal key property name>
。
例如,以下代码列表将导致 BlogId
阴影属性引入 Post
实体:
internal 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; }
// Since there is no CLR property which holds the foreign
// key for this relationship, a shadow property is created.
public Blog Blog { get; set; }
}
配置阴影属性
可以使用 Fluent API 来配置阴影属性。 调用 Property<TProperty>(String) 的字符串重载后,可以链接针对其他属性的任何配置调用。 在下面的示例中,由于 Blog
没有名为 LastUpdated
的 CLR 属性,因此将创建阴影属性:
internal 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
方法的名称与现有属性(阴影属性或实体类上定义的属性)的名称匹配,则代码将配置该现有属性,而不是引入新的阴影属性。
访问阴影属性
可以通过 ChangeTracker
API 获取和更改阴影属性值:
context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;
可以通过 EF.Property
静态方法在 LINQ 查询中引用阴影属性:
var blogs = context.Blogs
.OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));
在无跟踪查询后无法访问阴影属性,因为更改跟踪器不会跟踪返回的实体。
配置索引器属性
可以使用 Fluent API 来配置索引器属性。 调用 IndexerProperty
方法后,可以链接针对其他属性的任何配置调用。 在下面的示例中,Blog
定义了一个索引器,该索引器将用于创建索引器属性。
internal class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().IndexerProperty<DateTime>("LastUpdated");
}
}
public class Blog
{
private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
public int BlogId { get; set; }
public object this[string key]
{
get => _data[key];
set => _data[key] = value;
}
}
如果提供给 IndexerProperty
方法的名称与现有索引器属性的名称匹配,则代码将配置该现有属性。 如果实体类型具有属性,该属性由实体类上的属性提供支持,则会引发异常,因为只能通过索引器访问索引器属性。
可以通过 EF.Property
静态方法(如上所示)或通过使用 CLR 索引器属性在 LINQ 查询中引用索引器属性。
属性包实体类型
仅包含索引器属性的实体类型称为属性包实体类型。 这些实体类型没有阴影属性,EF 会改为创建索引器属性。 目前仅支持将 Dictionary<string, object>
作为属性包实体类型。 必须配置为具有唯一名称的共享类型实体 类型,并且必须使用 Set
调用实现相应的 DbSet
属性。
internal 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");
});
}
}
无论使用哪种普通实体类型(包括从属实体类型),都可以使用属性包实体类型。 但是,它们有一些限制:
- 它们不能具有阴影属性。
- 不支持索引器导航
- 不支持继承
- 某些关系模型构建 API 缺少共享类型实体类型的重载
- 其他类型不能标记为属性包