文章目录
- 影子属性和索引器属性
- 1. 外键影子属性
- 2. 配置影子属性
- 3. 访问影子属性
- 4. 配置索引器属性
- 5. 属性包实体类型
影子属性和索引器属性
影子属性(Shadow properties),
影子属性是一种属性,它不在.NET实体类中定义,而是在EF Core模型中为该实体类型定义的。
影子属性的值和状态完全是在更改追踪器(Change Tracker)中维护的。
当数据库中有不应该在映射的实体类上公开的数据时,影子属性是很有用的。
索引器属性(Indexer properties),
索引器属性是实体类型属性,由.NET实体类中索引器支持。可以使用.NET类实例上的索引器访问它们。它还允许你在不更改CLR类的情况下向型添加属性。
1. 外键影子属性
影子属性最常用于外键属性,两个实体之间的关系由数据库中的外键值来表示,但是关系是在实体类型上使用实体类型之间的导航属性进行管理的。按照约定,当在从实体类型中发现关系但没有发现外键属性时EF将引入影子属性。
该影子属性会被命名为<navigation property name><principal key property 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; }
// 因为这里没有保存关系外键的CLR属性,所以影子属性被创建
public Blog Blog { get; set; }
}
2. 配置影子属性
你可以使用fluent API来配置影子属性。一旦你调用了Property的字符串重载,你就能为其他属性链接任意配置调用。在下例中,因为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方法的名称与已有属性的名称匹配(影子属性或实体类上定义的属性),那么代码将配置已有属性,而不是引入新的影子属性。
3. 访问影子属性
影子属性的值可以通过ChangeTracker API获取和修改:
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定义了一个索引器,它用于创建一个索引器属性。
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方法的名称与现有的索引器属性名称匹配,那么代码将配置该现有属性。如果实体类型有一个由实体类上的属性支持的属性,则会引发异常,因为索引器属性必须通过索引器来访问。
索引器属性可以在LINQ查询中通过EF.Property静态方法(如上面展示的那样)被引用,或使用CLR索引器属性来引用。
5. 属性包实体类型
注意:
属性包实体类型(Property bag entity types)是在EF Core 5.0引入的。
只包含索引器属性的实体类型被称为属性包实体类型。这些实体类型没有影子属性,并且EF会创建索引器属性来替代它。目前只有Dictionary<string, object>支持属性包实体类型。它必须配置为具有唯一名称的共享类型实体类型,并且相应的DbSet属性必须使用Set调用实现。
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缺乏共享历史题类型的重载。
- 其他类型不能被标记为属性包。