繁体   English   中英

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

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

EF6:插入已具有主键值的实体,并为主键分配新值。

EF Core:它试图插入主键的值,但显然是因为SqlException而失败:

当IDENTITY_INSERT设置为OFF时,无法在表'Asset'中为identity列插入显式值。

我找到的解决方法是将PK重置为默认值(int为0)。

示例:

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

我正在将解决方案从EF 6迁移到EF Core,我希望避免在插入时手动重置所有ID。 上面的例子非常简单,有时我会插入一个完整的图形,在我的情况下,这意味着重置图形的所有PK和FK。

我能想到的唯一自动解决方案是使用反射来解析整个图形并重置ID。 但我认为这不是很有效......

我的问题:如何全局禁用该行为?

不幸的是,目前(从EF Core 2.0.1开始),这种行为是不可控制的。 我想它应该是EF6的改进,因为它允许你执行身份插入。

幸运的是,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]));

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

如果这些方法是virtual ,它们会很好,但它们不是,因此为了删除该检查,您基本上需要复制几乎所有代码。

将以下代码添加到项目中的新代码文件中:

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

然后在DbContext派生类中重写OnConfiguring并添加以下行:

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

就是这样 - 问题解决了。

由于EF Core不支持拦截器,我的建议是“破解它”。

如果覆盖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();
    }
}

这可能最终需要为每种类型设置一个循环,或者您可以获得创造性,但一般来说,这将实现您想要的,而无需对您的业务方法进行大量更改。

暂无
暂无

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

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