Entity Framework Core的影子属性(Shadow Property)
Entity Framework Core引入了一种新的属性类型,称为“影子”属性,该属性在EF 6.x中不存在。
阴影属性是未在.NET实体类中直接定义的属性。相反,您可以为实体数据模型中的特定实体类型配置它。可以在上下文类的OnModelCreating()方法中配置它们。
下图说明了shadow属性:
如上图所示,阴影属性不属于您的实体类。因此,您无法在访问实体的其他属性时访问它。只能在构建实体数据模型时为实体类型配置阴影属性,并且它们也将映射到数据库列。阴影属性的值和状态仅在更改跟踪器中维护。
让我们了解shadow属性的实际方面。假设我们需要维护数据库表中每个记录的创建和更新日期。您学习了如何通过在实体类中定义CreatedDate和UpdatedDate属性来设置EF Core中实体的创建和修改日期。在这里,我们将看到如何通过使用阴影属性而不在实体类中包含阴影属性来实现相同的结果。
考虑以下学生实体类。
public class Student
{
public int StudentID { get; set; }
public string StudentName { get; set; }
public DateTime? DateOfBirth { get; set; }
public decimal Height { get; set; }
public float Weight { get; set; }
}
上面的Student类不包含CreatedDate和UpdatedDate属性来维护创建或更新的时间。我们将它们配置为Student实体上的阴影属性。
定义影子属性
您可以使用Property()方法在OnModelCreating()中使用Fluent API为实体类型定义阴影属性。
以下内容在Student实体上配置了两个影子属性CreatedDate和UpdatedDate:
public class SchoolContext : DbContext
{
public SchoolContext() : base()
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().Property<DateTime>("CreatedDate");
modelBuilder.Entity<Student>().Property<DateTime>("UpdatedDate");
}
public DbSet<Student> Students { get; set; }
}
如您所见,Property()方法用于配置阴影属性。将shadow属性的名称指定为字符串,并将类型指定为通用参数。如果在Property()方法中指定的名称与现有属性的名称匹配,则EF Core将将该现有属性配置为阴影属性,而不是引入新的阴影属性。
数据库中的阴影属性
定义阴影属性后,我们需要更新数据库架构,因为阴影属性将映射到相应的数据库列。
为此,请在Visual Studio的程序包管理器控制台中使用以下命令添加数据库迁移。
PM> add-migration addShadowProperty
PM> update-database
现在,Student表将包括两列,SQL Server中的CreatedDate和UpdatedDate,如下所示。
因此,即使我们没有在Student类中包含这些属性并将其配置为阴影属性,数据库也将具有相应的列。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRP9Wp4Q-1581131560252)(d:
oteefcorepic25.png)]
访问影子属性
您可以使用EntityEntry的Property()方法获取或设置阴影属性的值。以下代码访问shadow属性的值。
using (var context = new SchoolContext())
{
var std = new Student(){ StudentName = "Bill" };
// sets the value to the shadow property
context.Entry(std).Property("CreatedDate").CurrentValue = DateTime.Now;
// gets the value of the shadow property
var createdDate = context.Entry(std).Property("CreatedDate").CurrentValue;
}
但是,在我们的方案中,我们想在SaveChanges()方法上自动将值设置为这些阴影属性,这样就不必在每个实体对象上手动设置它们。因此,请在上下文类中重写SaveChanges()方法,如下所示。
public override int SaveChanges()
{
var entries = ChangeTracker
.Entries()
.Where(e =>
e.State == EntityState.Added
|| e.State == EntityState.Modified);
foreach (var entityEntry in entries)
{
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
if (entityEntry.State == EntityState.Added)
{
entityEntry.Property("CreatedDate").CurrentValue = DateTime.Now;
}
}
return base.SaveChanges();
}
这将自动将值设置为CreatedDate和UpdatedDate阴影属性。
现在,执行以下代码并检查数据库中的记录。
using (var context = new SchoolContext())
{
var std = new Student(){ StudentName = "Bill" };
context.Add(std);
context.SaveChanges();
}
上面的代码将在Student表中插入带有CreatedDate和UpdatedDate的以下记录。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kI9fDpzD-1581131560252)(d:
oteefcorepic26.png)]
因此,通过配置阴影属性,我们不必将它们包括在实体类中。
在所有实体上配置阴影属性
您可以一次在所有实体上配置阴影属性,而不是为所有实体手动配置它们。
例如,我们可以一次在所有实体上配置CreatedDate和UpdatedDate,如下所示。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var allEntities = modelBuilder.Model.GetEntityTypes();
foreach (var entity in allEntities)
{
entity.AddProperty("CreatedDate",typeof(DateTime));
entity.AddProperty("UpdatedDate",typeof(DateTime));
}
}
何时使用阴影属性?
阴影属性可以在两种情况下使用:
- 当您不想在映射的实体上公开数据库列时,例如上面讨论的方案。
- 当您不想公开外键属性而只想使用导航属性来管理关系时。外键属性将是shadow属性并映射到数据库列,但不会作为实体的属性公开。 (在EF Core中,如果您未在实体类中定义外键属性,则它将自动为此生成阴影属性。您无需手动配置外键属性。)
了解如何在运行时检查阴影属性
https://www.entityframeworktutorial.net/faq/how-to-check-shadow-property-at-runtime.aspx