没有这样的表 - 使用Sqlite内存模式的EF Core

21

我正在尝试搭建我的测试环境,但Sqlite适配器出现了问题。每个创建的上下文都具有相同的连接,因此内存数据库应该为每个上下文正确构建。

但是当我尝试添加新内容时,它会抛出错误:“没有这样的表:% here_is_my_tablename%”。

我认为我的配置应该是好的。

Base:

public abstract class BaseServiceTests : IDisposable
{
    protected readonly SqliteConnection Connection;

    public BaseServiceTests()
    {
        Connection = new SqliteConnection("DataSource=:memory:");
        Connection.Open();
        Assert.NotNull(Connection);

        using (var ctx = BuildCoreContext())
        {
            ctx.Database.EnsureCreated();
        }   

        using (var ctx = BuildWebContext())
        {
            ctx.Database.EnsureCreated();
        }     
    }

    public void Dispose()
    {
        Connection.Close();
    }

    public DbContextOptions<TContext> Options<TContext>() where TContext: DbContext
    {
        var options = new DbContextOptionsBuilder<TContext>()
            .UseSqlite(Connection)
            .Options;

        return options;
    }

    public ServiceRequestCoreContext BuildCoreContext()
    {
        var ctx = new ServiceRequestCoreContext(Options<ServiceRequestCoreContext>(), null);
        ctx.Database.OpenConnection();

        return ctx;
    }

    public ServiceRequestWebContext BuildWebContext()
    {
        var ctx = new ServiceRequestWebContext(Options<ServiceRequestWebContext>(), null);
        ctx.Database.OpenConnection();

        return ctx;
    }
}

测试

public class RequestServiceTests : BaseServiceTests
{
    public async Task Prepare()
    {
        using (var ctx = BuildCoreContext())
        {
            await ctx.RequestTypes.AddAsync(new RequestType((int)RequestTypes.Order, "TestType - test"));
            await ctx.RequestStatuses.AddAsync(new RequestStatus((int)RequestStatuses.AcceptedForVeryfication, "test - test", "test - test"));
            await ctx.Companies.AddAsync(new CustomerCompany(1, "test - test", "Test - test"));
            await ctx.SaveChangesAsync();
        }
    }

    [Fact]
    public async Task when_creating_new_request_it_should_not_be_null()
    {
        //Arrange 
        await Prepare();
        var customerId = 1;
        var iGen = new IdentifyGenerator();

        //Act
        using (var webCtx = BuildWebContext())
        {
            webCtx.Database.OpenConnection();
            var service = new RequestService(webCtx, BuildCoreContext(), iGen);
            await service.CreateAsync(customerId);
            await webCtx.SaveChangesAsync();
        }

        //Assert
        using (var ctx = BuildWebContext())
        {
            ctx.ServiceRequests.Should().HaveCount(1);
            ctx.ServiceRequests.FirstOrDefault().Should().NotBeNull();                
        }
    }
}

1
我认为问题是两个不同的上下文处理相同的SqlConnection。这会导致问题吗? - bielu000
是的,在多连接的情况下,您需要使用共享缓存,因此您的连接字符串应该如下所示:"DataSource=file::memory:?cache=shared" - Shahin Dohan
2个回答

25
为了回答OP的问题,我相信问题在于多个上下文访问数据库而没有共享缓存。请将您的连接字符串更改为:"DataSource=file::memory:?cache=shared"
对于从Google搜索进来的人,请记住以下几点:
- 至少有一个连接必须保持打开状态以便访问数据库。 - 如果将从多个连接访问数据库,则必须使用共享缓存,具体如下:string connectionString = "DataSource=file::memory:?cache=shared"; - 如果使用ADO.NET,则官方提供的SQLite提供程序与Entity Framework不兼容。即使我已经正确配置了所有内容,但在测试时遇到“没有这样的表”错误,但一旦我从System.Data.SQLite.SQLiteConnection(System.Data.SQLite.Core nuget package)更改为Microsoft的提供程序:Microsoft.Data.Sqlite.SqliteConnection,错误就消失了。 - 官方的Sqlite ADO.NET适配器比Microsoft的适配器快2倍!因此,除非你绝对必须这么做,否则最好使用官方的Sqlite适配器。
示例代码:
[Fact]
public async Task MyTest()
{
    var dbContext = new MyDbContext("DataSource=file:memdb1?mode=memory&cache=shared");

    try
    {
        await dbContext.Database.OpenConnectionAsync();
        // Test your dbContext methods, which has an open connection to the in-memory database
        // If using ADO.NET to make new connections, use Microsoft.Data.Sqlite.SqliteConnection
    }
    finally
    {
        await dbContext.Database.CloseConnectionAsync();
    }
}

也要记得阅读文档! :)

更新:为了更轻松地使用内存数据库进行测试,我创建了一个TestContext,它继承自我的DbContext以便自动处理连接和数据库创建:(这次使用NUnit)

public sealed class TestContext : UsersContext
{
    public TestContext(string connectionString) : base(connectionString)
    {
        Database.OpenConnection();
        Database.EnsureCreated();
    }

    public override void Dispose()
    {
        Database.CloseConnection();
        base.Dispose();
    }
}

使用方法:

[Test]
public void AddingUsersShouldIncrementId()
{
    using (var dbContext = new TestContext("DataSource=file::memory:?cache=shared"))
    {
        dbContext.UsersDbSet.Add(new UserEntity());
        dbContext.SaveChanges();

        uint actualId = dbContext.Users.Single().Id;
        Assert.AreEqual(1, actualId);
    }
}

"官方的 Sqlite ADO.NET 适配器比微软的快两倍!所以除非你绝对必须使用,否则最好使用官方的 Sqlite 适配器。" 这个说法在2023年还有效吗?你有相关的来源吗? - JHBonarius
@JHBonarius 那只是我当时的观察,如果我没记错的话,可能比2倍少一点,但差异仍然很明显。不知道这在2023年是否仍然成立 :) - Shahin Dohan
我在一个连接上遇到了这个错误,我首先想问的问题是,我是否需要初始创建我的模式?或者有没有一些自动魔法可以创建我的内存模式? - Morgeth888
正在使用EFCore,并且我正在运行Database.EnsureCreated(); - Morgeth888

5

SQLite内存数据库只在连接打开时存在。您可以使用不同的DbContext,但如果关闭了连接,则内存数据库将被删除。

示例


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