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

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

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

我正在编写一个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异步,仍然没有运气。

c# .net sqlite dependency-injection xamarin.forms
3个回答
7
投票

您正在进行.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);

5
投票

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

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

TL; DR删除了存储库中的IDisposable实现,因为SqliteConnection是一个单例。


1
投票

我有同样的错误,但不是由于Disposable实现。由于某些未知的原因,如果我有以下内容它会破坏:

lock (locker)
{
    foreach (var item in database.Table<DBItems>()) //It broke on this line
    {
        //...
    }
}

所以我改行了

foreach (var item in database.Table<DBItems>().ToList()) //Notice the "ToList()"

问题解决了...

© www.soinside.com 2019 - 2024. All rights reserved.