【发布时间】:2021-03-10 07:38:10
【问题描述】:
更新:下面列出的示例代码现已完整且足够 在会议中生成影子备用键。当会议 实体继承自包含 RowVersion 属性的基本实体 影子备用密钥在会议实体中生成。 如果该属性直接包含在会议实体中, 如果没有继承,就不会生成影子备用键。
我的模型在 EF Core 3.1 中按预期工作。我升级到 .Net 5 和 EF Core 5,EF 将名为 TempId 的影子备用键属性添加到多个实体。除非我将这些属性添加到数据库中,否则 EF 无法加载这些实体。我可以在模型中找到的任何关系中都没有使用阴影备用键属性。几乎所有关于影子属性的讨论都是针对外键或隐藏属性。我找不到任何解释为什么 EF 会添加一个影子备用键,特别是如果它不使用该属性。有什么建议吗?
获得影子备用键的实体之一是会议,它是一种关系中的子项和另一种关系中的父项。我有许多类似的实体没有得到影子备用键,我看不出它们之间有什么区别。
我使用主键的备用键来循环识别所有影子属性和所有关系的模型实体。关系中不使用任何影子备用键。我确实看到了我专门使用备用键的两个定义的关系,所以我相信我的代码是正确的。
这是一个完整的简化 EF 上下文及其两个实体,它演示了问题。
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EFShadow
{
public partial class Conference
{
public Conference()
{
Meetings = new HashSet<Meeting>();
}
[Key]
public string ConferenceCode { get; set; }
[Required]
public string ConferenceName { get; set; }
public ICollection<Meeting> Meetings { get; }
}
public partial class Meeting : BaseEntity
{
public Meeting() { }
[Key]
public int MeetingId { get; set; }
[Required]
public string ConferenceCode { get; set; }
[Required]
public string Title { get; set; }
public Conference Conference { get; set; }
}
[NotMapped]
public abstract partial class BaseEntity
{
[Timestamp]
public byte[] RowVersion { get; set; }
}
public class EFShadowContext : DbContext
{
public EFShadowContext(DbContextOptions<EFShadowContext> options)
: base(options)
{
ChangeTracker.LazyLoadingEnabled = false;
}
public DbSet<Conference> Conferences { get; set; }
public DbSet<Meeting> Meetings { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Conference>(entity =>
{
entity.HasKey(e => e.ConferenceCode);
entity.ToTable("Conferences", "Settings");
entity.Property(e => e.ConferenceCode)
.IsRequired()
.HasMaxLength(25)
.IsUnicode(false)
.ValueGeneratedNever();
entity.Property(e => e.ConferenceName)
.IsRequired()
.HasMaxLength(100);
});
builder.Entity<Meeting>(entity =>
{
entity.HasKey(e => e.MeetingId);
entity.ToTable("Meetings", "Offerings");
entity.Property(e => e.ConferenceCode).HasMaxLength(25).IsUnicode(false).IsRequired();
entity.Property(e => e.Title).HasMaxLength(255).IsRequired();
//Inherited properties from BaseEntityWithUpdatedAndRowVersion
entity.Property(e => e.RowVersion)
.IsRequired()
.IsRowVersion();
entity.HasOne(p => p.Conference)
.WithMany(d => d.Meetings)
.HasForeignKey(d => d.ConferenceCode)
.HasPrincipalKey(p => p.ConferenceCode)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("Meetings_FK_IsAnOccurrenceOf_Conference");
});
}
}
}
这是我用来识别影子键的代码。
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace ConferenceEF.Code
{
public class EFModelAnalysis
{
readonly DbContext _context;
public EFModelAnalysis(DbContext context)
{
Contract.Requires(context != null);
_context = context;
}
public List<string> ShadowProperties()
{
List<string> results = new List<string>();
var entityTypes = _context.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
var entityProperties = entityType.GetProperties();
foreach (var entityProperty in entityProperties)
{
if (entityProperty.IsShadowProperty())
{
string output = $"{entityType.Name}.{entityProperty.Name}: {entityProperty}.";
results.Add(output);
}
}
}
return results;
}
public List<string> AlternateKeyRelationships()
{
List<string> results = new List<string>();
var entityTypes = _context.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
foreach (var fk in entityType.GetForeignKeys())
{
if (!fk.PrincipalKey.IsPrimaryKey())
{
string output = $"{entityType.DisplayName()} Foreign Key {fk.GetConstraintName()} " +
$"references principal ALTERNATE key {fk.PrincipalKey} " +
$"in table {fk.PrincipalEntityType}.";
results.Add(output);
}
}
}
return results;
}
}
}
这里是上下文初始化和处理代码。
var connectionSettings = ((LoadDataConferencesSqlServer)this).SqlConnectionSettings;
DbContextOptionsBuilder builderShadow = new DbContextOptionsBuilder<EFShadowContext>()
.UseSqlServer(connectionSettings.ConnectionString);
var optionsShadow = (DbContextOptions<EFShadowContext>)builderShadow.Options;
using EFShadowContext contextShadow = new EFShadowContext(optionsShadow);
EFModelAnalysis efModelShadow = new EFModelAnalysis(contextShadow);
var shadowPropertiesShadow = efModelShadow.ShadowProperties();
foreach (var shadow in shadowPropertiesShadow)
progressReport?.Report(shadow); //List the shadow properties
var alternateKeysShadow = efModelShadow.AlternateKeyRelationships();
foreach (var ak in alternateKeysShadow)
progressReport?.Report(ak); //List relationships using alternate key
我得到的输出是: EFShadow.Conference.TempId: 属性: Conference.TempId (no field, int) Shadow 需要 AlternateKey AfterSave:Throw。
没有关系使用这个备用键。
如果我消除了会议实体从 BaseEntity 的继承并直接在会议中包含 RowVersion 时间戳属性,则不会生成影子键。这是产生差异所需的唯一更改。
【问题讨论】:
-
无法使用提供的代码重现。我在干净的环境中,与您发布的唯一区别是评论以
entity.HasOne(d => d.CurrentPhase)开头的行(没有这样的属性/实体)和行base.Configure(entity);(不知道里面有什么)跨度> -
谢谢伊万。完整模型太大而无法包含。我会看看一个缩减模型是否会产生同样的问题,然后发布完整的模型。
-
我不知道这是不是好消息,但是只有 3 个实体的缩减模型没有添加阴影备用键。我从主模型中删除了尽可能多的实体,并且仍然生成了所有阴影备用键。 Model.DebugView 列出了影子键属性,并且没有列出这些键的任何用法。进一步的模型简化是困难的,因为其余的实体都参与了关系。对于确定原因还有其他建议吗?
-
原帖现在已更新为可演示问题的工作代码。
-
不幸的是,更新后的示例仍然没有重现该问题。所以我相信这里仍然有一些没有显示的东西导致它。让我们从名称“TempId”开始。是否有一个名为“Temp”的类具有属性“Id”?
OnModelCreating中是否有包含字符串“TempId”的代码?代码中是否有字符串“TempId”?等等。这个名称应该来自某个地方,EF Core 不使用硬编码的名称。
标签: entity-framework entity-framework-core ef-core-5.0