问题 无法从未打开的数据库创建命令


我搜索了很多,我找不到任何答案。

我正在编写一个Xamarin Forms Mobile应用程序,似乎当我最小化应用程序然后重新打开它或我的一个活动启动时会抛出以下异常:

SQLiteConnection.CreateCommand (System.String cmdText, System.Object[] ps)
SQLite.SQLiteException: Cannot create commands from unopened database
SQLiteConnection.CreateCommand (System.String cmdText, System.Object[] ps)
TableQuery`1[T].GenerateCommand (System.String selectionList)
TableQuery`1[T].GetEnumerator ()
System.Collections.Generic.List`1[T]..ctor (System.Collections.Generic.IEnumerable`1[T] collection) [0x00062] in :0
Enumerable.ToList[TSource] (System.Collections.Generic.IEnumerable`1[T] source)
AsyncTableQuery`1[T].<ToListAsync>b__9_0 ()
Task`1[TResult].InnerInvoke ()
Task.Execute ()

这是我的代码:

通用存储库 (创建Sqlite实例的位置)

public class Repository<T> : IRepository<T> where T : Entity, new()
{
     private readonly SQLiteAsyncConnection _db;

    public Repository(string dbPath)
    {
        _db = new SQLiteAsyncConnection(dbPath);
        _db.CreateTableAsync<T>().Wait();
    }
}

国际奥委会注册

FreshIOC.Container.Register<IRepository<Settings>>(new Repository<Settings>(dbPath)); // FreshIOC is a wrapper around TinyIOC

在我的App.xaml.cs OnResume中

protected override void OnResume()
{
    SQLiteAsyncConnection.ResetPool();
}

以上用 ResetPool 我把它放进去看看它是否有所作为,但事实并非如此。

网址活动

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    var url = Intent.Data.ToString();
    var split = url.Split(new[] { "ombi://", "_" }, StringSplitOptions.RemoveEmptyEntries);
    if (split.Length > 1)
    {
        var dbLocation = new FileHelper().GetLocalFilePath("ombi.db3");
        var repo = new Repository<OmbiMobile.Models.Entities.Settings>(dbLocation);
        var settings = repo.Get().Result;
        foreach (var s in settings)
        {
            var i = repo.Delete(s).Result;
        }
        repo.Save(new Settings
        {
            AccessToken = split[1],
            OmbiUrl = split[0]
        });
    }

    Intent startup = new Intent(this, typeof(MainActivity));
    StartActivity(startup);
    Finish();
}

我不知道还能做什么或寻找什么,我似乎无法找到有关此类错误的任何信息。

更新:

经过更多调试后,似乎只有在Url活动完成后才会发生。 我已从活动中删除了数据库代码,但似乎仍然存在。一旦Activity启动了主要 App() 然后运行此代码:

var repo = FreshIOC.Container.Resolve<IRepository<Settings>>();
try
{
    Task.Run(async () =>
    {
        settings = (await repo.Get()).FirstOrDefault();
    }).Wait();
}
catch (Exception e)
{
    Debug.WriteLine(e.Message);
    throw;
}

这是错误发生的地方。它发生在 Get() 被称为调用 return _db.Table<T>().ToListAsync();

我已经尝试使所有异步(没有帮助),创建存储库,连接和我们在哪里 CreateTableAsync 异步,仍然没有运气。


8573
2018-05-24 07:24


起源

我可以知道你正在使用哪个版本的Sqlite,让我检查它是否已知这种类型的错误,因为你在这里没有做错任何事。 - Dharani Kumar
@DharaniKumar sqlite-net-pcl 包是版本 1.4.118 - Jamie Rees
我看了一下API,如果你的连接没有在create命令中打开就抛出了错误,并且关闭实时连接的唯一方法就是调用Dispose时。是否有可能发布StacK Trace,让我们看看是否可以获得任何内容。 - Dharani Kumar


答案:


你正在进行同步阻塞调用 .Wait() 和 .Result 与异步API混合时可能会导致死锁。

SQLiteAsyncConnection 本来是用来异步使用的。

一个常见的工作是创建事件处理程序,以允许进行异步非阻塞调用。

例如,在打电话时 CreateTableAsync 在存储库中

public class Repository<T> : IRepository<T> where T : Entity, new() {
     private readonly SQLiteAsyncConnection _db;

    public Repository(string dbPath) {
        _db = new SQLiteAsyncConnection(dbPath);
        createTable += onCreateTable; //Subscribe to event
        createTable(this, EventArgs.Empty); //Raise event
    }

    private event EventHandler createTable = delegate { };
    private async void onCreateTable(object sender, EventArgs args) {
        createTable -= onCreateTable; //Unsubscribe from event
        await _db.CreateTableAsync<T>(); //async non blocking call
    }

    //...
}

存储库抽象似乎具有异步API,但存在同步调用。

再次,这可能会导致死锁,不建议。

如果意图是具有响应式UI或使用,则需要将代码重构为异步 SQLite.Net非同步版本,用于进行同步调用。

将URL活动重构为异步将按照与上面相同的格式进行重构。

protected override void OnCreate(Bundle bundle) {
    base.OnCreate(bundle);
    creating += onCreateCore; //subscribe to event
    creating(this, EventArgs.Empty); //raise event
}

private event EventHandler creating = delegate { };
private async void onCreateCore(object sender, EventArgs args) {
    creating -= onCreateCore; //unsubscribe to event
    var url = Intent.Data.ToString();
    var split = url.Split(new[] { "ombi://", "_" }, StringSplitOptions.RemoveEmptyEntries);
    if (split.Length > 1) {
        var dbLocation = new FileHelper().GetLocalFilePath("ombi.db3");
        var repo = new Repository<OmbiMobile.Models.Entities.Settings>(dbLocation);
        var settings = await repo.Get();
        foreach (var s in settings) {
            var i = await repo.Delete(s);
        }
        repo.Save(new Settings {
            AccessToken = split[1],
            OmbiUrl = split[0]
        });
    }

    Intent startup = new Intent(this, typeof(MainActivity));
    StartActivity(startup);
    Finish();
}

UPDATE

同样从设计的角度来看,连接的初始化应该从存储库中反转出来并在外部进行管理(SRP)。

public interface ISQLiteAsyncProvider {
    SQLiteAsyncConnection GetConnection();
}

public class DefaultSQLiteAsyncProvider : ISQLiteAsyncProvider {
    private readonly Lazy<SQLiteAsyncConnection> connection;

    public DefaultSQLiteAsyncProvider(string path) {
        connection = new Lazy<SQLiteAsyncConnection>(() => new SQLiteAsyncConnection(path));
    }

    public SQLiteAsyncConnection GetConnection() {
        return connection.Value;
    }
}

使用的连接的异步延迟初始化的想法

/// <summary>
/// Provides support for asynchronous lazy initialization.
/// </summary>
/// <typeparam name="T"></typeparam>
public class LazyAsync<T> : Lazy<Task<T>> {
    /// <summary>
    ///  Initializes a new instance of the LazyAsync`1 class. When lazy initialization
    ///  occurs, the specified initialization function is used.
    /// </summary>
    /// <param name="valueFactory">The delegate that is invoked to produce the lazily initialized Task when it is needed.</param>
    public LazyAsync(Func<Task<T>> valueFactory) :
        base(() => Task.Run(valueFactory)) { }
}

