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