如何在EF Core内存SQLite中测试一对多关系并初始化种子数据?
内存SQLite + EF Core MVC单元测试:一对多关系种子数据初始化与内存测试限制
一、一对多关系的种子数据初始化方法
针对ResourceRequest(主实体)和ResourceTags(从实体,一对多关联)的场景,初始化测试种子数据有两种实用方式,同时需要修正你现有测试代码中的关键问题:
方式1:从主实体关联子实体(贴合一对多语义)
先构建ResourceRequest实例,直接给它的ResourceTags集合添加标签,再将主实体存入上下文——EF会自动处理外键关联,无需手动设置ResourceId:
[TestMethod] public async Task AddResourceTags_SavesCorrectly() { using (var factory = new PrismContextFactory()) { // 准备测试数据 var resourceId = Guid.NewGuid(); var tagText = "tagtext"; var resourceRequest = new ResourceRequest { ID = resourceId.ToString(), SubscriptionID = "", TenantID = "", Name = "", ResourceGroup = "", ResourceTags = new List<ResourceTags> { new ResourceTags { Tag = tagText } } }; // 保存数据 using (var context = factory.CreateContext()) { context.Resources?.Add(resourceRequest); await context.SaveChangesAsync(); } // 验证数据 using (var context = factory.CreateContext()) { var tagCount = context.ResourceTags?.Count(); Assert.AreEqual(1, tagCount); var savedTag = await context.ResourceTags?.FirstOrDefaultAsync(r => r.Tag == tagText); Assert.IsNotNull(savedTag); Assert.AreEqual(resourceId.ToString(), savedTag.ResourceId); } } }
方式2:从子实体关联主实体(优化你的原写法)
如果要从ResourceTags反向关联ResourceRequest,需确保外键与主实体主键严格匹配,同时注意异步方法的正确使用:
[TestMethod] public async Task AddResourceTags_ViaChildEntity() { using (var factory = new PrismContextFactory()) { Random rnd = new Random(); var resourceId = new byte[32]; rnd.NextBytes(resourceId); var tagText = "tagtext"; var convertedId = Convert.ToBase64String(resourceId); var resourceTag = new ResourceTags { ResourceId = resourceId, Tag = tagText, ResourceRequest = new ResourceRequest { ID = convertedId, SubscriptionID = "", TenantID = "", Name = "", ResourceGroup = "" } }; using (var context = factory.CreateContext()) { context.ResourceTags?.Add(resourceTag); await context.SaveChangesAsync(); } using (var context = factory.CreateContext()) { var count = context.ResourceTags?.Count(); Assert.AreEqual(1, count); var savedTag = await context.ResourceTags?.FirstOrDefaultAsync(r => r.Tag == tagText); Assert.IsNotNull(savedTag); Assert.IsTrue(savedTag.ResourceId.SequenceEqual(resourceId)); } } }
关键修正点:
- 原测试中调用
FirstOrDefaultAsync未加await,断言的是Task对象而非实际实体,必须改用异步测试方法并等待异步查询完成。 - byte[]类型的主键/外键不能直接用
==比较,需用SequenceEqual判断内容是否一致。 - 确保主实体主键与子实体外键的类型、值完全匹配,否则EF无法正确关联。
二、内存SQLite测试的核心限制
特性支持不足:SQLite本身不支持部分EF Core及生产数据库的特性,比如:
- 嵌套事务、事务保存点等高级事务功能
- SQL Server专属函数(如
STRING_AGG、DATEFROMPARTS) Timestamp/RowVersion并发令牌的处理逻辑与生产库不一致- 全文搜索、空间数据等高级能力
数据生命周期绑定连接:内存SQLite的数据仅在连接存活期间存在,一旦连接关闭,所有数据丢失。必须让多个上下文实例共享同一连接才能复用测试数据。
数据类型差异:
DateTime精度仅到秒级,与SQL Server的毫秒/微秒精度存在差异decimal类型的精度、范围限制与生产数据库不同- 部分.NET类型(如
TimeSpan)的存储逻辑存在差异
并发测试无效:内存数据库是单进程单线程模型,无法模拟生产环境中的多线程并发操作,测试结果无法反映真实并发场景下的行为。
生产行为不一致风险:即使测试通过,由于SQLite与生产数据库(如SQL Server、PostgreSQL)的语法、执行计划、约束校验逻辑不同,仍可能在生产环境出现问题。
内容的提问来源于stack exchange,提问作者Tim Whatley
相关产品推荐
相关产品推荐

