星空网 > 软件开发 > ASP.net

Entity Framework 6 Code First 系列:使SQLite.CodeFirst支持DropCreateDatabaseIfModelChanges和RowVersion

没什么好说的,能支持DropCreateDatabaseIfModelChanges和RowVersion的Sqlite谁都想要。EntityFramework7正在添加对Sqlite的支持,虽然EF7不知道猴年马月才能完成正式版,更不知道MySql等第三方提供程序会在什么时候跟进支持,但是EF7中的确出现了Sqlite的相关代码。Sqlite支持EF6的CodeFirst,只是不支持从实体生成数据库,估计有很多人因为这个原因放弃了使用它。现在SQLite.CodeFirst的简单实现可以让我们生成数据库,因此在等待EF7的可以预见的长时间等待中,我们可以使用SQLite.CodeFirst,毕竟我们只是开发的时候使用DropCreateDatabaseIfModelChanges,Release时不会使用更不用担心SQLite.CodeFirst的简单实现会带来什么问题。可以直接修改源码,也可以参考最后面通过反射实现自定义DropCreateDatabaseIfModelChanges的方式。

1.RowVersion的支持:

可以从我的上一篇:在MySql中使用和SqlServer一致的RowVersion并发控制中采用相同的策略即可。我已经测试过在Sqlite中的可行性。

首先是RowVersion的配置:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)    {      modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();      modelBuilder.Configurations.AddFromAssembly(typeof(SqliteDbContext).Assembly);      modelBuilder.Properties()              .Where(o => o.Name == "RowVersion")              .Configure(o => o.IsConcurrencyToken()              .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None));      Database.SetInitializer(new SqliteDbInitializer(Database.Connection.ConnectionString, modelBuilder));    }

然后是SaveChanges的重写:

public override int SaveChanges()    {      this.ChangeTracker.DetectChanges();      var objectContext = ((IObjectContextAdapter)this).ObjectContext;      foreach (ObjectStateEntry entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added))      {        var v = entry.Entity as IRowVersion;        if (v != null)        {          v.RowVersion = System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());        }      }      return base.SaveChanges();    }

2.DropCreateDatabaseIfModelChanges支持

(1)生成__MigrationHistory:

DropCreateDatabaseIfModelChanges则需要修改SQLite.CodeFirst的代码,SQLite.CodeFirst生成的数据库不包含__MigrationHistory信息,所以我们首先修改SqliteInitializerBase添加__MigrationHistory,__MigrationHistory表是通过HistoryRow实体的映射,我们直接在EF源代码中找到相关部分作为参考。修改SqliteInitializerBase的SqliteInitializerBase方法,配置HistoryRow实体的映射。

