我可以使用 EF Core 5 InMemory 数据库进行集成测试吗

Posted

技术标签:

【中文标题】我可以使用 EF Core 5 InMemory 数据库进行集成测试吗【英文标题】:Can I Use EF Core 5 InMemory Database For Integration Testing 【发布时间】:2021-12-17 10:27:20 【问题描述】:

我正在从 net core 3.1 迁移到 net 5(EF Core 3 也迁移到 EF Core 5)。我们正在使用 EF Core 3 InMemory 数据库进行集成测试。迁移测试不再通过并且查询抛出错误后:

System.InvalidOperationException:LINQ 表达式 '形状查询表达式: 查询表达式: 内存查询表达式: 服务器查询表达式: InMemoryTableExpression:实体:RatingExclusionProduct .Where(valueBuffer => IsFalse(ValueBufferTryReadValue(valueBuffer, 0, 属性: RatingExclusion.Id (UUId) 必需 PK AfterSave:Throw).Equals(null)) && 对象.等于( objA:ExpressionExtensions.ValueBufferTryReadValue( 值缓冲:值缓冲, 指数:0, 属性:属性:RatingExclusion.Id (UUId) 必需 PK AfterSave:Throw), objB:ExpressionExtensions.ValueBufferTryReadValue( 值缓冲:值缓冲, 指数:0, 属性:属性:RatingExclusionProduct.ExclusionId (UUId) 必需 PK FK AfterSave:投掷))) 投影映射: 成员:EmptyProjectionMember 投影:EntityProjectionExpression: 属性:RatingExclusionProduct.ExclusionId (UUId) 必需 PK FK AfterSave:Throw -> ExpressionExtensions.ValueBufferTryReadValue( 值缓冲:值缓冲, 指数:0, 属性:属性:RatingExclusionProduct.ExclusionId (UUId) 必需 PK FK 保存后:投掷) 属性:RatingExclusionProduct.ProductId (UUId) 必需 PK FK 索引 AfterSave:Throw -> ExpressionExtensions.ValueBufferTryReadValue( 值缓冲:值缓冲, 指数:1, 属性:属性:RatingExclusionProduct.ProductId (UUId) 必需的 PK FK 索引 保存后:投掷) ,

ShaperExpression: EntityShaperExpression: 
        EntityType: RatingExclusionProduct
        ValueBufferExpression: 
            ProjectionBindingExpression: EmptyProjectionMember
        IsNullable: False

.AsQueryable()
.LeftJoin(
    inner: DbSet<ControlProduct>(), 
    outerKeySelector: o0 => EF.Property<UUId>(o0, "ProductId"), 
    innerKeySelector: c => EF.Property<UUId>(c, "Id"), 
    resultSelector: (o, i) => new TransparentIdentifier<RatingExclusionProduct, ControlProduct>(
        Outer = o, 
        Inner = i
    ))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly

通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”的调用, 或“ToListAsync”

我们能否以某种方式解决这个问题,或者我们需要切换到其他模拟数据库的方法?据我所知,发生了重大变化:

https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/breaking-changes#no-client-methods

我的问题与此有关吗?我在网上搜索过,有些人建议使用真实数据库进行测试,但我担心这会是一个非常缓慢的解决方案,而且我们有多个使用单个数据库的上下文,因此很难在测试开始时创建数据库。有些人建议创建上下文聚合(包含所有实体的单个上下文,仅用于创建数据库),但我不知道如何实现它。我认为我的问题看起来更像是后续问题的组合,但我对这个话题有点迷失了。非常感谢您的关注。

更新: UUId 是自定义类型,它被用作我们所有表的 PK。 为了解决它,我们使用:

    services
        .AddDbContext<RatingsContext>(opt =>
            opt.AddRelationalTypeMappingSourcePlugin<UUIdTypeMapperPlugin>()
                .Usemysql(connectionString, mso => mso
                    .ServerVersion(new Version(5, 7, 29), ServerType.MySql)
                    .EnableRetryOnFailure()
                )
        );

UUIdTypeMapperPlugin 在哪里:

public class UUIdTypeMapperPlugin : ITypeMappingSourcePlugin, IRelationalTypeMappingSourcePlugin

    public CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo)
    
        return mappingInfo.ClrType == typeof(UUId) ? new UUIdTypeMapper() : null;
    

    public RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
    
        return mappingInfo.ClrType == typeof(UUId) ? new UUIdTypeMapper() : null;
    

还有 UUIdTypeMapper:

public class UUIdTypeMapper : RelationalTypeMapping

    private static readonly ValueConverter<UUId, byte[]> _converter
        = new ValueConverter<UUId, byte[]>(uuid => uuid.ToByteArray(),
            byteArray => new UUId(byteArray));

    protected UUIdTypeMapper(RelationalTypeMappingParameters parameters) : base(parameters)
    
    

    public UUIdTypeMapper() : base(new RelationalTypeMappingParameters(
        new CoreTypeMappingParameters(typeof(UUId), _converter), "binary(16)")
    )
    
    

    protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
    
        return new UUIdTypeMapper(parameters);
    

    public override string GenerateSqlLiteral(object value)
    
        return $"0xvalue";
    

【问题讨论】:

什么是UUId?听起来像是用作 PK / FK 的自定义类型,很可能是导致问题的原因。 @IvanStoev 我已经更新了关于您的回复的问题 集成测试是什么意思?忘记“官方”定义——含义会根据您的观点而变化。内存提供程序旨在简化单元测试作为 low 保真模拟。它只是一个字典,因此无法模拟复杂的 SQL 操作。仅内存模式的 SQLite 提供程序更好,但 SQLite 过于受限,无法使其成为使用您想要的实际数据库产品进行测试的好选择。它甚至没有正确的数据类型 @PanagiotisKanavos 我们使用内存数据库来测试我们的 WebAPI 的控制器。在更新到 EF Core 5 之前一切正常(我们使用 EF Core 3 没有任何问题)。我知道这不是最好的解决方案,但我们最好需要一些便宜的解决方案 这不是我问的。 为什么您使用自定义 UUID 而不是 Guid?如果我们的 MySQL 提供程序不支持 GUID a) 将 Oracle 的提供程序替换为 Pomelo provider 并且 b) 映射到 Guid,请不要创建新的自定义类型。没有提供者知道如何处理您的自定义类型 【参考方案1】:

经过广泛研究,我发现 EF Core GitHub 上存在与我的问题相关的待处理问题,这是一个错误。

https://github.com/dotnet/efcore/issues/24318

我认为我们的团队应该转向在真实数据库上进行集成测试。有一个有用的链接: https://medium.com/nerd-for-tech/run-ef-core-integration-tests-in-a-repeatable-and-isolated-way-using-a-public-docker-image-with-a-e912a89c7bf4

【讨论】:

以上是关于我可以使用 EF Core 5 InMemory 数据库进行集成测试吗的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 XUnit 测试隔离 EF InMemory 数据库

text 设置EF InMemory数据库

ASP.NET Core 测试 - 在夹具中初始化 InMemory SQLite dbcontext 时获取 NullReferenceException

是否必须使用 .Include 加载 EF Core 5 中的子对象?

具有不同列名的 EF Core 5 HasForeignKey 问题

EF Core 5 与 Visual Studio for Mac