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

发布于 2024-10-08 13:09:07 字数 677 浏览 4 评论 0原文

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

POCO“User”应该引用我已经存在的 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 合并到我当前的数据库中,并且仍然采取使用该功能而不擦除全部内容?

I'm currently working on a project which is using EF Code First with POCOs. I have 5 POCOs that so far depends on the POCO "User".

The POCO "User" should refer to my already existing MemberShip table "aspnet_Users" (which I map it to in the OnModelCreating method of the DbContext).

The problem is that I want to take advantage of the "Recreate Database If Model changes" feature as Scott Gu shows at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - What the feature basically does is to recreate the database as soon as it sees any changes in my POCOs. What I want it to do is to Recreate the database but to somehow NOT delete the whole Database so that aspnet_Users is still alive. However it seems impossible as it either makes a whole new Database or replaces the current one with..

So my question is: Am I doomed to define my database tables by hand, or can I somehow merge my POCOs into my current database and still take use of the feature without wipeing it all?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

淡水深流 2024-10-15 13:09:08

我只能在 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 方法抛出异常,则所有数据都在内存中,因此一旦程序终止,您的数据就有丢失的风险。)无论如何,模型更改并不完全是事后的操作,因此请务必备份数据。

I was just able to do this in EF 4.1 with the following considerations:

The database is still deleted and recreated - it has to be to for the schema to reflect your model changes -- but your data remains intact.

Here's how: you read your database into your in-memory POCO objects, and then after the POCO objects have successfully made it into memory, you then let EF drop and recreate the database. Here is an example

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;   
        }           
    }
}

This example relies on a ObjectReader class which you can find here if you need it.

I wouldn't bother with the blog articles, read the documentation.

Finally, I would still suggest you always back up your database before running the initialization. (e.g. if the Seed method throws an exception, all your data is in memory, so you risk your data being lost once the program terminates.) A model change isn't exactly an afterthought action anyway, so be sure to back your data up.

避讳 2024-10-15 13:09:08

您可能会考虑的一件事是使用“断开连接”的外键。您可以保留 ASPNETDB,只使用用户键 (guid) 引用数据库中的用户。您可以按如下方式访问登录的用户:

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

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

Guid UserId = (Guid) currentUser.ProviderUserKey ;

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

One thing you might consider is to use a 'disconnected' foreign key. You can leave the ASPNETDB alone and just reference the user in your DB using the User key (guid). You can access the logged in user as follows:

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

And then use the User's key as a FK in your DB:

Guid UserId = (Guid) currentUser.ProviderUserKey ;

This approach decouples your DB with the ASPNETDB and associated provider architecturally. However, operationally, the data will of course be loosely connected since the IDs will be in each DB. Note also there will be no referential constraints, whcih may or may not be an issue for you.

超可爱的懒熊 2024-10-15 13:09:07

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

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

Code First 数据库演进(又名迁移)

As of EF Code First in CTP5, this is not possible. Code First will drop and create your database or it does not touch it at all. I think in your case, you should manually create your full database and then try to come up with an object model that matches the DB.

That said, EF team is actively working on the feature that you are looking for: altering the database instead of recreating it:

Code First Database Evolution (aka Migrations)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文