EF CodeFirst 必须要解决的问题

Entity Framework有三种模式:Model First、DB First和 CodeFirst,这里只谈CodeFirst。实际项目中如果采用了CodeFirst,那么必定会碰见下面这些问题:而且必须解决,否则开发及项目迭代过程中必定会有各类的困惑,以至于放弃使用EF CodeFirst。

以本人对EF CodeFirst 的学习过程,这些问题有:

问题1:数据库的表和模型(Model)的映射匹配冲突了怎么办?比如:先用CodeFirst从无到有创建了数据库,如果后续的开发过程中直接修改了数据库,直接修改项目里的Model匹配数据库的修改就可以了吗?或者修改Model后对应直接修改数据库表就可以了吗?

要回答这个问题,实际做一做也许就能找到客观的答案:

这里使用VS2017 新建一个类库项目和一个控制台项目,类库中添加EntityFramework的引用;

接着在Entity层中新建一个UserEntity模型:

public class UserEntity
    {
        public Guid Id { get; set; }
        public string UserName { get; set; }
        public string UserDesc { get; set; }
        public DateTime? CreateDate { get; set; }
    }

添加一个Entity和数据表的映射关系:如下面的代码所示:这里把User 指向数据表UserT, 并且Id为主键,且GUID类型自动由数据库生成:(EF 创建UserT表创建了DB默认值约束:ALTER TABLE [dbo].[UserT] ADD  DEFAULT (newsequentialid()) FOR [Id])

public class UserMap : EntityTypeConfiguration<UserEntity>
    {
        public UserMap()
        {
            this.ToTable("UserT");
            this.HasKey(t => t.Id);
            this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        }
    }

定义一个上下文的引用,派生至System.Data.Entity.DbContext, 用于数据库读写:

 public class SqlServerDbContext : DbContext
    {
        public SqlServerDbContext() : base("name=EFCodeFirst")
        {
            Database.SetInitializer<SqlServerDbContext>(new CreateDatabaseIfNotExists<SqlServerDbContext>());
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            dynamic user = Activator.CreateInstance(typeof(UserMap));
            modelBuilder.Configurations.Add(user);

            base.OnModelCreating(modelBuilder);
        }
    }

回到Console项目中:添加App.config

添加数据库连接字符串:

  <connectionStrings>
    <add name="EFCodeFirst" connectionString="data source=.;initial catalog=TestDB;persist security info=True;user id=sa;password=111111;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
  </connectionStrings>

在main函数执行添加User操作:

  static void Main(string[] args)
        {
            using (SqlServerDbContext context = new SqlServerDbContext())
            {
                context.Set<UserEntity>().Add(new UserEntity() { UserName = "U001", UserDesc = "add user" });
                context.SaveChanges();
            }
            Console.WriteLine("OL");
            Console.Read();
        }

执行测试结果:

回到问题:直接修改EF 自动生成的db表,先做如下实验:

实验1:新增无相关性的表,例如新建T1表;此时我们的EF测试程序中没有添加T1表的模型及映射,自然不会有冲突;

实验2:在UserT表新增列 CreateBy:然后运行程序,新增User没有问题;

实验3:改变程序中UserEntity的属性UserName 成UserDisplayName, 数据库UserT表的栏位对应修改一致;此时再次测试

看到上图提示很明显了,上下文的模型已经在数据库创建后发生更改了;要快速解决这个问题可以这样:

 public SqlServerDbContext() : base("name=EFCodeFirst")
        {
            Database.SetInitializer<SqlServerDbContext>(null);
        }

原理是:EF中DbContext首次加载时,OnModelCreating会检查__MigrationHistory表,如果DbContext与模型不匹配则会报错:

可以用Sql Profiler检测下sql的执行,搜索_MigrationHistory,可以看下图所示的sql执行:

Database.SetInitializer<SqlServerDbContext>(null);设置null后将不在检测__MigrationHistory,首次执行EF时也不会自动生成数据库,且还会报错:基础提供程序在 Open 上失败;SqlException: 无法打开登录所请求的数据库 。登录失败。
用户 'sa' 登录失败。

这里建议是当数据库生成后把__MigrationHistory的检测设置为null,可以优化EF首次执行的加载效率;

当然,你还可以换其他的Database.SetInitializer策略:

比如:DropCreateDatabaseIfModelChanges 、DropCreateDatabaseAlways等,但如果是正式环境,这样做会有没顶之灾。

话题再转回来:如果不设置null,解决模型变更后的异常:方法可以这样:

简单来说就是:enable-migration_> add-migration_>update-database

详细来看下过程:

设置Entity层为启动项目(重要,否则报异常):VS2017中菜单:“工具”——>"Nuget包管理器"——>"程序包管理器控制台"

此时再来看下数据库结构和__MigrationHistory表中的变化

这里有个一个缺陷:UserName改为UserDisplayName后 EF的处理是把UserName栏位删除,然后添加UserDisplayName栏位:

  public partial class user : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.UserT", "UserLoginAccount", c => c.String());
            AddColumn("dbo.UserT", "UserDisplayName", c => c.String());
            DropColumn("dbo.UserT", "UserName");
        }
        
        public override void Down()
        {
            AddColumn("dbo.UserT", "UserName", c => c.String());
            DropColumn("dbo.UserT", "UserDisplayName");
            DropColumn("dbo.UserT", "UserLoginAccount");
        }
    }

这样的处理显然是不可接受的,所以在实际开发项目中应该避免这样的操作,如果一定需要改列名,可以尝试保留原来的列,新增一列处理,最大限度保留原有数据;

回答第一个问题:1:数据库的表和模型(Model)的映射匹配冲突了怎么办?比如:先用CodeFirst从无到有创建了数据库,如果后续的开发过程中直接修改了数据库,直接修改项目里的Model匹配数据库的修改就可以了吗?或者修改Model后对应直接修改数据库表就可以了吗?

答:如果EF Database初始化策略为null:Database.SetInitializer<SqlServerDbContext>(null);

这样做是可以的。即使不是为null 的策略,如果数据库的修改同模型的匹配不会发生冲突,例如数据建立唯一约束,建立索引,新增栏位等操作也不会引起EF 报上下文数据创建后的模型改变异常;其他情形则需要通过EF 数据迁移来完成;

问题2:项目上线正式环境后,如果程序调整,正式环境数据库如何升级;

这里有两种方式:方式一:执行update-database -Verbos 则可以生成此次更新的数据库脚本,把它记录下来放在正式环境中执行就可以了;如下图所示:

实际开发中可能有多次迭代更新,这样就需要记录每次update 的升级脚本,如果每次更新是不同的开发人员来完成,容易造成混乱,不方便管理;

方式二:自动迁移更新数据库:

 internal sealed class Configuration : DbMigrationsConfiguration<MyCodeFirst.Entity.SqlServerDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = false;
            ContextKey = "MyCodeFirst.Entity.SqlServerDbContext";
        }

        protected override void Seed(MyCodeFirst.Entity.SqlServerDbContext context)
        {
            //  This method will be called after migrating to the latest version.
            //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
            //  to avoid creating duplicate seed data.
        }
    }

在SqlServerDbContext的构造函数中使用MigrateDatabaseToLatestVersion 方式:

Database.SetInitializer<SqlServerDbContext>(new MigrateDatabaseToLatestVersion<SqlServerDbContext, MyCodeFirst.Entity.Migrations.Configuration>());

这样EF框架自动帮我们做好了数据库的升级;

问题3:实际开发中,数据库都有专人负责设计,数据库在前,此时CodeFirst模式如何使用?

CodeFirst 代码优先:模型是先行设计的,数据库通过模型及模型到数据库表的映射来完成的。比较适合领域驱动设计的开发方式。如果是数据设计已经存在,EF也可以使用CodeFirst模式。

如下图示:

以这种方式添加的Model实际使用时会提示xxx对象已经存在:

通常的原因是EF会查询_MigrationHistory, 比对Model 不存在于数据库中则会生成Create Table 的语句;

处理的方式是不生成Create Table脚本并能在_MigrationHistory里自动迁移就可以了。做法是:

如下图所示:先add-migration xxx

EF自动生成迁移历史后,打开最后生成的迁移文件,注释掉其中的CreateTable,

然后:update-database 

再次执行程序,则不会报对象已经存在的异常了。注释的CreateTable最好取消注释,防止切换数据库连接自动迁移不能生成Create Table脚本。

个人的困惑:觉得使用EF CodeFirst就要通过手工编写模型,后生成DB。但手工编码不直观,而且工作量大,有点费力。如果是借助一些工具来辅助设计,通过工具来生成模型,又有些像Model First。即使自己编写程序来自动生成代码也是要先有可以参照的数据源,比如先有数据库,从数据库读取表的栏位字段及类型等信息后程序批量生成模型,这样做就有些像DB First。且EF发展后面只有CodeFirst。也许是没有真正领会到CodeFirst的精髓!

总结一下:选择EF CodeFirst不管通过何种方式来生成模型,需要保证数据库的表实体和程序模型的对应关系,碰见CodeFirst提示的冲突时,可以通过设置自动迁移或手动迁移完成。知道EF CodeFirst运行原理后可以就开发过程中碰见的各类问题灵活处理。在团队开发过程中,异常或冲突或许会经常发生,要制定一些规则或做好技术防范:比如专人负责迁移,做好备份或做好源代码管控,万一出现错误还可以迁移到上一步等。

 

 

 

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 Entity Framework Code First 可以方便地创建数据表。以下是基本步骤: 1.创建一个继承自 DbContext 的类,该类代表数据库上下文。 2.定义一个实体类,该类代表数据库中的一个表。在实体类上使用数据注解或 Fluent API,来指定表名、列名、数据类型、主键、外键等信息。 3.在数据库上下文类中,使用 DbSet 属性将实体类与数据库上下文关联起来。 4.创建一个数据库初始化类,该类继承自 DropCreateDatabaseIfModelChanges 或 CreateDatabaseIfNotExists,用于在应用程序启动时自动创建数据库和表。 5.在应用程序启动时,使用数据库初始化类的静态方法,来初始化数据库。 以下是一个示例: ```csharp // 1.创建数据库上下文 public class MyDbContext : DbContext { public DbSet<User> Users { get; set; } } // 2.定义实体类 public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } // 3.在数据库上下文中关联实体类 public class MyDbContext : DbContext { public DbSet<User> Users { get; set; } } // 4.创建数据库初始化类 public class MyDbInitializer : DropCreateDatabaseIfModelChanges<MyDbContext> { protected override void Seed(MyDbContext context) { // 初始化数据 context.Users.Add(new User { Name = "Tom", Age = 20 }); context.Users.Add(new User { Name = "Jerry", Age = 22 }); base.Seed(context); } } // 5.在应用程序启动时初始化数据库 Database.SetInitializer(new MyDbInitializer()); ``` 运行应用程序后,EF Code First 将会自动创建名为 MyDbContext 的数据库,并在其中创建一个名为 Users 的表,该表包含 Id、Name 和 Age 三列。同时,数据库初始化类 MyDbInitializer 会在数据库创建后,向 Users 表中插入两条初始化数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值