If the model changes, start with EF code and then recreate the database.

asp.net-membership entity-framework poco

Question

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?

1
13
12/19/2010 11:25:51 PM

Accepted Answer

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)

16
12/20/2010 12:34:44 AM

Popular Answer

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.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow