EF Core 6 自动添加导航属性

Mur*_*ock 6 c# domain-driven-design entity-framework-core ef-core-6.0

想象一下我有以下代码。

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)

我有以下 MyEntity 映射

builder.HasOne(o => o.SomeNavigationProperty).WithOne().HasForeignKey<MyEntity>(o => o.SomeNavigationPropertyId);
Run Code Online (Sandbox Code Playgroud)

SaveChangesAsync 失败并显示:

数据库操作预计影响1行,但实际影响0行;自加载实体以来,数据可能已被修改或删除。

如果我在保存后查看更改跟踪器,MyNavigationProperty已被标记为已修改且未添加。

有效的版本

var myEntity = new MyEntity(...);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddAsync(myEntity);
await Context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddAsync(myEntity.SomeNavigationProperty);
await Context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)

如果我阅读https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.addasync?view=efcore-6.0 ,这种行为在某种程度上是有意义的。我不完全理解为什么 ef core 决定将未跟踪的实体标记为已修改而不是已添加。

不管怎样......我的问题是我的域和存储库是分开的。换句话说,当属性被设置时,我无权访问上下文。

有没有一种方法可以告诉 EF Core 上下文重新添加所有内容(如果尚未添加)而不抱怨重复项?

就像是

await Context.RefreshAddAsync(myEntity);
Run Code Online (Sandbox Code Playgroud)

其中RefreshAddAsync将跳过MyEntity(如果已添加)并标记MyNavigationProperty为已添加。

任何其他建议表示赞赏。最糟糕的情况是,我将编写一个RefreshAddAsync使用反射的版本,但如果有的话,我希望有一个更干净的解决方案。

Mur*_*ock 0

我最终做到了这一点,效果非常好。

public static async Task AddOrUpdateEntityAsync<T>(this ArtBankContext context, T entity)
        where T : DomainEntity
{
    context.ThrowIfNull();
    entity.ThrowIfNull();

    var toProcessQueue = new Queue<DomainEntity>();
    var processed = new List<DomainEntity>();

    var tracked = RunWithAutoDetectChangesDisabled(() => context.ChangeTracker
        .Entries<DomainEntity>()
        .Select(o => o.Entity));

    toProcessQueue.Enqueue(entity);

    while (toProcessQueue.Any())
    {
        var toProcess = toProcessQueue.Dequeue();

        if (!tracked.Any(o => o == toProcess))
        {
            await context
                .AddEntityAsync(toProcess)
                    .ContinueOnAnyContext();
        }

        foreach (var toProcessPropertyInfo in toProcess
            .GetType()
            .GetProperties()
            .Where(o => o.PropertyType.IsAssignableTo(typeof(DomainEntity))))
        {
            var toProcessProperty = (DomainEntity?)toProcessPropertyInfo.GetValue(toProcess);

            if (toProcessProperty == null || processed.Any(o => o == toProcessProperty))
                continue;

            toProcessQueue.Enqueue(toProcessProperty);
        }

        processed.Add(toProcess);
    }

    TResult RunWithAutoDetectChangesDisabled<TResult>(Func<TResult> func)
    {
        //Ef auto detects changes when you call methods like context.ChangeTracker.Entries
        //In this specific use case the entities that we want to add dynamically gets detected and incorreectly added to the contexts.
        //Hence we disable the flag before executing
        var originalAutoDetectChangesEnabledValue = context.ChangeTracker.AutoDetectChangesEnabled;

        context.ChangeTracker.AutoDetectChangesEnabled = false;

        var result = func();

        context.ChangeTracker.AutoDetectChangesEnabled = originalAutoDetectChangesEnabledValue;

        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是用过的

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddOrUpdateEntityAsync(myEntity);
await Context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)