【问题标题】:Unable to track an entity because alternate key property is null无法跟踪实体,因为备用键属性为空
【发布时间】:2021-03-05 11:21:15
【问题描述】:

我已经一直在努力让它发挥作用,但没有成功。

架构: WPF 应用程序在 Azure WebApp(带有 JWT 的 ASP.NET Core ReST API)上添加、更新、接收和删除实体。

问题

当我第一次添加一个“事件”实体时,它确实工作得很好。即使是“第一轮”的更新也可以无缝工作。但是,如果我关闭 WPF 应用程序并尝试更新,它不起作用并抛出异常,无论我尝试在代码中修改什么都不起作用。

无法跟踪“事件”类型的实体,因为备用键属性“UniqueId”为空。如果备用键未在关系中使用,则考虑改用唯一索引。唯一索引可能包含空值,而备用键可能不包含。

UniqueId 不是 ID,它将用作可能会或可能不会出现的报告的外键。但可以肯定并确认,UniqueId 永远不会为空。我不知道为什么它一直告诉我。

有什么想法吗?

事件

internal class IncidentConfiguration : IEntityTypeConfiguration<Incident>
{
    internal static IncidentConfiguration Create() => new();
    public void Configure(EntityTypeBuilder<Incident> builder)
    {
        builder
            .Property(incident => incident.Id)
            .ValueGeneratedOnAdd();
        builder
            .Property(incident => incident.RowVersion)
            .IsConcurrencyToken()
            .ValueGeneratedOnAddOrUpdate();
        builder
            .Property(incident => incident.UniqueId)
            .HasField("_uniqueId")
            .IsRequired();
        builder
            .Property(incident => incident.Completion)
            .HasField("_completion");
        builder
            .Property(incident => incident.Status)
            .HasField("_status");
        builder
            .Property(incident => incident.Estimated)
            .HasField("_estimated")
            .HasConversion(new TimeSpanToTicksConverter());
        builder
            .Property(incident => incident.Actual)
            .HasField("_actual")
            .HasConversion(new TimeSpanToTicksConverter());
        builder
            .Property(incident => incident.Closed)
            .HasField("_closed");
        builder
            .Property(incident => incident.Comments)
            .HasField("_comments");
        builder
            .Property(incident => incident.Opened)
            .HasField("_opened");
        builder
            .Property(incident => incident.Updated)
            .HasField("_updated");
        builder
            .Property(incident => incident.BriefDescripion)
            .HasField("_briefDescripion");
        builder
            .Property(incident => incident.Project)
            .HasField("_project");
        builder
            .Ignore(incident => incident.IsUpdated);
    }
}

举报

internal class ReportConfiguration : IEntityTypeConfiguration<Report>
{
    internal static ReportConfiguration Create() => new();
    public void Configure(EntityTypeBuilder<Report> builder)
    {
        builder
            .Property(report => report.Id)
            .ValueGeneratedOnAdd();
        builder
            .Property(report => report.RowVersion)
            .IsConcurrencyToken()
            .ValueGeneratedOnAddOrUpdate();
        builder
            .HasOne(report => report.Incident)
            .WithMany(incident => incident.Reports)
            .HasForeignKey(report => report.UniqueId)
            .HasPrincipalKey(incident => incident.UniqueId)
            .OnDelete(DeleteBehavior.Cascade);
        builder
            .Ignore(report => report.IsUpdated);
    }
}

“更新”方法

public async Task<bool> UpdateAsync(Common.Models.Incident incident)
    {
        _manualResetEvent.WaitOne();
        try
        {
            using var context = new IncidentManagerContext(_connectionString);                
            context.Incidents.Update(incident);
            bool saveFailed;
            do
            {
                saveFailed = false;
                try
                {
                    await context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    saveFailed = true;
                    var entry = ex.Entries.Single();
                    entry.OriginalValues.SetValues(entry.GetDatabaseValues());
                }

            } while (saveFailed);
        }
        catch (Exception) { return false; }
        finally { _manualResetEvent.Set(); }
        return true;
    }

【问题讨论】:

  • 又钓鱼了一天,我怀疑这可能是一些基于命名约定的行为。甚至“.ValueGeneratedNever()”标记也无济于事。也许 EF 核心以不同的方式处理名称以“Id”结尾的实体(属性)。似乎 EF Core 只是简单地忽略了提供的字符串值并尝试先设置 null。
  • 另一个观察:在摆脱了那个糟糕的错误之后,又出现了另一个奇怪的行为。在几轮“更新”之后,“context.Incidents.Update(incident)”开始添加空实体而不是更新。这可能是最初的“空”错误的原因。那么,为什么 EF Core 会出人意料地添加“Null”条目而不是更新应该更新的内容呢?
  • 实际上,Entity Framwork 已经没有多少耐心了,我即将将所有内容重构回传统的数据库访问方法......
  • EF 的自动魔法行为确实令人沮丧。麻烦多于其价值。

标签: c# asp.net-core entity-framework-core


【解决方案1】:

最后我想出了以下解决方案: “上下文。事件。更新(事件);”仍然不会工作,而且可能永远不会。 所以我把它改成

context.Entry(await context.Incidents.FirstOrDefaultAsync(x => x.Id == incident.Id)).CurrentValues.SetValues(incident);

没有更多的幽灵条目也没有空错误,但导航属性现在消失或无法识别。

如何解决这个问题? 从自动模式切换到更多手动模式。我为事件数据模型实现了可为空的外键属性。

private int? _supporterId, _customerId;

/// <summary>
/// The supporter's foreign key
/// </summary>
[JsonPropertyName("supporterId")]
public int? SupporterId { get { return _supporterId; } set { SetProperty(ref _supporterId, value); } }

/// <summary>
/// The customer's foreign key
/// </summary>
[JsonPropertyName("customerId")]
public int? CustomerId { get { return _customerId; } set { SetProperty(ref _customerId, value); } }

实体类型配置的变化(事件)

builder
    .Property(incident => incident.SupporterId)
    .HasField("_supporterId");
builder
    .Property(incident => incident.CustomerId)
    .HasField("_customerId");

实体类型配置的变化(客户)

builder
    .HasMany(customer => customer.Incidents)
    .WithOne(incident => incident.Customer)
    .HasForeignKey(incident => incident.CustomerId)
    .OnDelete(DeleteBehavior.NoAction);

实体类型配置的变化(支持者)

builder
    .HasMany(supporter => supporter.Incidents)
    .WithOne(incident => incident.Supporter)
    .HasForeignKey(incident => incident.SupporterId)
    .OnDelete(DeleteBehavior.NoAction);

最后,通过这些手动实现和分配的可空外键,导航属性在从数据库读取时具有预期值。

但我仍然有一种不好的感觉,这只是一种解决方法,而不是应有的样子。如果有人有更好的想法,欢迎在这里分享。

【讨论】:

    猜你喜欢
    • 2020-03-26
    • 1970-01-01
    • 2021-08-27
    • 1970-01-01
    • 2020-03-02
    • 2023-01-03
    • 1970-01-01
    • 2022-01-26
    • 2018-06-20
    相关资源
    最近更新 更多