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

來源:互聯網
上載者:User

標籤:

沒什麼好說的,能支援DropCreateDatabaseIfModelChanges和RowVersion的Sqlite誰都想要。EntityFramework7正在添加對Sqlite的支援,雖然EF7不知道猴年馬月才能完成正式版,更不知道MySql等第三方提供者會在什麼時候跟進支援,但是EF7中的確出現了Sqlite的相關代碼。Sqlite支援EF6的CodeFirst,只是不支援從實體產生資料庫,估計有很多人因為這個原因放棄了使用它。現在SQLite.CodeFirst的簡單實現可以讓我們產生資料庫,因此在等待EF7的可以預見的長時間等待中,我們可以使用SQLite.CodeFirst,畢竟我們只是開發的時候使用DropCreateDatabaseIfModelChanges,Release時不會使用更不用擔心SQLite.CodeFirst的簡單實現會帶來什麼問題。

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不支援從代碼產生資料庫這兩點終於克服了。

希望你不是找了好久才找到這個解決方案。

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.