Entity Framework Core:自引用 FK 列值被替换为“NULL”

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

目标框架:.Net Core 2.1

EntityFrameworkCore:2.1.4

我在调用

SaveChanges()
时遇到了一些我不明白的行为。正如标题所示:我拥有有效的 GUID,这些 GUID 具有外键关系,并且在插入新记录时将同一个表替换为 NULL。

这只发生在特定列上,并且仅当 EF 为上下文中所做的更改生成 SQL 时才会发生。我可以在 SSMS 中插入相同的值。

为了演示目的,我在 GitHub 上创建了一个最小存储库,您可以使用它来重新创建我遇到的确切问题,但简而言之,如果您有这样的实体:

public class Account
{
    [Key]
    public Guid AccountId { get; set; }

    #region Audit

    public Guid? AddedByAccountId { get; set; }

    public DateTime AddedOnUtc { get; set; }

    public Guid? ModifiedByAccountId { get; set; }

    public DateTime ModifiedOnUtc { get; set; }

    #endregion Audit

    #region Navigation Properties

    [ForeignKey(nameof(AddedByAccountId))]
    public virtual Account AddedByAccount { get; set; }

    [ForeignKey(nameof(ModifiedByAccountId))]
    public virtual Account ModifiedByAccount { get; set; }

    #endregion Navigation Properties
}

以及像这样的派生 DbContext 实现:

public class EntityFrameworkDbContext : DbContext
{
    public EntityFrameworkDbContext(DbContextOptions<EntityFrameworkDbContext> options)
        : base(options)
    {
    }

    public DbSet<Account> Accounts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Account>(entity =>
        {
            // For some reason this self referencing key generates a unique
            // constraint in the migration script if we don't set this here.

            // "ModifiedByAccountId" does not have this issue! I suspect this might 
            // the root cause of the NULL data issue.
            entity.HasIndex(e => e.AddedByAccountId).IsUnique(false);
        });
    }
}

将任何内容插入到上下文中应该会产生相同的结果。例如:

private void InitialiseDatabase()
{
    var systemAccount = new Account
    {
        AccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
        AddedByAccountId = null,
        AddedOnUtc = DateTime.UtcNow,
        ModifiedByAccountId = null,
        ModifiedOnUtc = DateTime.UtcNow
    };

    var otherAccounts = new List<Account>
    {
        new Account
        {
            AccountId = Guid.Parse("015b76fc-2833-45d9-85a7-ab1c389c1c11"),
            AddedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            AddedOnUtc = DateTime.UtcNow,
            ModifiedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            ModifiedOnUtc = DateTime.UtcNow
        },
        new Account
        {
            AccountId = Guid.Parse("538ee0dd-531a-41c6-8414-0769ec5990d8"),
            AddedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            AddedOnUtc = DateTime.UtcNow,
            ModifiedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            ModifiedOnUtc = DateTime.UtcNow
        },
        new Account
        {
            AccountId = Guid.Parse("8288d9ac-fbce-417e-89ef-82266b284b78"),
            AddedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            AddedOnUtc = DateTime.UtcNow,
            ModifiedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            ModifiedOnUtc = DateTime.UtcNow
        },
        new Account
        {
            AccountId = Guid.Parse("4bcfe9f8-e4a5-49f0-b6ee-44871632a903"),
            AddedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            AddedOnUtc = DateTime.UtcNow,
            ModifiedByAccountId = Guid.Parse("35c38df0-a959-4232-aadd-40db2260f557"),
            ModifiedOnUtc = DateTime.UtcNow
        }
    };

    _dbContext.Add(systemAccount);
    _dbContext.AddRange(otherAccounts);

    try
    {
        _dbContext.SaveChanges();
    }
    catch (Exception ex)
    {
        // Just checking to see if anything was being raised.
    }            
}

将生成以下 SQL(使用 SQL Server Profiler 捕获并格式化以方便阅读):

exec sp_executesql N'SET NOCOUNT ON;

