(此文章同时发表在本人微信公众号“dotNET每日精华文章”,欢迎右边二维码来关注。)

题记:在用EF Core的内存数据库进行单元测试的时候遇到“无法访问已释放的对象”的错误怎么办?

之前在EF Core 1.0中使用Include的小技巧中简单谈到了使用EF Core内存数据库进行单元测试的方法。不过这个方法有个小问题,就是容易出现“无法访问已释放的对象”的错误。

在之前的示例代码中(http://git.oschina.net/ike/codes/jtu9dnsk3pe6x24clbq50),单元测试能够顺利通过,是因为db和db2两个实例对象并没有放到using里面让其自动disposed掉。可在大部分情况下,正确的写法应该是使用using的,就算不使用using,如果一次性执行多个测试,那么就会遇到这个“无法访问已释放的对象”错误。

根据这里的讨论(https://github.com/aspnet/EntityFramework/issues/4092),这一错误的根源是EF Core的内存数据库设计出来的行为和真实的关系数据库是一致的,同时又受到ServiceProvider作用域的控制。换句话说,就是在同一个ServiceProvider之下,内存数据库的数据和关系数据库的数据一样是保持“持久”的(当然是在内存中)。由此导致了,使用内存数据库进行单元测试,需要考虑两种情况:

1,单元测试涉及的每次数据操作,数据都完全隔离。

这种情况下,可以采用官方的文档(https://github.com/aspnet/EntityFramework.Docs/issues/95)中的说明,实例化或者注入ServiceCollection,然后每次操作数据库都要通过新的ServiceProvider获得DbContext实例。具体代码如下:

using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq; namespace ConsoleApp1
{
public class Program
{
public static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection
.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<SampleContext>(c => c.UseInMemoryDatabase()); using (var db = serviceCollection.BuildServiceProvider().GetService<SampleContext>())
{
db.Blogs.Add(new Blog { Url = "Test" });
db.SaveChanges();
Console.WriteLine(db.Blogs.Count());
} using (var db = serviceCollection.BuildServiceProvider().GetService<SampleContext>())
{
db.Blogs.Add(new Blog { Url = "Test" });
db.SaveChanges();
Console.WriteLine(db.Blogs.Count());
}
}
} public class SampleContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
} public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

2,单元测试涉及的每次数据操作,数据需要模拟真实的唯一数据库。

详细说来,就是我需要在一个TestFixture中,先插入一些示例数据,然后随后的每个单元测试都以这些示例数据为准。在这种情况下,为了避免遇到“无法访问已释放的对象”,就需要通过统一的ServiceProvider(上面已经解释了为什么需要统一的)来获得在Scoped中分别获取DbContext以避免之前的DbContext被释放。这种方式,官方也有参考的代码,具体我就不贴了,见(https://github.com/aspnet/MusicStore/blob/dev/src/MusicStore/Models/SampleData.cs)。下面是我给出的一个示例代码(代码片段见:http://git.oschina.net/ike/codes/t069no34dfu8p2zeahrsv):

    public class EFCoreInMemoryTest
{
[Fact]
public async Task WillSuccessWithWrongApproach()
{
var serviceCollection = new ServiceCollection();
serviceCollection
.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MarketDbContext>(options => options.UseInMemoryDatabase()); var serviceProvider = serviceCollection.BuildServiceProvider(); var db = serviceProvider.GetRequiredService<MarketDbContext>();
SampleData.Create(db); var db2 = serviceProvider.GetRequiredService<MarketDbContext>(); var products = await db2.Products.ToListAsync(); Assert.Equal(3, products.Count); var promotions = await db2.Promotions.ToListAsync(); Assert.Equal(2, promotions.Count);
} [Fact]
public async Task WillFaild()
{
var serviceCollection = new ServiceCollection();
serviceCollection
.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MarketDbContext>(options => options.UseInMemoryDatabase()); var serviceProvider = serviceCollection.BuildServiceProvider(); using (var db = serviceProvider.GetRequiredService<MarketDbContext>())
{
SampleData.Create(db);
} using (var db2 = serviceProvider.GetRequiredService<MarketDbContext>())
{
var products = await db2.Products.ToListAsync(); Assert.Equal(3, products.Count); var promotions = await db2.Promotions.ToListAsync(); Assert.Equal(2, promotions.Count);
} } [Fact]
public async Task WillSuccessWithRightApproach()
{
var serviceCollection = new ServiceCollection();
serviceCollection
.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MarketDbContext>(options => options.UseInMemoryDatabase()); var serviceProvider = serviceCollection.BuildServiceProvider(); DoDbActionInScoped(serviceProvider, (db) =>
{
SampleData.Create(db);
}); await DoDbActionInScopedAsync(serviceProvider, async (db2) =>
{
var products = await db2.Products.ToListAsync(); Assert.Equal(3, products.Count); var promotions = await db2.Promotions.ToListAsync(); Assert.Equal(2, promotions.Count);
});
} private void DoDbActionInScoped(IServiceProvider serviceProvider, Action<MarketDbContext> action)
{
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var db = serviceScope.ServiceProvider.GetRequiredService<MarketDbContext>())
{
action(db);
}
}
} private async Task DoDbActionInScopedAsync(IServiceProvider serviceProvider, Func<MarketDbContext, Task> action)
{
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var db = serviceScope.ServiceProvider.GetRequiredService<MarketDbContext>())
{
await action(db);
}
}
}
}

