【问题标题】:How can I create an Interface for my DbContext if I use EfCore.BulkExtensions如果我使用 EfCore.BulkExtensions,如何为我的 DbContext 创建接口
【发布时间】:2019-03-19 03:42:32
【问题描述】:

我正在开发一个使用 Entity Framework Core 2.0 的 ASP.Net Core 2.0 API。我正在尝试使用 XUnit 和 Moq 构建单元测试,但在为我的 DbContext 创建接口时遇到了问题,因此我可以在单元测试中模拟它。

目前,我的项目没有为我的上下文使用接口。我将它作为它的实现注入到我的存储库类中。在我的Startup.cs 中,我使用services.AddDbContext 进行设置。

典型存储库类构造函数的示例。

    public CompaniesRepository(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }

Startup.cs 示例

        services.AddDbContext<MyDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("MyDbConnectionString"),
            sqlOptions =>
            {
                sqlOptions.EnableRetryOnFailure(5,TimeSpan.FromSeconds(30),sqlTransientErrors);
            });
        });

而且这个方法一直这样工作得很好。

但是,现在我正在尝试设置单元测试,并且我希望能够模拟我的上下文,因此我需要为其创建一个接口。

所以,我向MyDbContext 添加了一个名为IMyDbContext 的接口,并将以下代码添加到我的Startup.cs,遵循blog post by Jerrie Pelser 中的建议

        services.AddScoped<IMyDbContext>(provider => provider.GetService<MyDbContext>());

这似乎有效,除了一个问题。我也在使用 Boris Djurdjevic 的EFCore.BulkExtensions NuGet,因此,我的存储库类出现编译错误,现在在其构造函数中注入了IMyDbContext 接口,说明我的接口不包含BulkInsert 的定义:

错误 CS1929“IMyDbContext”不包含“BulkInsert”的定义,并且最佳扩展方法重载“DbContextBulkExtensions.BulkInsert(DbContext, IList, BulkConfig, Action)”需要“DbContext”类型的接收器

我假设我需要以某种方式将BulkInsert 扩展方法添加到我的IMyDyBontext 接口,但我不确定如何正确执行此操作。如果我只是尝试将方法添加到我的界面,那么我会收到一条错误消息,说它没有在我的MyDbContext 类中实现,当然。

如何在MyDbContext 类中引用BulkInsert 扩展方法?

【问题讨论】:

  • 两件事:你不应该测试也不应该模拟 DbContext,而是使用 InMemoryDatabase。第二:存储库模式和 Entity Framework Core 更像是一种反模式。你只是把一切都复杂化了

标签: c# asp.net-core mocking entity-framework-core


【解决方案1】:

BulkInsert 是一种扩展方法。您不能轻松地模拟静态方法。

相反,您可以在 MyDbContext 类中实现它。然后,您可以模拟 IMyDbContext 进行单元测试。 注意:我没有测试它。

public interface IMyDbContext
{
    void BulkInsert<T>(IList<T> entities, BulkConfig bulkConfig = null,
        Action<decimal> progress = null) where T : class;

}

public class MyDbContext : DbContext, IMyDbContext
{
    public void BulkInsert<T>(IList<T> entities, BulkConfig bulkConfig = null,
        Action<decimal> progress = null) where T : class
    {
        this.BulkInsertOrUpdate(entities, bulkConfig, progress);
    }
}

【讨论】:

  • 这允许代码编译,但在实际调用 BulkInsert 方法时它会严重转储。我需要 BulkInsert,因为它可以显着提升性能,因此我可能需要重新考虑 xUnit/Moq 流程。正如 Camilo Terevinto 所提到的,我可以尝试新建一个 InMemory 上下文实例以插入我的 repo 类以进行单元测试。我会给你答案,因为它确实回答了我关于如何为我的 DbContext 创建接口的问题
【解决方案2】:

我最近在使用 EF Core BulkExtensions 包准备模拟我的 DbContext 以进行单元测试时遇到了这个问题。标记为正确答案的响应几乎正确(尽管他们确实说他们没有测试过他们的代码)。

当以上述方式在 DbContext 类中实现这一点时,您将在调用 BulkInsert 方法时收到 StackOverflowException。

所以,我开始调试并决定在 DbContext 类中的方法上放置一个断点,看看发生了什么。这让我理解了 StackOverflowException 的原因:由于“this”关键字的作用域,此方法调用自身而不是基础 DbContext 类上的方法。

解决方案相当简单,通过对 MyDbContext 实现中的 BulkInsert 方法实现进行以下更改来解决:

public async Task BulkInsertAsync<T>(IList<T> entities, BulkConfig bulkConfig = null, Action<decimal> progress = null) where T : class
{
    await ((DbContext)this).BulkInsertAsync<T>(entities, bulkConfig, progress);
}

另外,将以下行添加到您的 IMyDbContext 接口:

Task BulkInsertAsync<T>(IList<T> entities, BulkConfig bulkConfig = null, Action<decimal> progress = null) where T : class;

这确保了“this”的范围被强制转换为基本 DbContext 类类型,而不是我自己的 DbContext 实现的类型。因此,它不是调用自身,而是使用基 DbContext 类调用扩展方法。

使用此解决方案,您现在应该可以在代码中成功使用扩展方法,如下所示:

await _context.BulkInsertAsync<User>(users);

希望有人觉得这很有用。通过调试弄清楚并确保我可以在使用 BulkExtensions 包时轻松地保持所有方法可进行单元测试并不算太糟糕!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-21
    相关资源
    最近更新 更多