EF Code First:实体映射,数据迁移,重构(1)

一、前言

经过EF的《第一篇》,我们已经把数据访问层基本搭建起来了,但并没有涉及实体关系。实体关系对于一个数据库系统来说至关重要,而且EF的各个实体之间的联系,实体之间的协作,联合查询等也都依赖于这些实体关系。

二、实体映射

实体与数据库的映射可以通过DataAnnotation与FluentAPI两种方式来进行映射:

(一) DataAnnotation

DataAnnotation 特性由.NET 3.5中引进,给.NET中的类提供了一种添加验证的方式。DataAnnotation由命名空间System.ComponentModel.DataAnnotations提供。下面列举实体模型中常用的DataAnnotation特性:

KeyAttribute:对应数据库中的主键
RequiredAttribute:对应数据库中字段的数据是否可以为null
MaxLengthAttribute:对应数据库中字符串类型字段的最大长度
MinLengthAttribute:在数据库中无对应,但在代码中字符串最小长度
ConcurrencyCheckAttribute:指定用于开放式并发检查的列的数据类型
TimestampAttribute:将列的数据类型指定为行版本
System.ComponentModel.DataAnnotations命名空间中只定义了部分实体验证的特性,在EntityFramework程序集中定义了更多的数据映射特性:

DatabaseGeneratedAttribute:标记指定实体属性是由数据库生成的,并指定生成策略(None数据库不生成值,Identity当插入行时,数据库生成值,Computed当插入或更新行时,数据库生成值)
ColumnAttribute:指定实体属性在数据库中的列名及数据类型
TableAttribute:指定实体类对应的数据表名
ForeignKeyAttribute:指定导航属性的外键字段
NotMappedAttribute:标记指定实体属性在创建数据库中不创建对应字段
ComplexTypeAttribute:标记指定实体属性是将一个对象作为另一个对象的属性,映射到数据库中则子对象表现为多个属性字段
对于实体关系对应的数据表关系,无非“0:1,1:1,0:N,1:N,N:N”这几种,可以使用导航属性中的数据类型来表示,0…1端使用单实体类型表示,N端使用ICollection<T>集合类型表示。对于单实体端,默认是可为空的,即为0关系,如果要设置为1关系,要使用[Required]标签来进行标记。但对于一对一中的关系主体与依赖对象确无法做更细节的控制。 

(二) Fluent API

使用DataAnnotation非常简单,但对于EntityFramework中的特性,就要在实体类中引入EntityFramework程序集,但实体类最好能是保持与架构无关性的POCO类,才能更具通用性。所以,最好是在数据层中使用FluentAPI在数据层中进行实体类与数据库之间的映射工作。

当然,System.ComponentModel.DataAnnotations命名空间的DataAnnotation在EntityFramework程序集中也有相应的API:

HasKey - KeyAttribute:配置此实体类型的主键属性
IsRequired - RequiredAttribute:将此属性配置为必需属性。用于存储此属性的数据库列将不可以为null
HasMaxLength - MaxLengthAttribute:将属性配置为具有指定的最大长度
IsConcurrencyToken - ConcurrencyCheckAttribute:将属性配置为用作开放式并发标记
IsRowVersion - TimestampAttribute:将属性配置为数据库中的行版本。实际数据类型将因使用的数据库提供程序而异。将属性设置为行版本会自动将属性配置为开放式并发标记。
上面这些API均无需引用EntityFramework,推荐使用DataAnnotation方式来设置映射。

以下API的DataAnnotation特性是在EntityFramework中定义,如果也使用DataAnnotation方式来设置映射,就会给实体类增加额外的第三方程序集的依赖。所以以下API的映射推荐使用FluentAPI的方式来设置映射:

