简体   繁体   English

EF6与实体框架核心:插入实体而不将主键(标识)重置为零

[英]EF6 vs Entity Framework Core : Insert an entity without resetting the primary key (identity) to zero

EF6: inserting an entity that already has a value for the primary key works and assigns a new value to the primary key. EF6:插入已具有主键值的实体,并为主键分配新值。

EF Core : it tries to insert the value of the primary key and obviously fails with a SqlException : EF Core:它试图插入主键的值,但显然是因为SqlException而失败:

Cannot insert explicit value for identity column in table 'Asset' when IDENTITY_INSERT is set to OFF. 当IDENTITY_INSERT设置为OFF时,无法在表'Asset'中为identity列插入显式值。

The workaround I found is to reset the PK to the default value (0 for an int). 我找到的解决方法是将PK重置为默认值(int为0)。

Example : 示例:

        Asset asset = new Asset
        {
            Id = 3,
            Name = "Toto",
        };      

        using (AppDbContext context = new AppDbContext())
        {
            asset.Id = 0; // needs to be reset
            context.Assets.Add(asset);
            context.SaveChanges();
        }

I'm migrating a solution from EF 6 to EF Core and I'd like to avoid resetting manually all the IDs in case of an insertion. 我正在将解决方案从EF 6迁移到EF Core,我希望避免在插入时手动重置所有ID。 The example above is really simple, sometimes I have a whole graph to insert, in my case that would mean resetting all the PKs and FKs of the graph. 上面的例子非常简单,有时我会插入一个完整的图形,在我的情况下,这意味着重置图形的所有PK和FK。

The only automatic solution I can think of is using reflection to parse the whole graph and reset the IDs. 我能想到的唯一自动解决方案是使用反射来解析整个图形并重置ID。 But i don't think that's very efficient... 但我认为这不是很有效......

My question: how to disable that behaviour globally? 我的问题:如何全局禁用该行为?

Unfortunately currently (as of EF Core 2.0.1) this behavior is not controllable. 不幸的是,目前(从EF Core 2.0.1开始),这种行为是不可控制的。 I guess it's supposed to be improvement from EF6 since it allows you to perform identity inserts. 我想它应该是EF6的改进,因为它允许你执行身份插入。

Fortunately EF Core is build on service based architecture which allows you ( (although not so easily) to replace almost every aspect. In this case, the responsible service is called IValueGenerationManager and is implemented by ValueGenerationManager class. The lines that provide the aforementioned behavior are 幸运的是,EF Core构建在基于服务的体系结构上,允许您(尽管不是那么容易)替换几乎所有方面。在这种情况下,负责任的服务称为IValueGenerationManager ,由ValueGenerationManager类实现。提供上述行为的行是

private static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEntry entry)    
    => entry.EntityType.GetProperties().Where(    
        property => property.IsForeignKey()    
                    && property.ClrType.IsDefaultValue(entry[property]));

private static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry)    
    => entry.EntityType.GetProperties().Where(    
        property => property.RequiresValueGenerator()    
                    && property.ClrType.IsDefaultValue(entry[property]));

specifically the && property.ClrType.IsDefaultValue(entry[property]) condition. 特别是&& property.ClrType.IsDefaultValue(entry[property])条件。

It would have been nice if these methods were virtual , but they are not, so in order to remove that check you basically need to copy almost all code. 如果这些方法是virtual ,它们会很好,但它们不是,因此为了删除该检查,您基本上需要复制几乎所有代码。

Add the following code to a new code file in your project: 将以下代码添加到项目中的新代码文件中:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore
{
    public static class Extensions
    {
        public static DbContextOptionsBuilder UseEF6CompatibleValueGeneration(this DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ReplaceService<IValueGenerationManager, EF6CompatibleValueGeneratorManager>();
            return optionsBuilder;
        }
    }
}

namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
{
    public class EF6CompatibleValueGeneratorManager : ValueGenerationManager
    {
        private readonly IValueGeneratorSelector valueGeneratorSelector;
        private readonly IKeyPropagator keyPropagator;

        public EF6CompatibleValueGeneratorManager(IValueGeneratorSelector valueGeneratorSelector, IKeyPropagator keyPropagator)
            : base(valueGeneratorSelector, keyPropagator)
        {
            this.valueGeneratorSelector = valueGeneratorSelector;
            this.keyPropagator = keyPropagator;
        }

        public override InternalEntityEntry Propagate(InternalEntityEntry entry)
        {
            InternalEntityEntry chosenPrincipal = null;
            foreach (var property in FindPropagatingProperties(entry))
            {
                var principalEntry = keyPropagator.PropagateValue(entry, property);
                if (chosenPrincipal == null)
                    chosenPrincipal = principalEntry;
            }
            return chosenPrincipal;
        }

        public override void Generate(InternalEntityEntry entry)
        {
            var entityEntry = new EntityEntry(entry);
            foreach (var property in FindGeneratingProperties(entry))
            {
                var valueGenerator = GetValueGenerator(entry, property);
                SetGeneratedValue(entry, property, valueGenerator.Next(entityEntry), valueGenerator.GeneratesTemporaryValues);
            }
        }

        public override async Task GenerateAsync(InternalEntityEntry entry, CancellationToken cancellationToken = default(CancellationToken))
        {
            var entityEntry = new EntityEntry(entry);
            foreach (var property in FindGeneratingProperties(entry))
            {
                var valueGenerator = GetValueGenerator(entry, property);
                SetGeneratedValue(entry, property, await valueGenerator.NextAsync(entityEntry, cancellationToken), valueGenerator.GeneratesTemporaryValues);
            }
        }

        static IEnumerable<IProperty> FindPropagatingProperties(InternalEntityEntry entry)
        {
            return entry.EntityType.GetProperties().Where(property => property.IsForeignKey());
        }

        static IEnumerable<IProperty> FindGeneratingProperties(InternalEntityEntry entry)
        {
            return entry.EntityType.GetProperties().Where(property => property.RequiresValueGenerator());
        }

        ValueGenerator GetValueGenerator(InternalEntityEntry entry, IProperty property)
        {
            return valueGeneratorSelector.Select(property, property.IsKey() ? property.DeclaringEntityType : entry.EntityType);
        }

        static void SetGeneratedValue(InternalEntityEntry entry, IProperty property, object generatedValue, bool isTemporary)
        {
            if (generatedValue == null) return;
            entry[property] = generatedValue;
            if (isTemporary)
                entry.MarkAsTemporary(property, true);
        }
    }
}

then override OnConfiguring in your DbContext derived class and add the following line: 然后在DbContext派生类中重写OnConfiguring并添加以下行:

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

and that's it - problem solved. 就是这样 - 问题解决了。

Since EF Core doesn't support interceptors, my recommendation is to "hack it". 由于EF Core不支持拦截器,我的建议是“破解它”。

If you overwrite the SaveChanges method you can provide this functionality on every save without a massive refactor. 如果覆盖SaveChanges方法,则可以在没有大量重构的情况下在每次保存时提供此功能。

public class MyContext : DbContext
{
    //...

    public override int SaveChanges()
    {
        foreach (var dbEntityEntry in ChangeTracker.Entries<Asset>()
                                                   .Where(t => t.State == EntityState.Added))
        {
            dbEntityEntry.Entity.Id = default(int);
        }
        return base.SaveChanges();
    }
}

This may end up needing a loop for each type, or maybe you can get creative, but generally speaking this will accomplish what you want without making massive changes to your business methods. 这可能最终需要为每种类型设置一个循环,或者您可以获得创造性,但一般来说,这将实现您想要的,而无需对您的业务方法进行大量更改。

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

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