再谈EF Core内存数据库单元测试问题的更多相关文章

  1. 浅谈 EF CORE 迁移和实例化的几种方式

    出于学习和测试的简单需要,使用 Console 来作为 EF CORE 的承载程序是最合适不过的.今天笔者就将平时的几种使用方式总结成文,以供参考,同时也是给本人一个温故知新的机会.因为没有一个完整的 ...

  2. 浅谈.Net Core后端单元测试

    目录 1. 前言 2. 为什么需要单元测试 2.1 防止回归 2.2 减少代码耦合 3. 基本原则和规范 3.1 3A原则 3.2 尽量避免直接测试私有方法 3.3 重构原则 3.4 避免多个断言 3 ...

  3. [翻译] EF Core in Action 关于这本书

    Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...

  4. 深入理解 EF Core:EF Core 读取数据时发生了什么?

    阅读本文大概需要 11 分钟. 原文:https://bit.ly/2UMiDLb 作者:Jon P Smith 翻译:王亮 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的.其中可能 ...

  5. 深入理解 EF Core:EF Core 写入数据时发生了什么?

    阅读本文大概需要 14 分钟. 原文:https://bit.ly/2C67m1C 作者:Jon P Smith 翻译:王亮 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的.其中可能 ...

  6. 从一张图开始,谈一谈.NET Core和前后端技术的演进之路

    从一张图开始,谈一谈.NET Core和前后端技术的演进之路 邹溪源,李文强,来自长沙.NET技术社区 一张图 2019年3月10日,在长沙.NET 技术社区组织的技术沙龙<.NET Core和 ...

  7. 如何使用ASP.NET Core、EF Core、ABP(ASP.NET Boilerplate)创建分层的Web应用程序(第一部分)

    本文是为了学习ABP的使用,是翻译ABP官方文档的一篇实战教程,我暂时是优先翻译自己感兴趣或者比较想学习的部分,后续有时间希望能将ABP系列翻译出来,除了自己能学习外,有可能的话希望帮助一些英文阅读能 ...

  8. EF Core中避免贫血模型的三种行之有效的方法(翻译)

    Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...

  9. 【ASP.NET Core】EF Core - “影子属性” 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1

    [ASP.NET Core]EF Core - “影子属性”   有朋友说老周近来博客更新较慢,确实有些慢,因为有些 bug 要研究,另外就是老周把部分内容转到直播上面,所以写博客的内容减少了一点. ...

随机推荐

  1. JQuery知识点链接

    1.深入理解jQuery插件开发                      http://learn.jquery.com/plugins/basic-plugin-creation/ 2.jQuer ...

  2. [转]Java 征途:行者的地图

    前段时间应因缘梳理了下自己的 Java 知识体系, 成文一篇望能帮到即将走进或正在 Java 世界跋涉的程序员们. 第一张,基础图 大约在 2003 年我开始知道 Java 的(当时还在用 Delph ...

  3. Contains Duplicate III

    Given an array of integers, find out whether there are two distinct indices i and j in the array suc ...

  4. Reorder array to construct the minimum number

    Construct minimum number by reordering a given non-negative integer array. Arrange them such that th ...

  5. C#之系统自带保存属性

    源代码下载链接 程序开发很多时候需要根据运行环境做不通的参数配置,通过写ini之类的文本文件是一种方法,但这种方法也同时会把数据暴露 Winform开发中可以将需要配置的字段属性保存到程序中(其实也是 ...

  6. poj 1326

    http://poj.org/problem?id=1326 一个模拟的水题 题意就是要你算飞行的英里数. F代表头等舱,为实际飞行的英里数的2倍. B为商务舱,为实际飞行的英里数的1.5倍. Y为经 ...

  7. mysql中表名是order的CRUD的错误

    org.springframework.jdbc.BadSqlGrammarException: ### Error querying database. Cause: com.mysql.jdbc. ...

  8. Unity3d 残影效果(狂拽炫酷叼炸天)

    效果图,真的很叼啊 我根据别人的改进了一版,支持MeshFilter上的Mesh(需要确保Mesh的Read/Write是开启的否则不能正常工作) 非常感谢原作者给提供思路.http://blog.c ...

  9. 右下角浮窗&动画效果

    2015-07-17 11:07:57 CSS #goreg { width: 70px; position: fixed; bottom: 20px; right: 25px; ; opacity: ...

  10. js闭包问题

    function picLinkInit(parentClassName, imgW, imgH, childClassObjs) { var $match = $(parentClassName); ...