Невозможно создать команды из неоткрытой базы данных - PullRequest
0 голосов
/ 24 мая 2018

Я много раз искал и не могу найти ответы на этот вопрос.

Я пишу мобильное приложение Xamarin Forms, кажется, что я минимизирую приложение и затем снова открываю его или одно из моихзапускаются действия, возникает следующее исключение:

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 Activity

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 асинхронно, но все равно не повезло.

Ответы [ 3 ]

0 голосов
/ 04 июня 2018

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

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

TL; DR удалил реализацию IDisposable в хранилище, поскольку SqliteConnection был одноэлементным.

0 голосов
/ 24 апреля 2019

У меня была такая же ошибка, но не из-за реализации 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()"

Проблема решена ...

0 голосов
/ 30 мая 2018

Вы делаете синхронные вызовы блокировки, такие как .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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...