EF Code First - 如果模型更改,如何重新创建数据库

13

我目前正在开发一个使用EF Code First和POCO的项目。到目前为止,我有5个POCO依赖于POCO“User”。

POCO“User”应该引用我的现有MemberShip表“aspnet_Users”(我在DbContext的OnModelCreating方法中将其映射为该表)。

问题是,我想利用“Recreate Database If Model changes”功能,就像Scott Gu在http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx中所示。这个功能的基本作用是只要看到我的POCO中有任何更改,就重新创建数据库。我想要的是重新创建数据库,但不删除整个数据库,以便aspnet_Users仍然存在。然而,似乎不可能,因为它要么创建一个全新的数据库,要么用当前的数据库替换它。

所以我的问题是:我是否注定要手动定义数据库表,还是可以将我的POCO合并到当前的数据库中,并仍然利用该功能而无需全部擦除?

3个回答

16
到EF Code First CTP5为止,这是不可能的。Code First会删除和创建你的数据库,或者完全没有操作它。我认为在你的情况下,你应该手动创建完整的数据库,然后尝试设计一个与DB匹配的对象模型。
话虽如此,EF团队正在积极开发您寻找的功能:改变数据库而不是重新创建它:Code First Database Evolution (aka Migrations)

Alpha 3现已发布:http://blogs.msdn.com/b/adonet/archive/2011/09/21/code-first-migrations-alpha-3-no-magic-walkthrough.aspx - Chris Moschini

7

我刚刚通过以下方法在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类,如果需要,你可以在这里找到它。

我建议不要去看博客文章,而是阅读文档

最后,我仍然建议您在运行初始化之前备份数据库。(例如,如果Seed方法抛出异常,则所有数据都在内存中,因此一旦程序终止,您的数据就有丢失的风险。)模型更改并不是一个随意行为,所以请确保备份好您的数据。


5

您可以考虑使用“断开的”外键来解决此问题。您可以不动 ASPNETDB 数据库,只需使用用户键(guid)在您的数据库中引用用户即可。您可以按以下方式访问已登录的用户:

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

然后在你的数据库中使用用户的密钥作为外键:

Guid UserId = (Guid) currentUser.ProviderUserKey ;

这种方法在架构上将您的数据库与ASPNETDB及其相关提供程序解耦。但是,在操作上,数据当然会松散连接,因为每个数据库中都会有ID。请注意,也不会有引用约束,这可能或可能不是您关心的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接