• EF7创建模型入门篇


    在EF7中,创建一个模型是非常重要的步骤。本文将使用微软官方文档中的指南,来学习EF7中的创建模型篇,外加一点点个人理解。

    实体类型

    在 EF7 中,你需要使用 modelBuilder.Entity() 方法来告诉 EF7 你要包含哪些类型。默认情况下,EF7 会将实体类型的名称设置为表的名称。但是,你可以使用 ToTable() 方法来覆盖默认行为。
    如果你的数据库中有多个模式(schema),你可以使用 ToTable() 方法的另一个重载版本来指定表所属的架构。如果你想要为生成的表添加注释,可以使用 HasComment() 方法。如果你不想将某个类映射到数据库中的表。我们可以使用 modelBuilder.Entity().Ignore() 来排除它。

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity()
    		.ToTable("Blogs", schema: "dbo") //.ToTable("Blogs");
    		.HasComment("This table contains blog posts.");
    	modelBuilder.Ignore
    (); }

    共享类型实体类型

    在 EF7 中,你可以将一个类型映射到多个表中。这种情况通常发生在你有一组具有相似属性的类型,这些属性在不同的表中都需要使用。在这种情况下,你可以使用 ModelBuilder.SharedTypeEntity() 方法来创建一个实体类型,并将其映射到多个表中。

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        var addressEntity = modelBuilder.SharedTypeEntity
    ("Address"); addressEntity.ToTable("CustomerBillingAddresses"); addressEntity.ToTable("CustomerShippingAddresses"); modelBuilder.Entity() .OwnsOne(c => c.BillingAddress, b => { b.WithOwner().HasForeignKey("BillingAddressId"); b.ToTable("CustomerBillingAddresses"); }); modelBuilder.Entity() .OwnsOne(c => c.ShippingAddress, b => { b.WithOwner().HasForeignKey("ShippingAddressId"); b.ToTable("CustomerShippingAddresses"); }); }

    在上面的代码中,我们首先使用 ModelBuilder.SharedTypeEntity() 方法创建一个名为 Address 的实体类型。然后,我们使用 ToTable() 方法将该实体类型映射到多个表中。接下来,我们使用 OwnsOne() 方法来将 BillingAddressShippingAddress 属性映射到具有相应名称的表中。注意,我们还使用了 HasForeignKey() 方法来指定外键的名称。
    使用共享类型实体类型可以使你的代码更加简洁,并提高可维护性。通过使用共享类型实体类型,你可以将一个类型映射到多个表中,而不必在每个实体类型中都定义相同的映射代码。

    实体属性

    如果要排除实体属性,可以使用Ignore()方法。

    modelBuilder.Entity().Ignore(p => p.Age)
    

    定义列名。例如,下面的代码将为Person类中的LastName属性定义列名。

    modelBuilder.Entity().Property(p => p.LastName).HasColumnName("Last_Name")
    

    定义列注释。例如,下面的代码将为Person类中的LastName属性定义注释。

    modelBuilder.Entity().Property(p => p.LastName).HasComment("The last name of the person")
    

    定义列排序规则。例如,下面的代码将为Person类中的LastName属性定义排序规则。

    modelBuilder.Entity().Property(p => p.LastName).UseCollation("SQL_Latin1_General_CP1_CI_AS");
    

    定义列的数据类型(和数据库一致即可)。例如,下面的代码将为Person类中的Age属性定义为int类型:

    modelBuilder.Entity().Property(p => p.Age).HasColumnType("int");
    

    定义列的最大长度。例如,下面的代码将为Person类中的FirstName属性定义为50个字符的最大长度:

    modelBuilder.Entity().Property(p => p.FirstName).HasMaxLength(50);
    

    定义列的精度和小数位数。例如,下面的代码将为Person类中的Height属性定义为2位小数的精度:

    modelBuilder.Entity().Property(p => p.Height).HasPrecision(5, 2);
    

    定义是否为必需或可选属性。例如,下面的代码将为Person类中的FirstName属性定义为必需属性:

    modelBuilder.Entity().Property(p => p.FirstName).IsRequired();
    

    定义列在表中的顺序。例如,下面的代码将为Person类中的FirstName属性定义为表中第二个列:

    modelBuilder.Entity().Property(p => p.Id).HasColumnOrder(1);
    modelBuilder.Entity().Property(p => p.FirstName).HasColumnOrder(2);
    

    主键

    定义主键。根据约定,名为 Id 或 <类型名称>Id 的属性将被配置为实体的主键。

    internal class User
    {
        public string Id { get; set; } // 主键
        public string Name { get; set; }
    }
    

    如果我们不想使用默认约定规则,可以自定义规则。下面的代码将指定User类的Id属性作为主键并且重写设置主键的名称:

    modelBuilder.Entity().HasKey(p => p.Id).HasName("UserId");
    
    注意主键Id应是有序的

    在 MySQL 中,主键 Id 不是有顺序的时候,可能会导致新增性能下降的原因是,MySQL 默认使用 B-tree 索引来实现主键索引,如果主键 Id 是无序的,那么在插入数据时,MySQL 需要不断寻找合适的位置来插入新数据,这可能会导致 B-tree 索引不断被调整,从而影响插入性能。相反,如果主键 Id 是有序的,MySQL 可以更快速地找到要插入的位置,从而提高插入性能。

    注意主键Id应该是最后生成的

    有些程序可能会有延迟,导致数据库插入是非有序的。场景:假如我们先生成Id再处理业务逻辑。有两个线程,同时并发请求。第一个线程生成好Id 是 3,处理下面业务逻辑时发生了大概五毫秒的延迟。第二个线程也生成好了Id是 4,处理下面业务逻辑时没有延迟,就通过了。所以第二个线程,先进数据库插入Id为4。第一个线程因为有延迟来慢了一步,插入的Id是3。数据库Id就变成无序的了。

    使用基于时间戳的有序Guid作为主键

    对于非复合数字和 GUID 主键,EF Core 根据约定设置值生成。

    EF7中 Guid 是基于时间的算法精确到纳秒。因为一毫秒等于一百万纳秒,所以EF7的Guid一毫秒可以产生一百万的Id。
    优点:不可预测、有序(添加性能高)、在支持Guid(uuid)的数据库中(查询性能高)、开箱即用。
    缺点:(这是可以忽略不计的事情)并发中在同一纳秒内,产生的Id是会重复的。有时钟回拨问题。太长。
    EF7中的Guid有序算法比雪花算法更好。

    1. 雪花算法在并发时,也会重复。因为序列号和时间戳,即使我们配置正确了WorkId。不信你可以写个例子,思路是:10个并发同时生成Id,保存到安全线程字典中。重复就报个错。
    2. 雪花算法需要额外维护WorkId的工作。
    3. 有时钟回拨问题。

    使用方式见这篇文章:《EF7创建模型值生成篇》

    复合键

    复合键是指将多个列作为主键的一种设计模式,这些列在组合起来时才能唯一标识一条记录。相对于单一键,复合键更加灵活,可以更准确地描述实体之间的关系。例如,在一个订单系统中,一个订单可能由多个产品组成,此时可以使用复合键来标识订单编号和产品编号的组合,以确保每个订单中的每个产品都是唯一的。
    使用复合键的优点主要有两点。首先,它提供了更准确的数据描述,特别是在处理多对多关系时,可以更准确地表示关系的唯一性。其次,使用复合键可以提高查询效率,因为复合键可以利用数据库的索引机制,快速定位和访问数据。
    然而,复合键也存在一些缺点。首先,它增加了开发的复杂性,需要更多的设计和规划。其次,在使用ORM框架时,如EF7,复合键的使用需要特殊的处理,例如在Fluent API中进行配置。最后,复合键在某些情况下可能会导致性能问题,例如在大型数据库中,使用复合键可能会影响查询性能。
    在使用EF7时,可以通过Fluent API来配置复合键。以一个用户角色表为例,可以使用以下代码定义一个由用户Id和角色Id的复合主键:

    internal class UserRole
    {
        public string UserId { get; set; } 
        public string RoleId { get; set; }
    }
    public class MyContext : DbContext
    {
        public DbSet<Person> People { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity().HasKey(c => new { c.UserId, c.RoleId });
        }
    }
    

    在这个示例中,使用Fluent API中的HasKey方法来定义复合主键。由于复合键是由多个属性组成的,因此需要将它们放在一个匿名类型中作为参数传递给HasKey方法。

    注意合理的复合键会提高性能。

    MySql为例:当使用复合键并且确保关联数据都已经存在时,插入新数据时可能不会对性能产生太大影响。这是因为在 MySQL 中,使用复合键时,MySQL 会同时使用所有列生成 B-tree 索引,从而提高查询和插入的性能。在插入数据时,如果已经确保了关联数据的存在,那么 MySQL 可以更快速地插入新数据并生成新的索引,从而提高插入性能。
    但是,需要注意,如果你的表结构复杂,或者在插入数据时没有正确使用索引,那么复合键仍然可能会影响插入性能。

    备选键

    为什么要使用EF7备选键?
    在数据库中,主键通常用于唯一标识和检索实体对象。但是,有些情况下可能需要使用备选键来标识实体对象。例如,当主键不适合用作某些查询时,使用备选键可以提高数据库的性能和灵活性。
    优点:

    1. 提高性能:使用备选键可以减少复杂查询中的连接数量和查询时间,从而提高数据库的性能。
    2. 增强灵活性:备选键允许使用其他属性作为查询条件,这样就可以更灵活地查询数据库中的实体对象。
    3. 减少冲突:使用备选键可以避免主键冲突的情况,尤其是在多个实体对象使用同一主键的情况下。

    缺点:

    1. 增加复杂性:使用备选键会增加代码的复杂性,因为需要额外的配置和代码来实现备选键的功能。
    2. 增加维护成本:使用备选键会增加数据库的维护成本,因为需要更多的索引和查询,以及更多的代码来处理备选键。
    3. 影响数据完整性:如果备选键没有正确配置,可能会导致数据完整性的问题,因为重复的备选键可能会导致数据重复或丢失。

    为什么有这些优点和缺点?
    优点是因为备选键可以提供更灵活、更高效的查询和更少的主键冲突。缺点是因为使用备选键需要更多的配置和代码,并且可能会影响数据完整性。
    以下是一个简单的示例,演示如何使用备选键:

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
    
    public class MyContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
    		// 将Email属性指定为备选键
            modelBuilder.Entity().HasAlternateKey(p => p.Email); 
        }
    }
    

    复合备选键

    为什么要使用复合备选键?
    使用复合备选键的一个重要原因是在某些情况下,单个列可能不足以唯一标识表中的每一行。例如,在一个电影数据库中,如果只使用电影名称作为主键,则会出现多个电影名称相同的情况。因此,需要使用复合备选键,通过多个列来确定每个电影的唯一性。
    优点

    1. 更加灵活的数据建模:复合备选键使得数据建模更加灵活,可以在表中使用多个列来确定唯一性。这使得数据建模更加符合实际情况,可以更好地支持复杂的业务场景。
    2. 更好的性能:使用复合备选键可以提高数据库的性能。这是因为使用多个列来唯一标识行,可以减少在表中的扫描次数,从而提高查询性能。
    3. 更好的数据完整性:使用复合备选键可以更好地保证数据完整性。在使用复合备选键的表中,每个行的唯一性都由多个列决定,这使得在数据插入和更新时,更难发生数据冲突和错误。

    缺点

    1. 复杂性:使用复合备选键会增加表的复杂性。需要在表中定义多个列,以确定唯一性。此外,在查询时,需要指定多个列作为条件,以获取唯一的行。这可能会增加代码的复杂性,需要更加谨慎地编写代码。
    2. 不支持自动增长:使用复合备选键时,不支持自动增长功能。这是因为每个行的唯一性都是由多个列来决定的,如果一个列是自动增长的,就不能保证每一行都是唯一的。

    使用方式
    使用Fluent API是定义复合备选键的最佳方式。以下是使用Fluent API定义复合备选键的示例:

    public class Person
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }
    }
    
    public class PersonContext : DbContext
    {
        public DbSet<Person> Persons { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity()
    			// 将多个属性配置为备选键(即复合备选键)
    			.HasAlternateKey(p => new { p.FirstName, p.LastName, p.DateOfBirth })
    			// 可配置备选键的索引和唯一约束的名称:
    			.HasName("FirstName_LastName_DateOfBirth");
        }
    }
    

    分组配置模型

    可以使用分组配置,这样可以将实体和关系的配置组织在一起,使代码更具可读性。例如:

    public class BlogConfiguration : IEntityTypeConfiguration<Blog>
    {
        public void Configure(EntityTypeBuilder builder)
        {
            builder.HasKey(x => x.BlogId);
            builder.Property(x => x.Name).IsRequired();
        }
    }
    

    然后在DbContext中使用以下代码将此配置应用于Blog实体:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new BlogConfiguration());
    }
    

    使用数据注释来配置模型

    可以使用数据注释来配置EF7模型。数据注释是一种以属性和类注释的方式来提供元数据的方法。例如:

    public class Blog
    {
        [Key]
        public int BlogId { get; set; }
        [Required]
        public string Name { get; set; }
    }
    

    在这个示例中,我们使用了Key和Required注释来指定BlogId和Name属性的主键和IsRequired标志。

    内置约定

    除了手动配置外,EF7还提供了一些内置约定,可以根据惯例自动推断模型。例如,如果实体中有一个名为Id的属性,EF7会将其作为主键。如果实体之间具有引用关系,EF7会自动创建外键。

    删除现有约定

    如果不想使用内置约定,可以通过调用ModelBuilder.Conventions.Remove方法来删除它们。例如,以下代码删除了为外键列创建索引的约定:

    modelBuilder.Conventions.Remove();
    

    总结:

    在本文中,介绍了如何告诉EF7使用实体类型和过滤类型,并且还说了共享实体类型。我们还介绍了实体属性的配置。还介绍了四种键。介绍了使用Fluent API和数据注释来配置EF7模型。我们还了解了EF7的内置约定,并学习了如何删除现有约定。使用这些技术,可以轻松地创建和配置EF7模型,并更好地管理数据库访问代码。

  • 相关阅读:
    【小笔记】面对一个没搞过的任务,如何选择合适的算法模型?
    PDF文件怎么合并在一起?这三种方法快利用起来
    springboot毕设项目打印助手平台21swx(java+VUE+Mybatis+Maven+Mysql)
    AIGC创作系统ChatGPT源码,AI绘画源码,支持最新GPT-4-Turbo模型,支持DALL-E3文生图
    C#数组介绍
    Maven
    【Python】逻辑回归变量的显著性分析
    软件测试面试真题 | 什么是PO设计模式?
    leetcode-23.合并K个升序链表
    题解:ABC319C - False Hope
  • 原文地址:https://www.cnblogs.com/YataoFeng/p/17183466.html