如何在Xamarin Android中设置SQLite(SQLite.NET组件)数据库版本

时间:2015-06-15 07:39:21

标签: c# android sqlite xamarin xamarin.android

我想在 Xamarin(Monodroid)中创建 sqlite(SQLite.NET组件)数据库的版本,以及如何处理数据库的版本在表中的任何修改和playstore中的更新期间。我如何获得像Android原生SQLiteOpenHelper类的OnUpgrade功能。

我正在使用以下方式

string folder = Environment.GetFolderPath   (Environment.SpecialFolder.Personal);
var conn = new SQLiteConnection (System.IO.Path.Combine (folder, "stocks.db"));
conn.CreateTable<Stock>();
conn.CreateTable<Valuation>();

2 个答案:

答案 0 :(得分:7)

我不熟悉“SQLiteOpenHelper”,但我最近为Android应用程序构建了一个SQLite“迁移/升级”管理器。它可以与IOS一起使用,也可以使用SQLITE-NET PCL。我愿意听听你对此的看法。可能有更好的方法,但这是我的方法:

  1. 我的数据库有一些需要升级的主表(不可变数据)
  2. 其他表只是用户数据,外键引用 主要的数据。这些表通常不会更新。
  3. 升级/迁移基本上会修改主数据,尊重 当前用户数据
  4. 请允许我用实际案例解释一下:

    1. 主数据将是“成分”
    2. 用户数据表为“Stock”
    3. 当应用升级时,可以更新/升级成分表
    4. 用户无法修改Ingredients表,因为它是主数据
    5. 我的代码仍然不完美,因为我应该在事务中运行每次迁移,但它在我的TODO列表中:)

      应用程序启动时调用“DatabaseMigrationService.RunMigrations()”:

      public interface IMigrationService
      {
          Task RunMigrations();
      }
      
      public interface IMigration
      {
          IMigration UseConnection(SQLiteAsyncConnection connection);
          Task<bool> Run();
      }
      

      <强> DatabaseMigrationService

      public sealed class DatabaseMigrationService : IMigrationService
          {
              private ISQLite sqlite;
              private ISettingsService settings;
              private List<IMigration> migrations;
      
              public DatabaseMigrationService(ISQLite sqlite, ISettingsService settings)
              {
                  this.sqlite = sqlite;
                  this.settings = settings;
      
                  SetupMigrations();
              }
      
              private void SetupMigrations()
              {
                  migrations = new List<IMigration> {
                      new Migration1(),
                      new Migration2(),
                      new Migration3(),
                      new Migration4(),
                      new Migration5(),
                      new Migration6()
                  };
              }
      
              public async Task RunMigrations()
              {
                  // TODO run migrations in a transaction, otherwise, if and error is found, the app could stay in a horrible state
      
                  if (settings.DatabaseVersion < migrations.Count)
                  {
                      var connection = new SQLiteAsyncConnection(() => sqlite.GetConnectionWithLock());
      
                      while (settings.DatabaseVersion < migrations.Count)
                      {
                          var nextVersion = settings.DatabaseVersion + 1;
                          var success = await migrations[nextVersion - 1].UseConnection(connection).Run();
      
                          if (success)
                          {
                              settings.DatabaseVersion = nextVersion;
                          }
                          else
                          {
                              MvxTrace.Error("Migration process stopped after error found at {0}", migrations[nextVersion - 1].GetType().Name);
                              break;
                          }
                      }
                  }
              }
          }
      

      逻辑非常简单。在“while”loope中,我们检查当前的数据库版本(持久存储在设备存储上)。如果有更新的更新(迁移),我们运行它并更新持久的“DatabaseVersion”密钥。

      如您所见,构造函数中提供了2个辅助类: ISQLite sqlite ISettingsService设置 我正在使用MvvmCross(这不是强制性的),并且在每个平台(IOS / ANDROID)上都实现了 ISQLite 。我将展示Android实现:

      public class SqliteAndroid : ISQLite
          {
              private SQLiteConnectionWithLock persistentConnection;
      
              public SQLiteConnectionWithLock GetConnectionWithLock()
              {
                  if (persistentConnection == null)
                  {
                      var dbFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), Constants.DB_FILE_NAME);
                      var platform = new SQLitePlatformAndroid();
                      var connectionString = new SQLiteConnectionString(dbFilePath, true);
                      persistentConnection = new SQLiteConnectionWithLock(platform, connectionString);
                  }
      
                  return persistentConnection;
              }
          }
      

      设置只是一个基于此插件读取/写入简单值到平台持久性存储的类:https://github.com/jamesmontemagno/Xamarin.Plugins/tree/master/Settings

      public interface ISettingsService
          {
              int DatabaseVersion { get; set; }
              [...]
          }
      
          public class SettingsService : ISettingsService
          {
              private string databaseVersionKey = "DatabaseVersion";
              public int DatabaseVersion
              {
                  get { return CrossSettings.Current.GetValueOrDefault(databaseVersionKey, 0); } 
                  set
                  {
                      CrossSettings.Current.AddOrUpdateValue(databaseVersionKey, value);
                  }
              }
      }
      

      最后,迁移代码。 这是迁移的基类:

      public abstract class BaseMigration : IMigration
          {
              protected SQLiteAsyncConnection connection;
              protected string migrationName;
      
              public IMigration UseConnection(SQLiteAsyncConnection connection)
              {
                  this.connection = connection;
                  migrationName = this.GetType().Name;
                  return this;
              }
      
              public virtual async Task<bool> Run()
              {
                  try
                  {
                      MvxTrace.Trace("Executing {0}", migrationName);
                      int result = 0;
                      var commands = GetCommands();
                      foreach (var command in commands)
                      {
                          MvxTrace.Trace("Executing command: '{0}'", command);
                          try
                          {
                              var commandResult = await connection.ExecuteAsync(command);
                              MvxTrace.Trace("Executed command {0}. Rows affected {1}", command, commandResult);
                              result = result + commandResult;
                          }
                          catch (Exception ex)
                          {
                              MvxTrace.Error("Command execution error: {0}", ex.Message);
                              throw ex;
                          }
                      }
      
                      MvxTrace.Trace("{0} completed. Rows affected {1}", migrationName, result);
                      return result > 0;
                  }
                  catch (Exception ex)
                  {
                      MvxTrace.Error("{0} error: {1}", migrationName, ex.Message);
                      return false;
                  }
              }
      
              protected abstract List<string> GetCommands();
          }
      

      迁移1:

      internal sealed class Migration1 : BaseMigration
          {
              override protected List<string> GetCommands()
              {
                  return new List<string> {
                      "DROP TABLE IF EXISTS \"Recipes\";\n",
                      "DROP TABLE IF EXISTS \"RecipeIngredients\";\n",
                      "DROP TABLE IF EXISTS \"Ingredients\";\n",
                      "CREATE TABLE \"Ingredients\" (\n\t " +
                      "\"Id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t " +
                      "\"Name\" TEXT(35,0) NOT NULL COLLATE NOCASE,\n\t " +
                      "\"Family\" INTEGER NOT NULL,\n\t " +
                      "\"MeasureType\" INTEGER NOT NULL,\n\t " +
                      "\"DaysToExpire\" INTEGER NOT NULL,\n\t " +
                      "\"Picture\" TEXT(100,0) NOT NULL\n" +
                      ");",
                      "INSERT INTO \"Ingredients\" VALUES ('1', 'Aceite', '1', '2', '730', 'z_aceite_de_oliva.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('2', 'Sal', '1', '1', '9999', 'z_sal.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('3', 'Cebolla', '3', '1', '30', 'z_cebolla.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('4', 'Naranja', '4', '1', '21', 'z_naranja.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('5', 'Bacalao', '5', '1', '2', 'z_bacalao.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('6', 'Yogur', '6', '2', '21', 'z_yogur.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('7', 'Garbanzos', '7', '1', '185', 'z_garbanzos.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('8', 'Pimienta', '8', '1', '3', 'z_pimienta.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('9', 'Chocolate', '9', '1', '90', 'z_chocolate.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('10', 'Ketchup', '10', '2', '365', 'z_ketchup.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('11', 'Espinaca', '3', '1', '5', 'z_espinaca.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('12', 'Limón', '4', '3', '30', 'z_limon.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('13', 'Calamar', '5', '1', '2', 'z_calamares.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('14', 'Mantequilla', '6', '1', '21', 'z_mantequilla.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('15', 'Perejil', '8', '1', '7', 'z_perejil.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('16', 'Cacao', '9', '1', '365', 'z_cacao.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('17', 'Mayonesa', '10', '2', '7', 'z_mayonesa.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('18', 'Arroz', '7', '1', '999', 'z_arroz.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('19', 'Pepino', '3', '1', '15', 'z_pepino.jpg');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('20', 'Frambuesa', '4', '1', '3', 'z_frambuesa.jpg');"
                  };
              }
          }
      

      迁移2:

      internal sealed class Migration2 : BaseMigration
          {
              override protected List<string> GetCommands()
              {
                  return new List<string> {
                      "INSERT INTO \"Ingredients\" VALUES ('21', 'Otros (líquidos)', '0', '2', '365', 'z_otros_liquidos.png');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('22', 'Otros (sólidos)', '0', '1', '365', 'z_otros_solidos.png');\n",
                      "INSERT INTO \"Ingredients\" VALUES ('23', 'Otros (unidades)', '0', '3', '365', 'z_otros_unidades.png');"
                  };
              }
          }
      

      使用更新命令迁移的示例:

      internal sealed class Migration4 : BaseMigration
          {
              protected override List<string> GetCommands()
              {
                  return new List<string> {
                      "UPDATE Ingredients SET MeasureType = 3, Name = 'Ajo (diente)' WHERE Id = 106",
                      "UPDATE Ingredients SET MeasureType = 3 WHERE Id = 116",
                  };
              }
          }
      

      我希望这会有所帮助。无论如何,如果有人知道更好的方法,请分享

答案 1 :(得分:0)

您是否在Android-Xamarin中创建了一个sqlite帮助程序?

创建一个扩展SqliteHelper

的类
private class DatabaseHelper : SQLiteOpenHelper{
    internal DatabaseHelper(Context context)
        : base(context, dBName, null, databaseVersion){
    }
    public override void OnCreate(SQLiteDatabase db){
       db = SQLiteDatabase.OpenDatabase (destinationPath, null, 0);
    }
    public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
        Log.Wtf(tag, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data");
        this.OnCreate(db);
    }
}

P.S它有onCreate和onUpgrade方法来帮助你。