public const string DefaultTableName = "__MigrationHistory";    internal const int ContextKeyMaxLength = 300;    internal const int MigrationIdMaxLength = 150;    protected SqliteInitializerBase(string connectionString, DbModelBuilder modelBuilder)    {      DatabaseFilePath = SqliteConnectionStringParser.GetDataSource(connectionString);      ModelBuilder = modelBuilder;      // This convention will crash the SQLite Provider before "InitializeDatabase" gets called.      // See https://github.com/msallin/SQLiteCodeFirst/issues/7 for details.      modelBuilder.Conventions.Remove<TimestampAttributeConvention>();      modelBuilder.Entity<HistoryRow>().ToTable(DefaultTableName);      modelBuilder.Entity<HistoryRow>().HasKey(        h => new        {          h.MigrationId,          h.ContextKey        });      modelBuilder.Entity<HistoryRow>().Property(h => h.MigrationId).HasMaxLength(MigrationIdMaxLength).IsRequired();      modelBuilder.Entity<HistoryRow>().Property(h => h.ContextKey).HasMaxLength(ContextKeyMaxLength).IsRequired();      modelBuilder.Entity<HistoryRow>().Property(h => h.Model).IsRequired().IsMaxLength();      modelBuilder.Entity<HistoryRow>().Property(h => h.ProductVersion).HasMaxLength(32).IsRequired();    }

(2)初始化__MigrationHistory:

继续修改InitializeDatabase方法,在创建数据库后,初始化__MigrationHistory的信息。虽然采用了HistoryRow,但初始化信息我们只简单的使用生成的SQL语句作为判定实体和配置是否改变的依据,因为后面的InitializeDatabase方法中也是我们自己来判定实体和配置是否改变。

public virtual void InitializeDatabase(TContext context)    {      var model = ModelBuilder.Build(context.Database.Connection);      using (var transaction = context.Database.BeginTransaction())      {        try        {          var sqliteDatabaseCreator = new SqliteDatabaseCreator(context.Database, model);          sqliteDatabaseCreator.Create();          /*start*/          context.Set<HistoryRow>().Add(            new HistoryRow            {              MigrationId = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fffffff"),              ContextKey = context.GetType().FullName,              Model = System.Text.Encoding.UTF8.GetBytes(sqliteDatabaseCreator.GetSql().ToCharArray()),              ProductVersion = "6.1.2"            });          /*end*/          transaction.Commit();        }        catch (Exception)        {          transaction.Rollback();          throw;        }      }      using (var transaction = context.Database.BeginTransaction())      {        try        {          Seed(context);          context.SaveChanges();          transaction.Commit();        }        catch (Exception)        {          transaction.Rollback();          throw;        }      }    }

(3)添加DropCreateDatabaseIfModelChanges支持

添加SqliteDropCreateDatabaseIfModelChanges类,在InitializeDatabase方法中判读实体和配置是否改变。需要注意的是,删除sqlite文件时,即使关闭Connection和调用GC.Collect()仍然在第一次无法删除文件,所以必须进行多次尝试。

public override void InitializeDatabase(TContext context)    {      bool dbExists = File.Exists(DatabaseFilePath);      if (dbExists)      {        var model = ModelBuilder.Build(context.Database.Connection);        var sqliteDatabaseCreator = new SqliteDatabaseCreator(context.Database, model);        var newSql = sqliteDatabaseCreator.GetSql();        var oldSql = "";        oldSql = System.Text.Encoding.UTF8.GetString(context.Set<System.Data.Entity.Migrations.History.HistoryRow>().AsNoTracking().FirstOrDefault().Model);        context.Database.Connection.Close();        GC.Collect();         if (oldSql == newSql)        {          return;        }        for (int i = 0; i < 10; i++)        {          try          {            File.Delete(DatabaseFilePath);            break;          }          catch (Exception)          {            System.Threading.Thread.Sleep(1);          }        }      }      base.InitializeDatabase(context);    }

 

核心的代码已经贴出来,SQLite.CodeFirst本身的实现就比较简易,我添加的代码也比较简陋,因此在代码上没什么参考价值,只有使用和实用价值。毕竟只是在Debug开发时才需要这些功能的支持,对Sqlite本身和EF的提供程序没有任何影响。到这里终于松了口气,我们现在可以使用:Sql Server(CE)、Sqlite和Mysql进行Code First开发,采用相同的实体定义和配置,并且采用相同的并发控制。非Sql Server(CE)的并发控制和Sqlite不支持从代码生成数据库这两点终于克服了。

后记

修改源码是由于SQLite.CodeFirst的内部类无法调用,考虑到可以使用反射,于是有了下面不需要修改源码,通过反射实现直接自定义DropCreateDatabaseIfModelChanges的方式:

Entity Framework 6 Code First 系列:使SQLite.CodeFirst支持DropCreateDatabaseIfModelChanges和RowVersionEntity Framework 6 Code First 系列:使SQLite.CodeFirst支持DropCreateDatabaseIfModelChanges和RowVersion
  public class SqliteDropCreateDatabaseIfModelChanges<TContext> : IDatabaseInitializer<TContext> where TContext : DbContext  {    protected readonly DbModelBuilder ModelBuilder;    protected readonly string DatabaseFilePath;    public const string DefaultTableName = "__MigrationHistory";    internal const int ContextKeyMaxLength = 300;    internal const int MigrationIdMaxLength = 150;    public SqliteDropCreateDatabaseIfModelChanges(string connectionString, DbModelBuilder modelBuilder)    {      ModelBuilder = modelBuilder;      ConfigMigrationHistory(modelBuilder);    }    private void ConfigMigrationHistory(DbModelBuilder modelBuilder)    {      modelBuilder.Entity<HistoryRow>().ToTable(DefaultTableName);      modelBuilder.Entity<HistoryRow>().HasKey(        h => new        {          h.MigrationId,          h.ContextKey        });      modelBuilder.Entity<HistoryRow>().Property(h => h.MigrationId).HasMaxLength(MigrationIdMaxLength).IsRequired();      modelBuilder.Entity<HistoryRow>().Property(h => h.ContextKey).HasMaxLength(ContextKeyMaxLength).IsRequired();      modelBuilder.Entity<HistoryRow>().Property(h => h.Model).IsRequired().IsMaxLength();      modelBuilder.Entity<HistoryRow>().Property(h => h.ProductVersion).HasMaxLength(32).IsRequired();    }    public string GetSql(DbModel model)    {      Assembly asm = Assembly.GetAssembly(typeof(SqliteInitializerBase<>));      Type builderType = asm.GetType("SQLite.CodeFirst.Builder.CreateDatabaseStatementBuilder");      ConstructorInfo builderConstructor = builderType.GetConstructor(new Type[] { typeof(EdmModel) });      Object builder = builderConstructor.Invoke(new Object[] { model.StoreModel });      MethodInfo method = builderType.GetMethod("BuildStatement", BindingFlags.Instance|BindingFlags.InvokeMethod|BindingFlags.Public);      var statement = (IStatement)method.Invoke(builder, new Object[] { });      string sql = statement.CreateStatement();      return sql;    }    public void InitializeDatabase(TContext context)    {      var model = ModelBuilder.Build(context.Database.Connection);      var sqliteDatabaseCreator = new SqliteDatabaseCreator(context.Database, model);      var newSql = this.GetSql(model);      bool dbExists = File.Exists(DatabaseFilePath);      if (dbExists)      {        var oldSql = System.Text.Encoding.UTF8.GetString(context.Set<System.Data.Entity.Migrations.History.HistoryRow>().AsNoTracking().FirstOrDefault().Model);        context.Database.Connection.Close();        GC.Collect();        if (oldSql == newSql)        {          return;        }        for (int i = 0; i < 10; i++)        {          try          {            File.Delete(DatabaseFilePath);            break;          }          catch (Exception)          {            System.Threading.Thread.Sleep(1);          }        }      }      using (var transaction = context.Database.BeginTransaction())      {        try        {          context.Database.ExecuteSqlCommand(newSql);          context.Set<HistoryRow>().Add(            new HistoryRow            {              MigrationId = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fffffff"),              ContextKey = context.GetType().FullName,              Model = System.Text.Encoding.UTF8.GetBytes(newSql.ToCharArray()),              ProductVersion = "6.1.3"            });          transaction.Commit();        }        catch (Exception)        {          transaction.Rollback();          throw;        }      }      using (var transaction = context.Database.BeginTransaction())      {        try        {          Seed(context);          context.SaveChanges();          transaction.Commit();        }        catch (Exception)        {          transaction.Rollback();          throw;        }      }    }    protected virtual void Seed(TContext context)    {    }  }

View Code

希望你不是找了好久才找到这个解决方案。




原标题:Entity Framework 6 Code First 系列:使SQLite.CodeFirst支持DropCreateDatabaseIfModelChanges和RowVersion

关键词:sql

sql
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

亚马逊站外推广常见的问题解答:https://www.ikjzd.com/articles/91183
设计、开发人员必看的13个免费学习资源网站!:https://www.ikjzd.com/articles/91185
多个eBay卖家反映:这个默认选项增加了卖家的额外费用:https://www.ikjzd.com/articles/91186
跨境卖家一定要知道的国外最流行的10种在线支付网关!:https://www.ikjzd.com/articles/91187
亚马逊发布2019中小企业影响力报告 / 200亿的跨境物流巨头资产冻结被立案调查:https://www.ikjzd.com/articles/91188
收藏 | 如何利用售后卡一周增评上千条?:https://www.ikjzd.com/articles/91189
宠物梳专利查询分析:https://www.kjdsnews.com/a/1842293.html
温州旧货市场有玻璃柜卖吗?:https://www.vstour.cn/a/411246.html
相关文章
我的浏览记录
最新相关资讯
海外公司注册 | 跨境电商服务平台 | 深圳旅行社 | 东南亚物流