Не удается создать команды из нераскрытой базы данных

Я много искал, и я не могу найти ответы на это.

Я пишу приложение 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 я поставил это, чтобы посмотреть, не изменит ли это, но это не так.

Активность URL

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. Я удалил код DB из Activity, и все еще кажется, что это происходит. После запуска 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();

Я попытался сделать все async (не помогло), создание репозитория, соединение и где мы делаем CreateTableAsync async и до сих пор не повезло.

Ответы

Ответ 1

Спасибо за @Nkosi за его понимание и советы, которые были высоко оценены, но ни один из решений не работал.

После того, как вы спустили библиотеку sqlite.net-pcl (снова сохраненную OSS!) И отладив ее, казалось, что каждый раз, когда запускается моя Activity есть чек, чтобы проверить, было ли соединение открыто, а это не так, единственное место он был открыт для открытия, когда был построен SqliteConnection. Теперь, как я его написал, это был синглтон, но глупо мой Repository<T> реализовал IDisposable. Таким образом, мой контейнер IOC SqliteConnection от SqliteConnection но он никогда не воссоздавал его из-за того, что он был одиночным.

TL; DR удалил IDisposable реализацию в репозитории, потому что SqliteConnection был singleton.

Ответ 2

Вы совершаете синхронные блокирующие вызовы, такие как .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, но есть синхронные вызовы.

Опять же, это может вызвать тупик и не рекомендуется.

Код должен быть реорганизован для асинхронизации полностью, если намерение состоит в том, чтобы иметь отзывчивый интерфейс или использовать 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();
}

ОБНОВИТЬ

Также с точки зрения дизайна инициализация соединения должна быть инвертирована из репозитория и управляться извне (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);