EF Code First - Recreate Database If Model Changes

后端 未结 3 1828
攒了一身酷
攒了一身酷 2020-12-24 15:51

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

相关标签:
3条回答
  • 2020-12-24 15:59

    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.

    0 讨论(0)
  • 2020-12-24 16:03

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

    • CodeFirst
    • DropCreateDatabaseAlways
    • keeping the same connection string and database name

    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.

    0 讨论(0)
  • 2020-12-24 16:17

    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)

    0 讨论(0)
提交回复
热议问题