如何解决SQLite 内存数据库使用时态表测试 EF Core 应用程序
我们在 Entity Framework Core 应用程序中使用系统版本控制的时态表。这非常有效,但我们在创建测试时遇到了问题。
我一直按照本指南使用 sqlite 内存数据库来测试 Microsoft 的 EF Core 应用程序。
https://docs.microsoft.com/en-us/ef/core/testing/sqlite#using-sqlite-in-memory-databases
问题是 sqlite
会为 SysstartTime
抛出异常。这是意料之中的,因为该属性在 prop.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAddOrUpdate;
中被标记为 DbContext
,并且通常由 sql Server 处理。有没有办法在 sqlite 中完成这项工作?
sqliteException:sqlite 错误 19:'NOT NULL 约束失败: User.SysstartTime'。
用户:
public class User : IEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime SysstartTime { get; set; }
public DateTime SysEndTime { get; set; }
[required]
public string ExternalId { get; set; }
}
xUnit 测试:
public class QuestionUpdateTest: Idisposable
{
private readonly DbConnection _connection;
private readonly ApplicationDbContext _context = null;
public ChoiceSequencingQuestionUpdatetest()
{
var dbContextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.Usesqlite(CreateInMemoryDatabase())
.Options;
_connection = RelationalOptionsExtension.Extract(dbContextOptions).Connection;
_context = new ApplicationDbContext(dbContextOptions);
_context.User.Add(new User()
{
ExternalId = "1"
});
_context.SaveChangesNoUser();
}
private static DbConnection CreateInMemoryDatabase()
{
var connection = new sqliteConnection("Filename=:memory:");
connection.open();
return connection;
}
public void dispose() => _connection.dispose();
[Fact]
public void Test2()
{
}
}
ApplicationDbContext:
public int SaveChangesNoUser()
{
//Wont help since the property is marked as ValueGenerated
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.SysstartTime = DateTime.Now;
break;
}
}
}
return base.SaveChanges();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(string)))
{
if (property.GetMaxLength() == null)
property.SetMaxLength(256);
}
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(DateTime)))
{
property.SetColumnType("datetime2(0)");
}
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
foreach (var prop in et.GetProperties())
{
if (prop.Name == "SysstartTime" || prop.Name == "SysEndTime")
{
prop.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAddOrUpdate;
}
}
}
base.OnModelCreating(modelBuilder);
}
迁移:
public partial class Temporaltablesforallentities : Migration
{
List<string> tablesToUpdate = new List<string>
{
"User",};
protected override void Up(MigrationBuilder migrationBuilder)
{
foreach (var table in tablesToUpdate)
{
string alterStatement = $@"ALTER TABLE [{table}]
ADD PERIOD FOR SYstem_TIME ([SysstartTime],[SysEndTime])";
migrationBuilder.sql(alterStatement);
alterStatement = $@"ALTER TABLE [{table}]
SET (SYstem_VERSIONING = ON (HISTORY_TABLE = History.[{table}],DATA_CONSISTENCY_CHECK = ON));";
migrationBuilder.sql(alterStatement);
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
foreach (var table in tablesToUpdate)
{
string alterStatement = $@"ALTER TABLE [{table}] SET (SYstem_VERSIONING = OFF);";
migrationBuilder.sql(alterStatement);
alterStatement = $@"ALTER TABLE [{table}] DROP PERIOD FOR SYstem_TIME";
migrationBuilder.sql(alterStatement);
alterStatement = $@"DROP TABLE History.[{table}]";
migrationBuilder.sql(alterStatement);
}
}
}
解决方法
在protected override void OnModelCreating(ModelBuilder modelBuilder)
中是这样解决的:
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
foreach (var prop in et.GetProperties())
{
if (prop.Name == "SysStartTime" || prop.Name == "SysEndTime")
{
if (Database.IsSqlServer())
{
prop.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAddOrUpdate;
}
else
{
prop.SetDefaultValue(DateTime.Now);
}
}
}
}
,
尝试从您的模型中删除 SysStartTime 和 SysEndTime。您可以使用以下代码段添加它们:
创建一个 Constants.cs 或类似的文件:
public const string AddSystemVersioningFormatString = @"
ALTER TABLE [dbo].[{0}]
ADD SysStartTime datetime2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL
CONSTRAINT DF_{0}_SysStartTime DEFAULT SYSUTCDATETIME(),SysEndTime datetime2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
CONSTRAINT DF_{0}_SysEndTime DEFAULT CONVERT(datetime2,'9999-12-31 23:59:59.9999999'),PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)
ALTER TABLE [dbo].[{0}]
SET (SYSTEM_VERSIONING = ON (
HISTORY_TABLE = [dbo].[{0}History],DATA_CONSISTENCY_CHECK = ON )
)";
public const string RemoveSystemVersioningFormatString = @"
ALTER TABLE [dbo].[{0}] SET (SYSTEM_VERSIONING = OFF)
ALTER TABLE [dbo].[{0}] DROP PERIOD FOR SYSTEM_TIME
ALTER TABLE [dbo].[{0}] DROP CONSTRAINT DF_{0}_SysStartTime
ALTER TABLE [dbo].[{0}] DROP CONSTRAINT DF_{0}_SysEndTime
ALTER TABLE [dbo].[{0}] DROP COLUMN SysStartTime,SysEndTime
DROP TABLE IF EXISTS [dbo].[{0}History]
";
然后在您的迁移中:
migrationBuilder.Sql(string.Format(Constants.AddSystemVersioningFormatString,"User"));
因此您的模型不会知道额外的列,并且您不必在 EF 中显式设置任何内容,因为 SQL Server 将为您处理所有设置。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。