这使得现在可以重构存储库以使用延迟初始化,这允许删除存储库中的事件处理程序

public class Repository<T> : IRepository<T> where T : Entity, new() {

    public Repository(ISQLiteAsyncProvider provider) {
        this.connection = new LazyAsync<SQLiteAsyncConnection>(await () => {
            var db = provider.GetConnection();
            await db.CreateTableAsync<T>();
            return db;
        });
    }

    private readonly LazyAsync<SQLiteAsyncConnection> connection;

    public async Task<List<T>> Get() {
        var _db = await connection.Value;
        return await _db.Table<T>().ToListAsync();
    }

    public async Task<T> Get(int id) {
        var _db = await connection.Value;
        return await _db.Table<T>().Where(x => x.Id == id).FirstOrDefaultAsync();
    }

    public async Task<int> Save(T entity) {
        var _db = await connection.Value;
        return entity.Id == 0 
            ? await _db.InsertAsync(entity) 
            : await_db.UpdateAsync(entity);
    }

    public async Task<int> Delete(T entity) {
        var _db = await connection.Value;
        return await _db.DeleteAsync(entity);
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            // get rid of managed resources
        }
        // get rid of unmanaged resources
    }
}

并注册了

// same instance should be used for other repositories
var provider = new DefaultSQLiteAsyncProvider(dbPath); 
var settingsRepository = new Repository<Settings>(provider);
FreshIOC.Container.Register<IRepository<Settings>>(settingsRepository);

7
2018-05-30 03:56



我用我的结果更新了OP,+1,因为无论如何这是一个好主意。 - Jamie Rees
@JamieRees可以完整地显示存储库。我怀疑设计和连接的创建。可能需要使用连接进行更多重构。我正在考虑将其抽象出来。 - Nkosi
使用存储库更新OP - Jamie Rees
@JamieRees我做了一些进一步的重构。 - Nkosi
似乎没有工作,我已经用我发现的更多信息再次更新了我的OP - Jamie Rees


感谢@Nkosi对他的见解和建议,非常感谢,但没有一个解决方案有效。

下拉sqlite.net-pcl库(再次由OSS保存!)并通过它进行调试后,似乎每次我的 Activity 启动时检查是否连接 打开 而事实并非如此,唯一一个被打开的地方就是 SqliteConnection 是 。现在我写它的方式,它是一个单身,但愚蠢的我 Repository<T>实施 IDisposable。所以我的IOC容器正在处理 SqliteConnection 但由于它是一个单身人士,它永远不会重新创造它。

TL; DR已删除 IDisposable 因为存储库上的实现 SqliteConnection 是一个单身人士。


4
2018-06-04 07:39