EF Core SQLite内存中发生异常:SQLite错误1:“near 'MAX':syntax error”

14

我正在创建用于单元测试的SQLite内存数据库:

        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(); 在执行时抛出异常:Message: Microsoft.Data.Sqlite.SqliteException : SQLite Error 1: 'near "MAX": syntax error'.

github issue 中有这样的说法: 问题在于 varchar(max) 是 SqlServer 特定的类型。脚手架不应将其添加为关系型类型,因为这会导致将其传递到其他提供程序的迁移中生成无效的 SQL。

那么,如果我的数据库包含许多 varchar(max) 列,我如何在内存中使用 SQLite 进行单元测试呢?


如果您正在使用EF Core,为什么不使用内存提供程序而不是SQLite呢? - DavidG
@DavidG 我正在使用“内存SQLite数据库”链接,因为我需要测试事务。 - Deivydas Voroneckis
如果您使用'Migrate'而不是'EnsureCreated'呢? - Silvermind
@Silvermind 我尝试了这个方法,但是我遇到了另一个异常。当我调用 _offerContext.Set<Aggregate>().FindAsync(id); 时,我会得到异常:typeof(Microsoft.Data.Sqlite.SqliteException): SQLite Error 1: 'no such table: Offers'. 而不是 null。如果我传递的 id 对应的实体存在,它会返回正确的对象。但是我想测试当我找不到实体时的行为。 - Deivydas Voroneckis
4个回答

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中定义一个标志作为true(在我这里,表示这是用于我的SqlServer)。每当Test项目初始化AppDbContext时,将标志设置为false(因为我们使用SqlLite进行测试)。
最后,在具有nvarchar(max)的实体的OnModelCreating中检查标志,如果为false(表示我正在运行测试),则将这些nvarchar(max)属性的ColumnTypes设置为Text。
在AppDbContext中:
public static bool IsSqlServer = true;

在 AppDbContext.OnModelCreating 中,对于具有 nvarchar 属性的实体,请执行以下操作将类型从 nvarchar(max) 更改为 text:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Entity<YourType>( entity => 
   {
      if (!IsSqlServer)
         entity.Property(YourPropertyName).HasColumnType("text");
   }
}

最后,在您的测试项目中进行初始化时,请将标志设置为false以更改类型,使其不会失败。

AppDbContext.IsSqlServer = false;

0

如同我在另一个针对Oracle数据库的回答中所提到的https://dev59.com/gLzpa4cB1Zd3GeqPT-mC#66203112,如果你正在使用迁移工具,你可以创建一个方法来修复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");
        }
    }
}

我希望这个方法能够起作用,但是EF Core不再支持在模型完全构建之前检索模型元数据。请参考以下问题了解详情:https://github.com/dotnet/efcore/issues/8034 - undefined
@AsbjørnUlsberg 我无法理解这个问题的要点。在 OnModelCreating 方法中可能会调用 modelBuilder.ApplyConfiguration()modelBuilder.ApplyConfigurationsFromAssembly(),在操作之前设置模型的关系和设置,那么问题在哪里呢?我最近使用旧版本的 EF Core 和 7.0.2 多次采用这种方法,没有出现任何问题。 - undefined
问题可能是因为我不知道在检索模型元数据之前需要调用modelBuilder.ApplyConfiguration()。我记不太清楚了,但我想我最终找到了另一种解决方案,不会再重新访问这个问题。 - undefined
1
@AsbjørnUlsberg 很高兴知道这一点。一定要告诉我们你是如何成功让它工作的,如今对于了解 EF Core 的解决方法是没有限制的。 - undefined

0
在项目属性窗口中,转到您的表格配置所在的项目的“构建”选项卡。 在这里您会看到以下内容:
 builder.Property<string>(nameof(BigTextColumn))
               .HasColumnType("nvarchar(max)");

在"条件编译符号"字段中添加符号SQLITE并保存。
在表配置文件中,使用这个代替。
#if SQLITE
            builder.Property<string>(nameof(BigTextColumn))
               .HasColumnType("text");
#else
            builder.Property<string>(nameof(BigTextColumn))
               .HasColumnType("nvarchar(max)");
#endif

这应该解决类型不兼容的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接