首页 > 解决方案 > 如何在 Entity Framework Core 中控制内存中生成的键值?

问题描述

背景

我正在开发一个基于 ASP.NET Core 2.1 和 EntityFrameworkCore 2.1.1 的 Web 应用程序。除了其他类型的测试,我正在使用InMemory Web API 测试对 Web.Api 进行某种集成测试。

由于测试数据相当复杂,我已经基于迁移的数据库的一部分生成了一个 JSON 文件,并且当内存测试开始时,测试正在从这个 JSON 文件中导入数据。

问题

只读测试工作得很好。但是,当我为具有标识 ( ValueGenerated == ValueGenerated.OnAdd) 的实体插入内容时,它会自动获得值 1(所有 PK 都是ints)。这是有道理的,因为任何生成器 EF 在幕后使用来生成这些值都没有被指示从某个值生成。

但是,对于生成现有键值的插入,这显然不能正常工作。

我尝试过的事情

  1. [当前工作解决方案]移动键值- 在反序列化所有对象后,我对所有相关实体执行 ids 移动操作(它们获得“大”值)。这可以正常工作,但容易出错(例如,某些实体具有静态 ID,我必须确保正确定义所有外键/导航属性,而且现在有点慢,因为我依靠反射来正确识别键 /需要移动的导航属性)

  2. 将值生成器配置为从大值开始

测试启动.cs

services.AddSingleton<IValueGeneratorCache, ValueGeneratorCache>();
services.AddSingleton<ValueGeneratorCacheDependencies, ValueGeneratorCacheDependencies>();

ID 生成功能

public const int StartValue = 100000000;

class ResettableValueGenerator : ValueGenerator<int>
{
    private int _current;

    public override bool GeneratesTemporaryValues => false;

    public override int Next(EntityEntry entry)
    {
        return Interlocked.Increment(ref _current);
    }

    public void Reset() => _current = StartValue;
}


public static void ResetValueGenerators(CustomApiContext context, IValueGeneratorCache cache, int startValue = StartValue)
{
    var allKeyProps = context.Model.GetEntityTypes()
        .Select(e => e.FindPrimaryKey().Properties[0])
        .Where(p => p.ClrType == typeof(int));
    var keyProps = allKeyProps.Where(p => p.ValueGenerated == ValueGenerated.OnAdd);

    foreach (var keyProperty in keyProps)
    {
        var generator = (ResettableValueGenerator)cache.GetOrAdd(
            keyProperty,
            keyProperty.DeclaringEntityType,
            (p, e) => new ResettableValueGenerator());

        generator.Reset();
    }
}

调试时,我可以看到我的实体正在迭代,因此应用了重置。

将数据推送到内存数据库中

private void InitializeSeeding(IServiceScope scope)
{
    using (scope)
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<CustomApiContext>();

            // this pushes deserialized data + static data into the database
            InitDbContext(context);

            var valueGeneratorService = services.GetRequiredService<IValueGeneratorCache>();
            ResetValueGenerators(context, valueGeneratorService);
        }
        catch (Exception ex)
        {
            var logger = services.GetService<ILogger<Startup>>();
            logger.LogError(ex, "An error occurred while seeding the database.");
        }
    }
}

实际插入

这是使用通用存储库完成的,但归结为:

Context.Set<T>().Add(entity);
Context.SaveChanges();

问题:如何在 Entity Framework Core 中控制内存中生成的键值?

标签: entity-framework-coreintegration-testingin-memory-database

解决方案


推荐阅读