ToTable - TableAttribute:配置此实体类型映射到的表名
HasColumnName - ColumnAttribute:配置用于存储属性的数据库列的名称
HasForeignKey - ForeignKeyAttribute:将关系配置为使用在对象模型中的外键属性。如果未在对象模型中公开外键属性,则使用Map方法
Ignore - NotMappedAttribute:从模型中排队某个属性,使该属性不会映射到数据库
HasRequired:通过此实体类型配置必需关系。除非指定此关系,否则实体类型的实例将无法保存到数据库。数据库中的外键不可为null。
HasOptional:从此实体类型配置可选关系。实体类型的实例将能保存到数据库,而无需指定此关系。数据库中的外键可为null。
HasMany:从此实体类型配置一对多关系。
WithOptional:将关系配置为required:optional。(required:0…1端的1,表示必需,不可为null;optional:0…1端的0,表示可选,可为null。下同)
WithOptionalDependent:将关系配置为optional:optional。要配置的实体类型将成为依赖对象,且包含主体的外键。作为关系目标的实体类型将成为关系中的主体。
WithOptionalPrincipal:将关系配置为optional:optional。要配置的实体类型将成为关系中的主体。作为关系目标的实体类型将成为依赖对象,且包含主体的外键。
WithRequired:将关系的指定端配置为必需的,且在关系的另一端有导航属性。
WithRequiredDependent:将关系配置为required:required。要配置的实体类型将成为依赖对象,且包含主体的外键。作为关系目标的实体类型将成为关系中的主体。
WithRequiredPrincipal:将关系配置为required:required。要配置的实体类型将成为关系中的实体。作为关系目标的实体类型将成为依赖对象,且包含主体的外键。
WillCascadeOnDelete:配置是否对关系启用级联删除。
Map:将关系配置为使用未在对象模型中公开的外键属性。可通过指定配置操作来自定义列和表。如果指定了空的配置操作,则约定将生成列名。如果在对象模型中公开了外键属性,则使用 HasForeignKey 方法。并非所有关系都支持在对象模型中公开外键属性。
MapKey:配置外键的列名。
ToTable:配置外键列所在表的名称和架构。
经常用到的DataAnnotation与FluentAPI列举完了,使用上还是遵守这个原则:

如果在System.ComponentModel.DataAnnotations命名空间存在相应的标签,就使用 DataAnnotation 的方式,如果不存在,则使用 FluentAPI 的方式。

(三) 映射代码示例

实体类关系图:

上图是一个以用户信息为中心的实体关系图,关系说明如下:

一个用户可拥有一个可选的用户扩展信息(1 - 0)
一个用户扩展信息拥有一个必需的所属用户信息(0 - 1)
一个用户扩展信息拥有一个用户地址信息(复合类型)
一个用户可对应多个登录日志信息(1 - N)
一个登录日志拥有一个必需的所属用户信息(N- 1)
一个用户可以拥有多个角色(N - N)
一个角色可以分配给多个用户(N - N)
实体类定义:

用户信息

namespace GMF.Demo.Core.Models
{
    /// <summary>
    ///     实体类——用户信息
    /// </summary>
    [Description("用户信息")]
    public class Member : Entity
    {
        public int Id { get; set; }  

        [Required]
        [StringLength(20)]
        public string UserName { get; set; }  

        [Required]
        [StringLength(32)]
        public string Password { get; set; }  

        [Required]
        [StringLength(20)]
        public string NickName { get; set; }  

        [Required]
        [StringLength(50)]
        public string Email { get; set; }  

        /// <summary>
        /// 获取或设置 用户扩展信息
        /// </summary>
        public virtual MemberExtend Extend { get; set; }  

        /// <summary>
        /// 获取或设置 用户拥有的角色信息集合
        /// </summary>
        public virtual ICollection<Role> Roles { get; set; }  

        /// <summary>
        /// 获取或设置 用户登录记录集合
        /// </summary>
        public virtual ICollection<LoginLog> LoginLogs { get; set; }
    }
}
用户扩展信息

namespace GMF.Demo.Core.Models
{
    /// <summary>
    ///     实体类——用户扩展信息
    /// </summary>
    [Description("用户扩展信息")]
    public class MemberExtend : Entity
    {
        /// <summary>
        /// 初始化一个 用户扩展实体类 的新实例
        /// </summary>
        public MemberExtend()
        {
            Id = CombHelper.NewComb();
        }  

        public Guid Id { get; set; }  

        public string Tel { get; set; }  

        public MemberAddress Address { get; set; }  

        public virtual Member Member { get; set; }
    }
}
用户地址信息

namespace GMF.Demo.Core.Models
{
    /// <summary>
    /// 用户地址信息
    /// </summary>
    public class MemberAddress
    {
        [StringLength(10)]
        public string Province { get; set; }  

        [StringLength(20)]
        public string City { get; set; }  

        [StringLength(20)]
        public string County { get; set; }  

        [StringLength(60, MinimumLength = 5)]
        public string Street { get; set; }
    }
}  

登录记录信息

namespace GMF.Demo.Core.Models
{
    /// <summary>
    /// 实体类——登录记录信息
    /// </summary>
    [Description("登录记录信息")]
    public class LoginLog : Entity
    {
        /// <summary>
        /// 初始化一个 登录记录实体类 的新实例
        /// </summary>
        public LoginLog()
        {
            Id = CombHelper.NewComb();
        }  

        public Guid Id { get; set; }  

        [Required]
        [StringLength(15)]
        public string IpAddress { get; set; }  

        /// <summary>
        /// 获取或设置 所属用户信息
        /// </summary>
        public virtual Member Member { get; set; }
    }
}  

角色信息

namespace GMF.Demo.Core.Models
{
    /// <summary>
    ///     实体类——角色信息
    /// </summary>
    [Description("角色信息")]
    public class Role : Entity
    {
        public Role()
        {
            Id = CombHelper.NewComb();
        }  

        public Guid Id { get; set; }  

        [Required]
        [StringLength(20)]
        public string Name { get; set; }  

        [StringLength(100)]
        public string Description { get; set; }  

        /// <summary>
        /// 获取或设置 角色类型
        /// </summary>
        public RoleType RoleType { get; set; }  

        /// <summary>
        /// 获取或设置 角色类型的数值表示,用于数据库存储
        /// </summary>
        public int RoleTypeNum { get; set; }  

        /// <summary>
        ///     获取或设置 拥有此角色的用户信息集合
        /// </summary>
        public virtual ICollection<Member> Members { get; set; }
    }
}  

角色类型(枚举)

namespace GMF.Demo.Core.Models
{
    /// <summary>
    /// 表示角色类型的枚举
    /// </summary>
    [Description("角色类型")]
    public enum RoleType
    {
        /// <summary>
        /// 用户类型
        /// </summary>
        [Description("用户角色")]
        User = 0,  

        /// <summary>
        /// 管理员类型
        /// </summary>
        [Description("管理角色")]
        Admin = 1
    }
}  

实体类映射:

实体类映射中,关系的映射配置在关系的两端都可以配置。例如,用户信息与登录信息的 一对多 关系可以在用户信息端配置:

HasMany(m => m.LoginLogs).WithRequired(n => n.Member);

等效于在登录日志信息端配置:

HasRequired(m => m.Member).WithMany(n => n.LoginLogs);

但是,如果所有的关系映射都在作为主体的用户信息端进行配置,势必造成用户信息端配置的臃肿与职责不明。所以,为了保持各个实体类型的职责单一,实体关系推荐在关系的非主体端进行映射。

用户信息映射类,用户信息是关系的主体,所有的关系都不在此映射类中进行配置

namespace GMF.Demo.Core.Data.Configurations
{
    public class MemberConfiguration : EntityTypeConfiguration<Member>
    {
    }
}
用户扩展信息映射类,配置用户扩展信息与用户信息的 0:1 关系

namespace GMF.Demo.Core.Data.Configurations
{
    public class MemberExtendConfiguration : EntityTypeConfiguration<MemberExtend>
    {
        public MemberExtendConfiguration()
        {
            HasRequired(m => m.Member).WithOptional(n => n.Extend);
        }
    }
}
用户地址信息映射类,配置用户地址信息的复杂类型映射,复杂类型继承于 ComplexTypeConfiguration<>

namespace GMF.Demo.Core.Data.Configurations
{
    public class MemberAddressConfiguration : ComplexTypeConfiguration<MemberAddress>
    {
        public MemberAddressConfiguration()
        {
            Property(m => m.Province).HasColumnName("Province");
            Property(m => m.City).HasColumnName("City");
            Property(m => m.County).HasColumnName("County");
            Property(m => m.Street).HasColumnName("Street");
        }
    }
}
登录记录信息映射,配置登录信息与用户信息的 N:1 的关系

namespace GMF.Demo.Core.Data.Configurations
{
    public class LoginLogConfiguration : EntityTypeConfiguration<LoginLog>
    {
        public LoginLogConfiguration()
        {
            HasRequired(m => m.Member).WithMany(n => n.LoginLogs);
        }
    }
}
角色信息映射,配置角色信息与用户信息的 N:N 的关系

namespace GMF.Demo.Core.Data.Configurations
{
    public class RoleConfiguration : EntityTypeConfiguration<Role>
    {
        public RoleConfiguration()
        {
            HasMany(m => m.Members).WithMany(n => n.Roles);
        }
    }
}
映射类的应用:

映射类需要在数据访问上下文中进行应用才能生效,只要在DbContext的OnModelCreating方法中进行映射配置添加即可。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    //移除一对多的级联删除约定,想要级联删除可以在 EntityTypeConfiguration<TEntity>的实现类中进行控制
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
    //多对多启用级联删除约定,不想级联删除可以在删除前判断关联的数据进行拦截
    //modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();  

    modelBuilder.Configurations.Add(new MemberConfiguration());
    modelBuilder.Configurations.Add(new MemberExtendConfiguration());
    modelBuilder.Configurations.Add(new MemberAddressConfiguration());
    modelBuilder.Configurations.Add(new RoleConfiguration());
    modelBuilder.Configurations.Add(new LoginLogConfiguration());
}
三、数据迁移

经过上面的折腾,数据库结构已经大变,项目当然运行不起来了。

根据提示,必须进行迁移来更新数据库结构。EntityFramework的数据迁移通过 NuGet 来进行。打开程序包管理器控制台(Package Manager Console),键入“ get-help EntityFramework”命令,可以获得相关的帮助信息。

若想了解各个子命令的帮助细节,也可键入“get-help 子命令”命令,例如:get-help Enable-Migrations

下面我们来对项目进行数据迁移,在我们的项目中,EntityFramework的依赖止于项目GMF.Demo.Core.Data,项目的数据迁移也是在此项目中进行。迁移步骤如下:

在“程序包管理器控制台”键入命令:Enable-Migrations -ProjectName GMF.Demo.Core.Data

添加后,项目中添加了一个名为Migrations的文件夹

添加生成以下代码:
namespace GMF.Demo.Core.Data.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration<DemoDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }  

        protected override void Seed(DemoDbContext 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. E.g.
            //
            //    context.People.AddOrUpdate(
            //      p => p.FullName,
            //      new Person { FullName = "Andrew Peters" },
            //      new Person { FullName = "Brice Lambson" },
            //      new Person { FullName = "Rowan Miller" }
            //    );
            //
        }
    }
}
方法Seed中可以进行数据迁移后的数据初始化工作,将在每次迁移之后运行。如上代码所示,AddOrUpdate是IDbSet<TEntity>的扩展方法,如果指定条件的数据不存在,则会添加,如果存在,会更新。所以,如果数据是通过此方法来初始化的,在与业务更新之后,再次进行数据迁移后,还是会被还原。

还有一个名为InitialCreate的类,配置生成数据库的细节:

InitialCreate

namespace GMF.Demo.Core.Data.Migrations
{
    public partial class InitialCreate : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "dbo.Roles",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        Name = c.String(nullable: false, maxLength: 20),
                        Description = c.String(maxLength: 100),
                        IsDeleted = c.Boolean(nullable: false),
                        AddDate = c.DateTime(nullable: false),
                        Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
                    })
                .PrimaryKey(t => t.Id);  

            CreateTable(
                "dbo.Members",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        UserName = c.String(nullable: false, maxLength: 20),
                        Password = c.String(nullable: false, maxLength: 32),
                        NickName = c.String(nullable: false, maxLength: 20),
                        Email = c.String(nullable: false, maxLength: 50),
                        IsDeleted = c.Boolean(nullable: false),
                        AddDate = c.DateTime(nullable: false),
                        Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
                    })
                .PrimaryKey(t => t.Id);  

            CreateTable(
                "dbo.MemberExtends",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        IsDeleted = c.Boolean(nullable: false),
                        AddDate = c.DateTime(nullable: false),
                        Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
                        Member_Id = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Members", t => t.Member_Id)
                .Index(t => t.Member_Id);  

            CreateTable(
                "dbo.LoginLogs",
                c => new
                    {
                        Id = c.Guid(nullable: false),
                        IpAddress = c.String(nullable: false, maxLength: 15),
                        IsDeleted = c.Boolean(nullable: false),
                        AddDate = c.DateTime(nullable: false),
                        Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
                        Member_Id = c.Int(),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Members", t => t.Member_Id)
                .Index(t => t.Member_Id);  

            CreateTable(
                "dbo.MemberRoles",
                c => new
                    {
                        Member_Id = c.Int(nullable: false),
                        Role_Id = c.Guid(nullable: false),
                    })
                .PrimaryKey(t => new { t.Member_Id, t.Role_Id })
                .ForeignKey("dbo.Members", t => t.Member_Id, cascadeDelete: true)
                .ForeignKey("dbo.Roles", t => t.Role_Id, cascadeDelete: true)
                .Index(t => t.Member_Id)
                .Index(t => t.Role_Id);  

        }  

        public override void Down()
        {
            DropIndex("dbo.MemberRoles", new[] { "Role_Id" });
            DropIndex("dbo.MemberRoles", new[] { "Member_Id" });
            DropIndex("dbo.LoginLogs", new[] { "Member_Id" });
            DropIndex("dbo.MemberExtends", new[] { "Member_Id" });
            DropForeignKey("dbo.MemberRoles", "Role_Id", "dbo.Roles");
            DropForeignKey("dbo.MemberRoles", "Member_Id", "dbo.Members");
            DropForeignKey("dbo.LoginLogs", "Member_Id", "dbo.Members");
            DropForeignKey("dbo.MemberExtends", "Member_Id", "dbo.Members");
            DropTable("dbo.MemberRoles");
            DropTable("dbo.LoginLogs");
            DropTable("dbo.MemberExtends");
            DropTable("dbo.Members");
            DropTable("dbo.Roles");
        }
    }
}  

执行“Add-Migration FirstMigration”命令,添加一个名为FirstMigration的迁移

执行“Update-Database”命令,更新数据库架构

如果更新数据库存在冲突而不能执行更新,可以添加 -Force强制执行,例如:“Update-Database -Force”
设置自动迁移
每次都通过控制台来进行迁移太过麻烦,可以设置为自动迁移。
有以下两个参数可以对自动迁移进行设置:
1. AutomaticMigrationsEnabled:获取或设置 指示迁移数据库时是否可使用自动迁移的值。
2. AutomaticMigrationDataLossAllowed:获取或设置 指示是否可接受自动迁移期间的数据丢失的值。如果设置为false,则将在数据丢失可能作为自动迁移一部分出现时引发异常。
修改迁移的Configuration类如下:
namespace GMF.Demo.Core.Data.Migrations
{
    internal sealed class Configuration : DbMigrationsConfiguration<DemoDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }  

        protected override void Seed(DemoDbContext context)
        {
            List<Member> members = new List<Member>
            {
                new Member { UserName = "admin", Password = "123456", Email = "[email protected]", NickName = "管理员" },
                new Member { UserName = "gmfcn", Password = "123456", Email = "[email protected]", NickName = "郭明锋" }
            };
            DbSet<Member> memberSet = context.Set<Member>();
            memberSet.AddOrUpdate(m => new { m.Id }, members.ToArray());
        }
    }
}
修改数据库初始化策略如下:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<DemoDbContext, Configuration>());
四、代码重构

经过上面的演练,我们的项目变成如下图所示:

现在的项目中,数据访问上下文DemoDbContext代码如下所示:

namespace GMF.Demo.Core.Data.Context
{
    /// <summary>
    ///     Demo项目数据访问上下文
    /// </summary>
    [Export(typeof(DbContext))]
    public class DemoDbContext : DbContext
    {
        #region 构造函数  

        /// <summary>
        ///     初始化一个 使用连接名称为“default”的数据访问上下文类 的新实例
        /// </summary>
        public DemoDbContext()
            : base("default") { }  

        /// <summary>
        /// 初始化一个 使用指定数据连接名称或连接串 的数据访问上下文类 的新实例
        /// </summary>
        public DemoDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString) { }  

        #endregion  

        #region 属性  

        public DbSet<Role> Roles { get; set; }  

        public DbSet<Member> Members { get; set; }  

        public DbSet<MemberExtend> MemberExtends { get; set; }  

        public DbSet<LoginLog> LoginLogs { get; set; }  

        #endregion  

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //移除一对多的级联删除约定,想要级联删除可以在 EntityTypeConfiguration<TEntity>的实现类中进行控制
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            //多对多启用级联删除约定,不想级联删除可以在删除前判断关联的数据进行拦截
            //modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();  

            modelBuilder.Configurations.Add(new MemberConfiguration());
            modelBuilder.Configurations.Add(new MemberExtendConfiguration());
            modelBuilder.Configurations.Add(new MemberAddressConfiguration());
            modelBuilder.Configurations.Add(new RoleConfiguration());
            modelBuilder.Configurations.Add(new LoginLogConfiguration());
        }
    }
}
由代码可以看出,当前的上下文类与业务实体是强耦合的,分别耦合在DbSet<TEntity>的属性与OnModelCreating方法上。如果要解耦,对于属性,可以使用DbContext.Set<TEntity>()方法来实现指定实体的属性,对于OnModelCreating中的方法实现中的映射配置对象,则可提取一个通用接口,通过接口进行分别映射。

定义接口如下:

namespace GMF.Component.Data
{
    /// <summary>
    ///     实体映射接口
    /// </summary>
    [InheritedExport]
    public interface IEntityMapper
    {
        /// <summary>
        ///     将当前实体映射对象注册到当前数据访问上下文实体映射配置注册器中
        /// </summary>
        /// <param name="configurations">实体映射配置注册器</param>
        void RegistTo(ConfigurationRegistrar configurations);
    }
}
IEntityMapper接口添加了MEF的InheritedExport特性,该特性可以沿着继承链传递所施加的特性。在需要的时候,就可以通过ImportManyAttribute一次性导出所有实现了IEntityMapper接口的实现类对象。

在实体映射类中添加IEntityMapper的实现,如角色映射类中:

namespace GMF.Demo.Core.Data.Configurations
{
    public class RoleConfiguration : EntityTypeConfiguration<Role>, IEntityMapper
    {
        public RoleConfiguration()
        {
            HasMany(m => m.Members).WithMany(n => n.Roles);
        }  

        public void RegistTo(ConfigurationRegistrar configurations)
        {
            configurations.Add(this);
        }
    }
}
下面来对数据访问上下文进行改造,并转移到数据组件 GMF.Component.Data 中。

添加一个IEnumerable<IEntityMapper>类型的属性EntityMappers,并添加ImportManyAttribute,用于引入所有实现了IEntityMapper的类的对象。

在重写的OnModelCreating方法中,遍历EntityMappers集合,调用其中的RegistTo进行实体映射类对象的添加。

namespace GMF.Component.Data
{
    /// <summary>
    ///     EF数据访问上下文
    /// </summary>
    [Export(typeof (DbContext))]
    public class EFDbContext : DbContext
    {
        public EFDbContext()
            : base("default") { }  

        public EFDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString) { }  

        [ImportMany]
        public IEnumerable<IEntityMapper> EntityMappers { get; set; }  

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();  

            if (Configuration == null)
            {
                return;
            }
            foreach (var mapper in EntityMappers)
            {
                mapper.RegistTo(modelBuilder.Configurations);
            }
        }
    }
}
上下文EFDbContext在单元操作类 EFRepositoryContext 类中进行使用:

namespace GMF.Component.Data
{
    /// <summary>
    ///     数据单元操作类
    /// </summary>
    [Export(typeof (IUnitOfWork))]
    internal class EFRepositoryContext : UnitOfWorkContextBase
    {
        /// <summary>
        ///     获取 当前使用的数据访问上下文对象
        /// </summary>
        protected override DbContext Context
        {
            get { return EFDbContext; }
        }  

        [Import(typeof (DbContext))]
        private EFDbContext EFDbContext { get; set; }
    }
}
经过如此重构,DbContext上下文就与实体无关了,数据访问功能与业务实体便完成解耦。

我们来对比重构前后的变化:

我们来对比一下重构前后的变化:

重构前:
1.【缺】上下文与实体类强耦合,不通用,每添加一个实体都要修改上下文类
2.【优】上下文中有实体信息,支持手动数据迁移,可以使用命令行进行迁移功能的启用、添加迁移脚本
重构后:
1.【优】上下文与实体类解耦,能通用
2.【缺?优?】上下文中没有实体信息,无法支持手动迁移(手动迁移需要上下文中的实体信息来生成迁移脚本),但支持自动迁移(使用运行时的上下文,这时已经加载了实体信息了)

那么,是否重构,就看具体需求而定了。不过项目最终还是要使用自动迁移的,因为在项目上线之后,如果需要迁移线上的数据库,那时数据库在服务器中了,就不可能使用VS的控制台命令行的方式来添加迁移了,只能启用自动迁移来完成数据库结构的变更迁移。

还有个问题,就是没有了带实体的上下文类,无法通过命令行的方式来启用数据迁移功能了,怎样启用数据迁移呢?这时需要手动在GMF.Demo.Core.Data项目中手动来添加一个Migrations文件夹,再在文件夹中添加一个继承自 DbMigrationsConfiguration<TDbContext> 的Configuration类来启用数据迁移功能,并且设置AutomaticMigrationsEnabled与AutomaticMigrationDataLossAllowed两个属性为true来启用自动迁移。最后,还需要将数据库初始化策略更改为“迁移数据库到最新版本(MigrateDatabaseToLatestVersion)”,即 Database.SetInitializer(new MigrateDatabaseToLatestVersion<TDbContext, Configuration>()); 来引用Configuration类。

五、源码获取

GMFrameworkForBlog4.zip

为了让大家能第一时间获取到本架构的最新代码,也为了方便我对代码的管理,本系列的源码已加入微软的开源项目网站 http://www.codeplex.com,地址为:

https://gmframework.codeplex.com/

原文链接:http://www.cnblogs.com/guomingfeng/archive/2013/06/15/mvc-ef-configuration-migration.html

EF Code First:实体映射,数据迁移,重构(1)

时间: 2024-11-01 01:49:20

EF Code First:实体映射,数据迁移,重构(1)的相关文章

EF6.0+APS.NET MVC5.0项目初探三(code first实体映射到数据库)

到这里架构就搭建完了,该向里面填充东西的时候了,如上篇:EF6.0+APS.NET MVC5.0项目初探二(类库引用关系及说明) 第一步 :在需要添加EF的类库Domain.DbContext上右击->管理NuGet程序包->找到Entity FrameWork下载安装. 如图: 第二步:新建DbContext 第三步:在类库Domain.Entity上添加引用System.ComponentModel.DataAnnotations(用于验证的引用) 并新建实体类. 1 using Syst

EF Code First数据库映射规则及配置

EF Code First数据库映射规则主要包括以下方面: 1.表名及所有者映射 Data Annotation: 指定表名 1 using System.ComponentModel.DataAnnotations;2 3 [Table("Product")]4 public class Product指定表名及用户 using System.ComponentModel.DataAnnotations;[Table("Product", Schema = &qu

EF应用CodeFirst模式,数据迁移的基本用法要点摘记

第一次使用EntityFramework做CodeFirst的开发,在做数据迁移时遇到不少问题,花费了一整天的时间学习调整,总算时学会了基本用法和要点.现在整理后贴出来,希望对和我一样的初用者能有一些帮助,少走一些弯路,少花一点时间摸索,都是值得的. 一. 模型设计 1.  遵循EF标准,注意表关系配对 2.  数据模型里尽量把必须的属性和说明都写全 3.  EF默认id字段为主键,如果没有,需指定主键 二. 数据迁移 1.  命令运行环境:visual studio工具栏->工具->NuGe

第05章 EntityFramework6:Code Frist实体关系设计--迁移数据库--初始化数据

1.写作背景 在写这篇文章前,本想继续补充完前面介绍到WebApi知识点,但下面的讲解,要进行实体/模型设计,并对数据进行CRUD实际操作了,所以先介绍一下EF基本使用啦! 本章为何不以最新版的EF7(下一章也会介绍它)先讲? 一是EF7还处于beta阶段,功能也没有开发完毕,生产环境应用有诸多问题: 二是EF7想比之前的版本变化还是很多的,我们先从代码对比上了解,以便你以后从EF6升级到EF7: 三是EF6目前也不能在ASP.NET 5类型项目中使用,但它对ASP.NET 5以外的类型项目非常

一步一步学EF系列【3、数据迁移】

我们每篇的内容都不多,所以希望在学习的过程中最后能亲自敲一下代码 这样更有利于掌握. 我们现在接着上篇的例子,我们现在给随便的表增加一个字段 CreateTime 创建日期 运行一下 看看会怎么样 修改实体类,代码给大家分享一下 public partial class Post { /// <summary> /// 随笔的主键id /// </summary> public int PostId { get; set; } // 随笔的标题 public string Post

Entity Framework Code First实体关联数据加载

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 在项目过程中,两个实体数据之间在往往并非完全独立的,而是存在一定的关联关系,如一对一.一对多及多对多等关联.存在关联关系的实体,经常根据一个实体的实例来查询获取与之关联的另外实体的实例. Entity Framework常用处理数据关联加载的方

【EF Code First】Migrations数据库迁移

1,打开工具->NuGet程序管理器->程序包管理器控制台 默认项目中要选择  数据访问上下文类  所在的项目 我的DB是在命名空间CodeFirst.UI下的所以选择CodeFirst.UI 然后执行Enable-Migrations 如果失败,有一种错误是会提醒你未安装XXXX或者缺少XXXX(遇到过一次,错误信息未保留,下次有机会补充), 需要安装 (不知道是什么鬼东西): 去官网上下载安装可以了http://www.microsoft.com/zh-cn/download/detail

EF Code First教程-03 数据库迁移Migrator

要在nuget 程序包管理控制台中输入命令 基本命令 Enable-Migrations   //打开数据库迁移 Add-Migration AddBlogUrl    //新增一个数据库迁移版本   AddBlogUrl是要新增版本名称,这个名称必须是唯一的,不能重复 Update-Database  //更新数据库 Update-Database –TargetMigration: 版本名称  //迁移到特定版本(回滚) 详细内容请看:http://www.cnblogs.com/inday

EF 中 Code First 的数据迁移以及创建视图

写在前面: EF 中 Code First 的数据迁移网上有很多资料,我这份并没什么特别.Code First 创建视图网上也有很多资料,但好像很麻烦,而且亲测好像是无效的方法(可能是我太笨,没搞成功),我摸索出了一种简单有效的方法,这里分享给大家. EF是Entity Framework(实体框架)的简写,是微软出品的用来操作数据库的一个框架,会ASP.NET MVC的朋友对他肯定都不陌生.由于学艺不精,我对EF存在一疑虑,就不以[提问]的方式来问了,我以[总结]的方式来表达,如果总结有误的地

ef core数据迁移的一点小感悟

ef core在针对mysql数据迁移的时候,有些时候没法迁移...有两种情况没法迁移,一种是因为efcore的bug问题导致没法迁移,这个在github上有个问题集,另外一种是对数据表进行较大幅度的变更,导致外键导航之类的变更较多,无法正常迁移,并且涉及到该表的迁移有多条. 然后我就自己琢磨,在不删除所有迁移记录的情况下怎么顺利把数据表更改掉,然后摸索了一阵,确实成功了,步骤如下. 1.先把表备份后,删除这个表.2. 在migration记录表中,涉及到该表的迁移全部删除.3. 删除迁移记录中