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上的代码是这样的:

View Code
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下生成的。

  

Configuration.cs
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构造工厂(我用的是第一个方法)。构造函数必须这样写:  

View Code
  public RealTimeDbContext()
            : base(_connectionString)
        {
        }

  不能写成这样:

View Code
  public RealTimeDbContext()
        {
        }

  区别是如果不给基类DbContext构造函数提供数据库连接字符串,他就会在实体模型发生改变时默认在你计算机本地建立数据库(当然如果你就是要在本地建立数据库那就是另外一回事了)。

  我们的项目有关解决这个问题的代码如下:

RealTimeDbContext.cs
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.

 

  第一次写博客,思维逻辑没那么严谨,也有好多现在没想到的细节。看了文章如果还有不明白的可以留言一起探讨。

posted on 2013-03-07 19:32  行动大于一切  阅读(1620)  评论(2编辑  收藏  举报