CodeFirst数据迁移(不丢失数据库原有数据)
之前一篇是关于Entityframework的开发模式CodeFirst(另外两种模式分别为ModelFirst和DataBaseFirst)的数据迁移问题的讨论。这个问题从我们开始选择entityframework作为我们团队的数据库持久化层框架时就一直困扰着我们。
一开始在寻找如何可以让CodeFirst能够每次在实体模型发生改变时,自动改变数据库表结构,最后通过在DbContext初始化构造时加上这句话实现了功能
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TContext>());
虽然每次模型更改数据库表结构都会更新,但是具体过程不能满足我们的需要。因为这句话的具体实现过程是如果模型发生变化就先删除数据库,然后再重建,这样就会导致我们之前保存的数据会丢失。现在是在开发阶段,倒还好,大不了在手动添加些数据,可是如果在以后部署系统后,模型又发生了变化,那该怎么办呢?难道不管不顾直接重建数据库?那肯定不行。最笨最直接的方法是手动备份数据库数据,但是这个对大多数数据库小白的程序员来说还是有点难度的。我们最想的方式是一旦模型更改,在程序启动时,数据库能够在保留原来数据的前提下进行数据库表的结构的更新。
在网上找了很久,终于功夫不负有心人,在msdn的网页http://msdn.microsoft.com/en-us/data/jj591621.aspx给我找到了解决方案。
文章总共有三个解决方案,经过一番研究和测试之后,最终决定采用了最后一种解决方案Automatically Upgrading on Application Startup,以为他是最简单迅速的。不过这个过程也不是一番风顺的,在实际使用过程中,也有几个需要注意的地方。
msdn上的代码是这样的:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.Entity; using MigrationsDemo.Migrations; namespace MigrationsDemo { class Program { static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>()); using (var db = new BlogContext()) { db.Blogs.Add(new Blog { Name = "Another Blog " }); db.SaveChanges(); foreach (var blog in db.Blogs) { Console.WriteLine(blog.Name); } } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } }
关键代码是这句:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>());
这句代码中的BlogContext,继承自DbContext,Configuration配置类是我们在VS2012的“程序包管理控制台”,运行Enable-Migrations命令之后会在项目中生成Migrations下生成的。
using System.Data.Entity; using System.Data.Entity.Migrations; /// <summary> /// 数据库迁移配置信息类 /// </summary> /// <typeparam name="T">需要迁移的数据库上下文</typeparam> public class Configuration<T> : DbMigrationsConfiguration<T> where T : DbContext { /// <summary> /// 构造函数 /// </summary> public MigrateDataBaseConfig() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } }
这个类是经过我和同事修改过的,是一个泛型版本,不具体依赖于某个DbContext实现。
前面的过程按照msdn文章进行一点问题都没有,但是有一点问题我们必须注意,这也是我和同事摸索了好一会儿才发现的。就是想要实现CodeFirst的自动数据迁移功能,具体的DbContext实现类必须提供默认构造函数或者DbContext构造工厂(我用的是第一个方法)。构造函数必须这样写:
public RealTimeDbContext() : base(_connectionString) { }
不能写成这样:
public RealTimeDbContext() { }
区别是如果不给基类DbContext构造函数提供数据库连接字符串,他就会在实体模型发生改变时默认在你计算机本地建立数据库(当然如果你就是要在本地建立数据库那就是另外一回事了)。
我们的项目有关解决这个问题的代码如下:
using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.ModelConfiguration.Conventions; using System.Linq; using SCADA.RTDB.Core.Alarm; using SCADA.RTDB.Core.Variable; using SCADA.RTDB.EntityFramework.DbConfig; using SCADA.RTDB.StorageModel; namespace SCADA.RTDB.EntityFramework.Context { /// <summary> /// 实时数据的实体集合 /// </summary> public class RealTimeDbContext : DbContext, IConvention { private static string _connectionStr; #region 变量集合和组集合 /// <summary> /// 变量组集合 /// </summary> public IDbSet<VariableGroupStorage> VariableGroupSet { get; set; } /// <summary> /// 数字变量集合 /// </summary> public IDbSet<DigitalVariableStorage> DigitalSet { get; set; } /// <summary> /// 模拟变量集合 /// </summary> public IDbSet<AnalogVariableStorage> AnalogSet { get; set; } /// <summary> /// 字符变量集合 /// </summary> public IDbSet<TextVariableStorage> TextSet { get; set; } /// <summary> /// 报警组集合 /// </summary> public IDbSet<AlarmGroup> AlarmGroupSet { get; set; } /// <summary> /// 变量报警集合 /// </summary> public IDbSet<AlarmBase> AlarmSet { get; set; } #endregion #region 构造函数 /// <summary> /// /// </summary> public RealTimeDbContext() : base(_connectionStr) { } /// <summary> /// 变量实体集构造函数 /// </summary> /// <param name="variableRepositoryConfig">变量仓储配置信息类</param> internal RealTimeDbContext(RepositoryConfig variableRepositoryConfig) : base(variableRepositoryConfig.DbNameOrConnectingString) { _connectionStr = variableRepositoryConfig.DbNameOrConnectingString; switch (variableRepositoryConfig.DbType) { case DataBaseType.SqlCeConnectionFactory: Database.DefaultConnectionFactory = new SqlCeConnectionFactory(variableRepositoryConfig.ProviderInvariantName); break; case DataBaseType.SqlConnectionFactory: Database.DefaultConnectionFactory = new SqlConnectionFactory(); break; case DataBaseType.LocalDbConnectionFactory: Database.DefaultConnectionFactory = new LocalDbConnectionFactory(variableRepositoryConfig.LocalDbVersion); break; } // Database.SetInitializer(new DropCreateDatabaseIfModelChanges<RealTimeDbContext>()); Database.SetInitializer( new MigrateDatabaseToLatestVersion<RealTimeDbContext, MigrateDataBaseConfig<RealTimeDbContext>>()); VariableGroupStorage rootGroup = VariableGroupSet.FirstOrDefault(root => root.ParentId == null); if (rootGroup == null) { rootGroup = new VariableGroupStorage {Name = VariableGroup.RootGroup.Name, ParentId = null}; VariableGroupSet.Add(rootGroup); base.SaveChanges(); } } #endregion #region 保存set集合到数据库 /// <summary> /// 保存集合更改 /// </summary> internal void SaveAllChanges() { base.SaveChanges(); } #endregion /// <summary> /// /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<AlarmBase>().Ignore(m=>m.Variable); base.OnModelCreating(modelBuilder); } } }
配置文件类就是上面提供的Configuration.cs.
第一次写博客,思维逻辑没那么严谨,也有好多现在没想到的细节。看了文章如果还有不明白的可以留言一起探讨。