代码之家  ›  专栏  ›  技术社区  ›  Jamie Rees

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

  •  12
  • Jamie Rees  · 技术社区  · 7 年前

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

    我正在编写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代码,但似乎仍然会发生这种情况。一旦活动启动 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 回复  |  直到 7 年前
        1
  •  7
  •   Nkosi    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,但仍有同步调用。

    同样,这会导致僵局,不建议这样做。

    如果想要有一个响应性的用户界面或使用,则需要对代码进行重构,以便一直保持异步状态。 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);
    
        2
  •  5
  •   Jamie Rees    7 年前

    感谢@nkosi的洞察力和建议,这是非常感谢,但没有任何解决方案起作用。

    在拉下sqlite.net-pcl库后(再次被oss保存!)通过调试,每次我 Activity 启动。检查连接是否 open 它不是,唯一被打开的地方是 SqliteConnection constructed . 按照我写的方式,这是一首单曲,但愚蠢的是 Repository<T> 实施 IDisposable . 所以我的国际奥委会集装箱正在处理 sqliteConnection(sqliteConnection) 但由于它是一个独身者,它从来没有重新创建过。

    删除了tl;dr 可识别 在存储库上实现,因为 sqliteConnection(sqliteConnection) 是单身汉。

        3
  •  1
  •   Pierre    7 年前

    我也犯了同样的错误,但不是因为 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()"
    

    问题解决了…

    推荐文章