INSERT INTO [Accounts] 
(
    [AccountId], 
    [AddedByAccountId], 
    [AddedOnUtc], 
    [ModifiedByAccountId], 
    [ModifiedOnUtc]
)
VALUES 
(
    @p5, 
    @p6, 
    @p7, 
    @p8, 
    @p9
),
(
    @p10, 
    @p11, 
    @p12, 
    @p13, 
    @p14
),
(
    @p15, 
    @p16, 
    @p17, 
    @p18, 
    @p19
),
(
    @p20, 
    @p21, 
    @p22, 
    @p23, 
    @p24
);
',N'@p5 uniqueidentifier,
@p6 uniqueidentifier,
@p7 datetime2(7),
@p8 uniqueidentifier,
@p9 datetime2(7),
@p10 uniqueidentifier,
@p11 uniqueidentifier,
@p12 datetime2(7),
@p13 uniqueidentifier,
@p14 datetime2(7),
@p15 uniqueidentifier,
@p16 uniqueidentifier,
@p17 datetime2(7),
@p18 uniqueidentifier,
@p19 datetime2(7),
@p20 uniqueidentifier,
@p21 uniqueidentifier,
@p22 datetime2(7),
@p23 uniqueidentifier,
@p24 datetime2(7)',
@p5='015B76FC-2833-45D9-85A7-AB1C389C1C11',
@p6=NULL,
@p7='2018-11-10 14:29:25.5363017',
@p8='35C38DF0-A959-4232-AADD-40DB2260F557',
@p9='2018-11-10 14:29:25.5363022',
@p10='538EE0DD-531A-41C6-8414-0769EC5990D8',
@p11=NULL,
@p12='2018-11-10 14:29:25.5363031',
@p13='35C38DF0-A959-4232-AADD-40DB2260F557',
@p14='2018-11-10 14:29:25.5363034',
@p15='8288D9AC-FBCE-417E-89EF-82266B284B78',
@p16=NULL,
@p17='2018-11-10 14:29:25.5363039',
@p18='35C38DF0-A959-4232-AADD-40DB2260F557',
@p19='2018-11-10 14:29:25.5363042',
@p20='4BCFE9F8-E4A5-49F0-B6EE-44871632A903',
@p21='35C38DF0-A959-4232-AADD-40DB2260F557',
@p22='2018-11-10 14:29:25.5363047',
@p23='35C38DF0-A959-4232-AADD-40DB2260F557',
@p24='2018-11-10 14:29:25.5363047'

我怀疑这个问题的根源在于我在第二个片段中突出显示的

"IsUnique(false")
代码行,因为这只发生在
AddedByAccountId
属性/列。但是,如果我不包含此行,那么创建的迁移脚本将添加唯一约束(我不想要)。

是否有人遇到过在不应该创建约束时创建约束或(更重要的是)插入时用 NULL 值替换实际数据的问题?

干杯。

c# sql-server entity-framework-core
1个回答
2
投票

// 由于某种原因,这个自引用键生成一个唯一的
// 如果我们不在此处设置,则迁移脚本中的约束。

//“ModifiedByAccountId”没有这个问题!我怀疑这可能
// NULL 数据问题的根本原因。

你的怀疑是对的。 EF Core 对 2 个自引用导航属性感到困惑,并错误地决定(可能是一个错误)它们代表单个一对一关系:

一对一关系双方都有参考导航属性。它们遵循与一对多关系相同的约定,但在外键属性上引入了唯一索引,以确保只有一个依赖项与每个主体相关。

当然,您想要的是两个一对多关系,因此不要修复索引(这不会像您已经看到的那样有帮助),只需显式映射它们即可:

modelBuilder.Entity<Account>().HasOne(e => e.AddedByAccount).WithMany();
modelBuilder.Entity<Account>().HasOne(e => e.ModifiedByAccount).WithMany();
© www.soinside.com 2019 - 2024. All rights reserved.