【问题标题】:EF Code First - Recreate Database If Model ChangesEF Code First - 如果模型更改,则重新创建数据库
【发布时间】:2010-12-19 23:25:51
【问题描述】:

我目前正在开发一个使用 EF Code First 和 POCO 的项目。到目前为止,我有 5 个 POCO,它们取决于 POCO“用户”。

POCO“用户”应该引用我已经存在的 MemberShip 表“aspnet_Users”(我将它映射到 DbContext 的 OnModelCreating 方法中)。

问题是我想利用 Scott Gu 在http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx 上展示的“如果模型更改则重新创建数据库”功能 - 该功能的基本作用是在看到任何更改后立即重新创建数据库我的 POCO。我想要它做的是重新创建数据库,但以某种方式不删除整个数据库,以便 aspnet_Users 仍然存在。然而,这似乎是不可能的,因为它要么创建一个全新的数据库,要么用.. 替换当前的数据库。

所以我的问题是:我注定要手动定义我的数据库表,还是我可以以某种方式将我的 POCO 合并到我当前的数据库中,并且仍然可以使用该功能而不将其全部擦除?

【问题讨论】:

    标签: entity-framework asp.net-membership poco


    【解决方案1】:

    从 CTP5 中的 EF Code First 开始,这是不可能的。 Code First 将删除并创建您的数据库,否则它根本不会触及它。我认为在您的情况下,您应该手动创建完整的数据库,然后尝试提出与数据库匹配的对象模型。

    也就是说,EF 团队正在积极开发您正在寻找的功能:更改数据库而不是重新创建它:

    Code First Database Evolution (aka Migrations)

    【讨论】:

    【解决方案2】:

    我只是在 EF 4.1 中能够做到这一点,考虑以下几点:

    数据库仍被删除并重新创建 - 架构必须如此才能反映您的模型更改 - 但您的数据保持不变。

    方法如下:将数据库读入内存中的 POCO 对象,然后在 POCO 对象成功进入内存后,让 EF 删除并重新创建数据库。这是一个例子

    public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {
    
        /// <summary>
        /// Connection from which to ead the data from, to insert into the new database.
        /// Not the same connection instance as the DbContext, but may have the same connection string.
        /// </summary>
        DbConnection connection;
        Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
        public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
            this.connection = connection;           
            this.map = map ?? ReadDataIntoMemory();         
        }
    
        //read data into memory BEFORE database is dropped
        Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
            Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
            switch (connection.State) {
                case System.Data.ConnectionState.Closed:
                    connection.Open();
                    break;
            }
            using (this.connection) {
                var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
                                let elementType = p.PropertyType.GetGenericArguments()[0]
                                let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
                                where dbsetType.IsAssignableFrom(p.PropertyType)
                                select new Tuple<PropertyInfo, Type>(p, elementType);
    
                foreach (var tuple in metaquery) {
                    map.Add(tuple, ExecuteReader(tuple));
                }
                this.connection.Close();
                Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
            }       
            return map; 
        }
    
        protected override void Seed(NorthindDbContext context) {
    
            foreach (var keyvalue in this.map) {
                foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
                    PropertyInfo p = keyvalue.Key.Item1;
                    dynamic dbset = p.GetValue(context, null);
                    dbset.Add(((dynamic)obj));
                }
            }
    
            context.SaveChanges();
            base.Seed(context);
        }
    
        System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
            DbCommand cmd = this.connection.CreateCommand();
            cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
            DbDataReader reader = cmd.ExecuteReader();
            using (reader) {
                ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
                                            .GetConstructors()[0];
                ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
                LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
                System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
                MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
                "ToArray",
                new Type[] { tuple.Item2 },
                Expression.Constant(objreader));
                LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
                var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
                return array;   
            }           
        }
    }
    

    此示例依赖于 ObjectReader 类,如果需要,您可以找到 here。

    我不会打扰博客文章,请阅读documentation。

    最后,我仍然建议您始终在运行初始化之前备份您的数据库。 (例如,如果 Seed 方法抛出异常,您的所有数据都在内存中,因此一旦程序终止,您就有丢失数据的风险。)无论如何,模型更改并不是事后才采取的行动,因此请务必备份您的数据.

    【讨论】:

      【解决方案3】:

      您可能会考虑使用“断开连接”的外键。您可以不理会 ASPNETDB,只需使用用户密钥 (guid) 引用数据库中的用户。您可以按如下方式访问已登录的用户:

      MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
      

      然后将用户的密钥用作数据库中的 FK:

      Guid UserId = (Guid) currentUser.ProviderUserKey ;
      

      这种方法在架构上将您的数据库与 ASPNETDB 和关联的提供程序解耦。但是,在操作上,数据当然会松散连接,因为 ID 将在每个 DB 中。另请注意,不会有参考约束,这对您来说可能是也可能不是问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多