在不进行修改的情况下加载、添加和保存实体时,Entity Framework Core 会引发主键约束冲突错误

问题描述 投票:0回答:1

我目前正在开发一个 Azure Function 应用程序,该应用程序使用 Entity Framework Core 将数据保存在 Microsoft SQL Server 中。

现在的问题是我无法加载、添加和保存数据。

在下面的服务代码中,我加载记录,将它们添加到上下文中,然后保存它们。不会发生任何修改。但是,我遇到了违反主键约束错误。

我已经添加了所有相关代码。

几个小时后,我无法弄清楚 Entity Framework Core 将实体视为新实体并尝试插入它们的方式。

任何帮助表示赞赏!

错误:

Microsoft.EntityFrameworkCore.DbUpdateException:保存实体更改时发生错误。有关详细信息,请参阅内部异常。

Microsoft.Data.SqlClient.SqlException(0x80131904):违反主键约束“PK_NotificationRecords”。无法在对象“dbo.NotificationRecords”中插入重复的键。重复的键值为 (02289e67-e7f1-401e-f603-08dcf4ef7c1b)。

服务:

private async Task DispatchNotifications(List<ApplicationsByOwner> applicationsByOwners, CancellationToken cancellationToken)
{
    var activeNotificationRecords = (await notificationRepository.GetActiveNotificationRecords(assetType: AssetType.Application, cancellationToken: cancellationToken)).ToList();
    await notificationRepository.AddNotificationRecordsAsync(activeNotificationRecords, cancellationToken);
    await notificationRepository.SaveChangesAsync(cancellationToken); # ERROR HERE
}

数据库上下文:

public sealed class AssetOwnershipContext : DbContext
{
    public DbSet<NotificationRecord> NotificationRecords { get; set; }

    public AssetOwnershipContext(DbContextOptions<AssetOwnershipContext> options) : base(options)
    {
        Database.EnsureCreated();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // NotificationRecord
        modelBuilder.Entity<NotificationRecord>().Property(p => p.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
        modelBuilder.Entity<NotificationRecord>().Property(p => p.UpdatedAt).HasDefaultValueSql("GETUTCDATE()").ValueGeneratedOnAdd();
        modelBuilder.Entity<NotificationRecord>().Property(d => d.AssetType).HasConversion<string>();

        base.OnModelCreating(modelBuilder);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
}

存储库:

public async Task<IEnumerable<NotificationRecord>> GetActiveNotificationRecords(Guid? owner = default, AssetType? assetType = default, CancellationToken cancellationToken = default) 
{
   var query = context.NotificationRecords.Where(x => x.IsActive);

   if (owner.HasValue)
   {
       query = query.Where(x => x.OwnerId == owner);
   }

   if (assetType.HasValue)
   {
       query = query.Where(x => x.AssetType == assetType);
   }

   return await query.ToListAsync(cancellationToken);
}

public async Task AddNotificationRecordsAsync(IEnumerable<NotificationRecord> notificationRecords, CancellationToken cancellationToken = default)
{
    await context.NotificationRecords.AddRangeAsync(notificationRecords, cancellationToken);
}

public async Task<bool> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    return await context.SaveChangesAsync(cancellationToken) >= 0;
}

NotificationRecord

[Index(nameof(OwnerId))]
[Index(nameof(AssetId))]
[Index(nameof(UserPrincipalName))]
[Index(nameof(CreatedAt))]
public class NotificationRecord
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; init; }
    [Required] public Guid OwnerId { get; set; }
    [Required] public Guid AssetId { get; set; }
    [Required] public AssetType AssetType { get; set; }
    [MaxLength(256)] public string? DisplayName { get; set; }
    [MaxLength(256)] public string? GivenName { get; set; }
    [MaxLength(256)] public string? Surname { get; set; }
    [Required][MaxLength(256)] public string? UserPrincipalName { get; set; }
    // [Required] public ICollection<Notification> Notifications { get; } = new List<Notification>();
    [Required] public DateTime? CreatedAt { get; set; }
    [Required] public DateTime? UpdatedAt { get; set; }
    [Required] public bool IsActive { get; set; } = true;
}
c# .net entity-framework-core
1个回答
0
投票

这个最小的示例除了混淆 EF 之外什么也没做,具体取决于您的 Repository 类的实际用途。

var activeNotificationRecords = (await notificationRepository.GetActiveNotificationRecords(assetType: AssetType.Application, cancellationToken: cancellationToken)).ToList();
await notificationRepository.AddNotificationRecordsAsync(activeNotificationRecords, cancellationToken);
await notificationRepository.SaveChangesAsync(cancellationToken); # ERROR HERE

根据您的存储库代码,正在调用“添加”方法,该方法尝试将这些项目添加回上下文中的通知记录中

DbSet
将告诉 EF 在这些记录已存在时将记录插入表中。

您所需要的是:

var activeNotificationRecords = (await notificationRepository.GetActiveNotificationRecords(assetType: AssetType.Application, cancellationToken: cancellationToken)).ToList();
// make your changes to the notification records...

await notificationRepository.SaveChangesAsync(cancellationToken);

如果您的“更改”涉及关联分离的

List<ApplicationsByOwner> applicationsByOwners
,您将会遇到问题。 如果您想要更新诸如导航属性之类的内容,那么对获取实体的读取调用需要预先加载这些关联,并且您想要与这些实体关联的任何实体都需要由该操作正在使用的 DbContext 进行跟踪。避免传递分离的实体,因为与简单地通过 ID 获取新关系相比,正确地重新关联它们更加复杂且容易出错。

我强烈建议删除该存储库模式层。 EF 已经以

DbSet<NotificationRecord>
的形式提供了一个存储库。使用顶层的薄层抽象只会混淆和削弱像这样的 EF 操作。 (至少它不是通用存储库:)基于 EF 的显式存储库模式可以简化单元测试或强制执行常见的低级过滤规则(多租户、软删除等),但在其他方面保留它简单并利用 EF 已提供的功能。此类抽象可能会隐藏问题,导致查询效率低下,或者最终变得非常复杂,您需要重新发明轮子来尝试维护 EF 已经提供的一些开箱即用的灵活性。 (投影、分页、排序、过滤、预加载等)

© www.soinside.com 2019 - 2024. All rights reserved.