我正在为单元测试创建 SQLite In Memory 数据库:
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new BloggingContext(options))
{
context.Database.EnsureCreated();
}
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("http://sample.com");
}
// Use a separate instance of the context to verify correct data was saved to database
using (var context = new BloggingContext(options))
{
Assert.AreEqual(1, context.Blogs.Count());
Assert.AreEqual("http://sample.com", context.Blogs.Single().Url);
}
}
context.Database.EnsureCreated(); 失败,异常: 消息:Microsoft.Data.Sqlite.SqliteException:SQLite 错误 1:'接近“MAX”:语法错误'。
有github问题说: 这里的问题是 varchar(max) 是 SqlServer 特定类型。脚手架不应将其添加为关系类型,它会传递给其他提供商的迁移,这很容易在迁移时生成无效的 sql。
但是如果我的数据库包含许多 varchar(max) 列,那么我如何在内存中使用 SQLite 进行单元测试呢?
我没有找到直接的解决方案,但已开始使用配置文件解决方法。您没有说明您是否正在使用 EF 配置,所以如果不需要,请原谅基础知识。
在您的 DbContext 中放置以下内容:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ChildrensSsiContext).Assembly);
}
现在创建一个如下所示的静态类。它将占用您的财产并处理配置。
internal static class ConfigurationHelper
{
internal static void ConfigureVarcharMax(PropertyBuilder<string> propertyBuilder, bool isRequired = true)
{
propertyBuilder
.IsRequired(isRequired)
//.HasColumnType("varchar(max)");
.HasColumnType("text");
}
}
为每个要配置的实体创建一个配置类
public class MyEntityWithVarcharMaxConfiguration
: IEntityTypeConfiguration<MyEntityWithVarcharMax>
{
public void Configure(EntityTypeBuilder<MyEntityWithVarcharMax> builder)
{
ConfigurationHelper.ConfigureVarcharMax(builder.Property(e => e.MyVarcharMaxProperty));
}
}
保留 HasColumnType("text") 未注释以进行测试。然后在添加迁移时注释该行并取消注释 HasColumnType("varchar(max)")。
你需要记住这样做很痛苦,但这是一个相当简单的解决方法。
我的解决方法:在 AppDbContext 中定义一个标志为真(在我的例子中,这意味着这是我的 SqlServer)。每当从测试项目初始化 AppDbContext 时,将标志设置为 false(因为我们使用 SqlLite 进行测试)。
最后,在 OnModelCreating 中为那些具有 nvarchar(max) 的实体检查标志,如果为假(意味着我正在运行测试)将那些 nvarchar(max) 属性的 ColumnTypes 设置为 Text。
在 AppDbContext 中:
public static bool IsSqlServer = true;
在 AppDbContext.OnModelCreating 中,为具有这些 nvarchar 属性的实体执行以下操作,将类型从 nvarchar(max) 更改为文本:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<YourType>( entity =>
{
if (!IsSqlServer)
entity.Property(YourPropertyName).HasColumnType("text");
}
}
最后,在您的测试项目初始化中,将标志设置为 false 以更改类型,这样它就不会失败。
AppDbContext.IsSqlServer = false;
在我对 Oracle 数据库的其他答案中可以找到https://stackoverflow.com/a/66203112/5786039,如果您正在使用迁移,则可以创建一种方法来修复 Sqlite 的迁移操作。
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using System.Linq;
public static class MigrationBuilderExtensions
{
public static void ConfigForSqlite(this MigrationBuilder migrationBuilder)
{
//For each table registered in the builder, let's change the type of nvarchar to TEXT
foreach (CreateTableOperation createTableOperation in migrationBuilder.Operations.ToArray().OfType<CreateTableOperation>())
{
foreach (var column in createTableOperation.Columns.Where(x => x.ColumnType.StartsWith("nvarchar", StringComparison.OrdinalIgnoreCase)))
{
if (column.ColumnType.Contains('(') && !column.ColumnType.Contains("MAX", StringComparison.OrdinalIgnoreCase))
column.MaxLength = int.Parse(column.ColumnType.Substring("nvarchar".Length + 1).Replace(")", ""));
column.ColumnType = "TEXT";
}
}
}
}
只需在 Migration.Up() 方法末尾调用扩展方法即可。
这样您就不需要为每个数据库提供者重新创建迁移,并且可以创建具有适当修复和条件的多数据库提供者设计。 或者...
但是,如果您不使用迁移,则可以使用相同的方法在运行时修复数据库模型。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Entity Configurations...
//Continue if db provider is Sqlite
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entity.GetProperties().Where(p => p.GetColumnType().StartsWith("nvarchar", StringComparison.OrdinalIgnoreCase)))
{
var columnType = property.GetColumnType();
if (columnType.Contains('(') && !columnType.Contains("MAX", StringComparison.OrdinalIgnoreCase))
property.SetMaxLength(int.Parse(columnType.Substring("nvarchar".Length + 1).Replace(")", "")));
property.SetColumnType("TEXT");
}
}
}