EF Core SQLite in memory exception: SQLite Error 1: 'near "MAX": syntax error'

问题描述 投票:0回答:3

我正在为单元测试创建 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 进行单元测试呢?

c# sqlite entity-framework-core ef-core-2.0 ef-core-2.1
3个回答
0
投票

我没有找到直接的解决方案,但已开始使用配置文件解决方法。您没有说明您是否正在使用 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)")。

你需要记住这样做很痛苦,但这是一个相当简单的解决方法。


0
投票

我的解决方法:在 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;

0
投票

在我对 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");
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.