简体   繁体   English

EF Core 6 自动添加导航属性

[英]EF Core 6 automatically add navigation property

Imagine I had the following code.想象一下我有以下代码。

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.SaveChangesAsync();

I have the following mapping for MyEntity我对 MyEntity 有以下映射

builder.HasOne(o => o.SomeNavigationProperty).WithOne().HasForeignKey<MyEntity>(o => o.SomeNavigationPropertyId);

SaveChangesAsync fail with: SaveChangesAsync 失败:

The database operation was expected to affect 1 row(s), but actually affected 0 row(s);数据库操作预计会影响 1 行,但实际上影响了 0 行; data may have been modified or deleted since entities were loaded.自加载实体以来,数据可能已被修改或删除。

If I look at the change tracker after save, MyNavigationProperty has been marked as modified and not added.如果我在保存后查看更改跟踪器, MyNavigationProperty已被标记为已修改且未添加。

Versions that work有效的版本

var myEntity = new MyEntity(...);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddAsync(myEntity);
await Context.SaveChangesAsync();

and

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddAsync(myEntity.SomeNavigationProperty);
await Context.SaveChangesAsync();

This behavior makes sense to a degree if I read up on https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.addasync?view=efcore-6.0 .如果我阅读https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.addasync?view=efcore-6.0 ,这种行为在一定程度上是有意义的。 I do not completely understand why ef core decides to mark the untracked entity as modified instead of added though.我不完全理解为什么 ef core 决定将未跟踪的实体标记为已修改而不是添加。

Either way... my problem is that my domain and repository are separated.无论哪种方式......我的问题是我的域和存储库是分开的。 In other words when the property gets set I do not have access to the context.换句话说,当属性被设置时,我无法访问上下文。

Is there a way I can tell the EF Core context to re-add all if its not already added without it complaining about duplicates?有没有一种方法可以告诉 EF Core 上下文重新添加所有内容(如果尚未添加)而不抱怨重复?

Something like就像是

await Context.RefreshAddAsync(myEntity);

where RefreshAddAsync will skip MyEntity (if it's already added) and mark MyNavigationProperty as added. RefreshAddAsync将跳过MyEntity (如果已添加)并将MyNavigationProperty标记为已添加。

Any other suggestions appreciated.任何其他建议表示赞赏。 Worse case scenario I'll write a version of RefreshAddAsync using reflection, but would like a cleaner solution if there is one.更糟糕的情况我将使用反射编写一个RefreshAddAsync版本,但如果有一个更清洁的解决方案。

If I'm not mistaken MyEntities is item and SomeNavigationProperty is parent.如果我没记错的话 MyEntities 是 item 而 SomeNavigationProperty 是父级。 You have an item and you work to create a parent for your current item.您有一个项目,并且您正在努力为当前项目创建一个父项。 Your this code seems like the most appropriate method for this situation:您的这段代码似乎是最适合这种情况的方法:

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddAsync(myEntity.SomeNavigationProperty);
await Context.SaveChangesAsync();

But if you have parent data.但是如果你有父数据。 You can do like this:你可以这样做:

var someNavigationProperty = Context.SomeNavigationProperty.GetById(id);
someNavigationProperty.MyEntities = anyMyEntitiesData;
await Context.SaveChangesAsync();

Due to your constraints I would try the following:由于您的限制,我会尝试以下操作:

The default constructor of your navigation property sets its Id property to some special value, defined as ´public static readonly` field on the entity (eg -1 or Guid.NewGuid()).导航属性的默认构造函数将其 Id 属性设置为某个特殊值,定义为实体上的“public static readonly”字段(例如 -1 或 Guid.NewGuid())。

At next add an interceptor to your context that's being executed before SaveChanges() and within that method ask the ChangeTracker to give you all entities, filtered by the navigation property type, the special Id value and the state being Modified.接下来,在SaveChanges()之前执行的上下文中添加一个拦截器,并在该方法中要求 ChangeTracker 为您提供所有实体,这些实体按导航属性类型、特殊 Id 值和正在修改的状态进行过滤。 Now change on all these items the entity state to be Added and exit your interceptor.现在在所有这些项目上更改要添加的实体状态并退出拦截器。

Unfortunately I can't create a working example right now, but IMHO this should solve your problem.不幸的是,我现在无法创建一个工作示例,但恕我直言,这应该可以解决您的问题。 I also ran into this problem when upgrading to EF core 6, but on all places I had access to the context class itself and could add the navigation property to the corresponding context collection directly to avoid this problem, but I think the above described way should also work.我在升级到 EF Core 6 时也遇到了这个问题,但是在所有地方我都可以访问上下文类本身并且可以直接将导航属性添加到相应的上下文集合中以避免这个问题,但我认为上述方式应该也工作。

I ended up doing this which works pretty well.我最终这样做了,效果很好。

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;
    }
}

it is the used like它是使用过的

var myEntity = Context.MyEntities.GetById(id);
myEntity.SomeNavigationProperty = new MyNavigationProperty(...);
await Context.AddOrUpdateEntityAsync(myEntity);
await Context.SaveChangesAsync();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM