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

asp.net-membership entity-framework poco

Question

I'm presently engaged in a project that makes use of POCOs and EF Code First. I now have 5 POCOs that rely on the POCO "User."

The POCO "User" should relate to the "aspnet Users" MemberShip database that I already have (which I map it to in the OnModelCreating method of the DbContext).

My issue is that I want to use the "Recreate Database If Model Changes" function, as Scott Gu demonstrates at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - When it detects any changes in my POCOs, the functionality essentially recreates the database. I want it to recreate the database but somehow keep aspnet Users alive by not deleting the whole database. However, it seems to be impossible since it either creates a brand-new Database or overwrites the existing one.

So, my issue is: Must I construct my database tables manually, or is there a way to integrate my POCOs into my present database so that I can still utilize the functionality without erasing everything?

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

Accepted Answer

This is not feasible as per CTP5's EF Code First release. Your database will either be dropped and created by Code First or it won't be touched at all. In such situation, I believe you should manually construct your whole database before attempting to develop an object model that is compatible with the DB.

Having said that, the EF team is currently working on the functionality you're after: modifying the database rather than rebuilding it:

Changes to the Code First Database (aka Migrations)

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

Popular Answer

I could only do this with EF 4.1 with the following factors in mind:

In order for the schema to reflect your model changes, the database must still be wiped and restored, but your data is still remains whole.

This is how: When reading your database into your in-memory POCO objects, you let EF drop and restore the database after the POCO objects have successfully made it into memory. Here's an illustration

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

The ObjectReader class used in this example may be found in here if necessary.

I would read the documentation instead of bothering with the blog posts.

Last but not least, I still advise you to regularly backup your database before doing the startup. (For instance, all of your data is in memory if the Seed function throws an exception, so you run the risk of losing everything if the application crashes.) Changing a model isn't exactly a last-minute decision, so be sure you have a backup of your data.



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