配置模型

使用配置的时候,尽量不要用到特别高级的特性,以免将模型与数据库绑死,对后来业务造成困难。

表类配置

表名称

按照约定,每个实体类型将设置为映射到与公开实体的 DbSet 属性同名的数据库表。 如果给定实体不存在 DbSet,则使用类名称。

  1. //数据批注
  2. [Table("blogs")]
  3. public class Blog
  4. {
  5. public int BlogId { get; set; }
  6. public string Url { get; set; }
  7. }
  8. //fluent API
  9. protected override void OnModelCreating(ModelBuilder modelBuilder)
  10. {
  11. modelBuilder.Entity<Blog>()
  12. .ToTable("blogs");
  13. }

表架构

使用关系数据库时,表按约定在数据库的默认架构中创建。 例如,Microsoft SQL Server 将使用 dbo 架构 (SQLite 不支持) 的架构。
你可以配置要在特定架构中创建的表,如下所示:

  1. //数据注解
  2. [Table("blogs", Schema = "blogging")]
  3. public class Blog
  4. {
  5. public int BlogId { get; set; }
  6. public string Url { get; set; }
  7. }
  8. //fluent API
  9. protected override void OnModelCreating(ModelBuilder modelBuilder)
  10. {
  11. modelBuilder.Entity<Blog>()
  12. .ToTable("blogs", schema: "blogging");
  13. }

还可以在模型级别定义 Fluent API 的默认架构,而不是为每个表指定架构:

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. modelBuilder.HasDefaultSchema("blogging");
  4. }

表注释

您可以设置对数据库表设置的任意文本注释,以允许您在数据库中记录架构:

  1. //数据注解
  2. [Comment("Blogs managed on the website")]
  3. public class Blog
  4. {
  5. public int BlogId { get; set; }
  6. public string Url { get; set; }
  7. }
  8. //fluent API
  9. protected override void OnModelCreating(ModelBuilder modelBuilder)
  10. {
  11. modelBuilder.Entity<Blog>()
  12. .HasComment("Blogs managed on the website");
  13. }

表属性配置

模型中的每个实体类型都具有一组属性,这些属性 EF Core 将从数据库中读取和写入数据,如果使用关系数据库,实体属性将映射到表列。
多属性配置:

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. modelBuilder.Entity<Blog>(
  4. b =>
  5. {
  6. b.HasKey("_id");
  7. b.Property(e => e.Author);
  8. b.Property(e => e.Name);
  9. });
  10. modelBuilder.Entity<Post>(
  11. b =>
  12. {
  13. b.HasKey("_id");
  14. b.Property(e => e.Title);
  15. b.Property(e => e.PostedOn);
  16. });
  17. }

排除属性映射

按照约定,具有getter与setter的公共属性都将包含在模型中。
但是可以配置来排除特定属性:

  1. //数据注解
  2. public class Blog
  3. {
  4. public int BlogId { get; set; }
  5. public string Url { get; set; }
  6. [NotMapped]
  7. public DateTime LoadedFromDatabase { get; set; }
  8. }
  9. //fluent API
  10. protected override void OnModelCreating(ModelBuilder modelBuilder)
  11. {
  12. modelBuilder.Entity<Blog>()
  13. .Ignore(b => b.LoadedFromDatabase);
  14. }

配置映射列名

按照约定,使用关系数据库时,实体属性映射到与属性同名的表列。
如果希望使用不同的名称配置列,可以使用以下代码片段:

  1. //数据注解
  2. public class Blog
  3. {
  4. [Column("blog_id")]
  5. public int BlogId { get; set; }
  6. public string Url { get; set; }
  7. }
  8. //fluent API
  9. protected override void OnModelCreating(ModelBuilder modelBuilder)
  10. {
  11. modelBuilder.Entity<Blog>()
  12. .Property(b => b.BlogId)
  13. .HasColumnName("blog_id");
  14. }

数据类型映射

使用关系数据库时,数据库提供程序根据属性的 .NET 类型选择数据类型。 它还会考虑其他元数据,如配置的 最大长度、属性是否是主键的一部分等。
例如,对于用作键的属性,SQL Server 将 DateTime 属性映射到 datetime2(7) 列,并将属性映射 string 到 nvarchar(max) 列或 nvarchar(450) 。
您还可以配置列,以便为列指定精确的数据类型。 例如,以下代码将配置 Url 为一个非 unicode 字符串,其最大长度为 200 ,Rating 小数位数为,小数 5 位数为,小数位数为 2:

  1. //数据注解
  2. public class Blog
  3. {
  4. public int BlogId { get; set; }
  5. [Column(TypeName = "varchar(200)")]
  6. public string Url { get; set; }
  7. [Column(TypeName = "decimal(5, 2)")]
  8. public decimal Rating { get; set; }
  9. }
  10. //fluent API
  11. protected override void OnModelCreating(ModelBuilder modelBuilder)
  12. {
  13. modelBuilder.Entity<Blog>(
  14. eb =>
  15. {
  16. eb.Property(b => b.Url).HasColumnType("varchar(200)");
  17. eb.Property(b => b.Rating).HasColumnType("decimal(5, 2)");
  18. });
  19. }

最大长度

配置最大长度会向数据库提供程序提供有关要为给定属性选择的相应列数据类型的提示。 最大长度仅适用于数组数据类型,例如 string 和 byte[] 。
在下面的示例中,配置最大长度为500将导致 nvarchar(500) 在 SQL Server 上创建类型为的列:

  1. //数据注解
  2. public class Blog
  3. {
  4. public int BlogId { get; set; }
  5. [MaxLength(500)]
  6. public string Url { get; set; }
  7. }
  8. //fluent API
  9. protected override void OnModelCreating(ModelBuilder modelBuilder)
  10. {
  11. modelBuilder.Entity<Blog>()
  12. .Property(b => b.Url)
  13. .HasMaxLength(500);
  14. }

精度小数位数

从 EFCore 5.0 开始,可以使用 Fluent API 来配置精度和小数位数。 它告知数据库提供程序所需的存储空间量。 它仅适用于提供程序允许精度和小数位数变化的数据类型,通常为 decimal 和 DateTime 。
对于 decimal 属性,精度定义表示列将包含的任何值所需的最大位数,而 scale 定义所需的最大小数位数。 对于 DateTime 属性,精度定义表示秒的小数部分所需的最大位数,并且不使用小数位数。
在下面的示例中, Score 将属性配置为具有精度14和小数位数2将导致 decimal(14,2) 在 SQL Server 上创建类型为的列,并且 LastUpdated 将属性配置为具有精度3将导致类型为的列 datetime2(3) :

  1. protected override void OnModelCreating(ModelBuilder modelBuilder)
  2. {
  3. modelBuilder.Entity<Blog>()
  4. .Property(b => b.Score)
  5. .HasPrecision(14, 2);
  6. modelBuilder.Entity<Blog>()
  7. .Property(b => b.LastUpdated)
  8. .HasPrecision(3);
  9. }

必须属性与可选属性

如果属性对于包含有效,则将其视为可选 null 。 如果 null 不是要分配给属性的有效值,则将其视为必需属性。 映射到关系数据库架构时,必需的属性将创建为不可为 null 的列,而可选属性则创建为可以为 null 的列。

  1. public class Customer
  2. {
  3. public int Id { get; set; }
  4. public string FirstName { get; set; } // Required by convention
  5. public string LastName { get; set; } // Required by convention
  6. public string? MiddleName { get; set; } // Optional by convention
  7. // Note the following use of constructor binding, which avoids compiled warnings
  8. // for uninitialized non-nullable properties.
  9. public Customer(string firstName, string lastName, string? middleName = null)
  10. {
  11. FirstName = firstName;
  12. LastName = lastName;
  13. MiddleName = middleName;
  14. }
  15. }

显示配置

可以按如下所示将 “约定” 可以为 “可选” 的属性配置为 “必需”:

//注解
public class Blog
{
    public int BlogId { get; set; }

    [Required]
    public string Url { get; set; }
}
//fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .IsRequired();
}

列排序

可以在文本列上定义排序规则,以确定如何对它们进行比较和排序。 例如,以下代码段将 SQL Server 列配置为不区分大小写:

modelBuilder.Entity<Customer>().Property(c => c.Name)
    .UseCollation("SQL_Latin1_General_CP1_CI_AS");

列注释

您可以设置在数据库列上设置的任意文本注释,以允许您在数据库中记录架构:

//注解
public class Blog
{
    public int BlogId { get; set; }

    [Comment("The URL of the blog")]
    public string Url { get; set; }
}
//fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .HasComment("The URL of the blog");
}

表主键配置

键充当每个实体实例的唯一标识符。 EF 中的大多数实体都有一个键,此键映射到关系数据库中 主键 属性。

配置主键

按照约定,将名为 Id 或的属性 Id 配置为实体的主键。

internal class Car
{
    public string Id { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

internal class Truck
{
    public string TruckId { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

若不遵循默认约定,可以使用注解或fluent API来配置主键:

//注解
internal class Car
{
    [Key]
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}
//fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasKey(c => c.LicensePlate);
}

当然还可以通过fluent API来配置复合主键(只能用API来配置复合主键):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasKey(c => new { c.State, c.LicensePlate });
}

主键名称

按照约定,使用名称创建关系数据库主键 PK_ 。 可以按如下所示配置 primary key 约束的名称:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(b => b.BlogId)
        .HasName("PrimaryKey_BlogId");
}

主键类型

虽然 EF Core 支持使用任何基元类型的属性作为主键(包括 string 、 Guid 和其他类型),但 byte[] 并不是所有数据库都支持将所有类型作为键。 在某些情况下,可以自动将键值转换为支持的类型,否则应 手动指定转换。
在将新实体添加到上下文时,键属性必须始终具有非默认值,但某些类型将 由数据库生成。 在这种情况下,当添加实体进行跟踪时,EF 会尝试生成一个临时值。 在调用 SaveChanges 后,临时值将替换为数据库生成的值。
按照约定,如果应用程序不提供值,则将 “short”、”int”、”long” 或 “Guid” 类型的非复合主键设置为为插入的实体生成值。 您的数据库提供程序通常会负责必要的配置;例如,SQL Server 中的数字主键将自动设置为标识列。

类型值的配置

数据库列的值可以通过各种方式生成:主键列经常是自动递增的整数,其他列具有默认值或计算的值等。此页详细介绍了 EF Core 的配置值生成的各种模式。

默认值

在关系数据库中,可以使用默认值来配置列;如果插入的行没有该列的值,将使用默认值。
可以在属性上配置默认值:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Rating)
        .HasDefaultValue(3);
}

还可以指定用于计算默认值的 SQL 片段:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

计算列

在大多数关系数据库上,列可以配置为在数据库中计算其值,通常具有引用其他列的表达式:

modelBuilder.Entity<Person>()
    .Property(p => p.DisplayName)
    .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");

日期或时间值生成

常见的请求是包含一个数据库列,其中包含第一次插入列时的日期/时间 (添加) 上生成的值,或上次更新的时间 (添加或更新) 生成的值。 由于有各种策略来执行此操作,因此 EF Core 提供商通常不会为日期/时间列自动设置值生成-您必须自行配置。

创建时间戳

将日期/时间列配置为具有行的创建时间戳通常是使用适当的 SQL 函数配置默认值的问题。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Created)
        .HasDefaultValueSql("getdate()");
}

影子和索引器配置

影子属性是未在 .NET 实体类中定义但在 EF Core 模型中为该实体类型定义的属性。 这些属性的值和状态纯粹在更改跟踪器中进行维护。 当数据库中的数据不应在映射的实体类型上公开时,阴影属性非常有用。
索引器属性是实体类型属性,由 .NET 实体类中的 索引器 支持。 可以在 .NET 类实例上使用索引器对其进行访问。 它还允许向实体类型添加其他属性,而无需更改 CLR 类。

外键影子属性

影子属性最常用于外键属性,其中两个实体之间的关系由数据库中的外键值表示,但使用实体类型之间的导航属性在实体类型上管理关系。 按照约定,当发现关系但在依赖实体类中找不到外键属性时,EF 会引入一个影子属性。
该属性将被命名 (在依赖实体上导航(指向主体实体)用于命名) 。 如果主体键属性名称包含导航属性的名称,则该名称将只是 。 如果依赖实体上没有导航属性,则会在其位置使用主体类型名称。
例如,下面的代码列表将导致将 BlogId 影子属性引入到 Post 实体:

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    // Since there is no CLR property which holds the foreign
    // key for this relationship, a shadow property is created.
    public Blog Blog { get; set; }
}

配置索引器属性

你可以使用熟知的 API 来配置索引器属性。 调用方法后 IndexerProperty ,可以链接到其他属性的任何配置调用。 在下面的示例中, Blog 已定义索引器,并将用于创建索引器属性。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().IndexerProperty<DateTime>("LastUpdated");
}

如果提供给方法的名称 IndexerProperty 与现有索引器属性的名称匹配,则代码将配置该现有属性。 如果实体类型具有由实体类的属性支持的属性,则会引发异常,因为索引器属性只能通过索引器访问。

实体关系

关系定义

  • 相关实体: 这是包含外键属性的实体。 有时称为关系的 “子级”。
  • 主体实体: 这是包含主/备用键属性的实体。 有时称为关系的 “父项”。
  • 主体密钥: 唯一标识主体实体的属性。 这可能是主键或备用密钥。
  • 外键: 用于存储相关实体的主体键值的依赖实体中的属性。
  • 导航属性:在主体和/或从属实体上定义的属性,该属性引用相关实体。
    • 集合导航属性: 一个导航属性,其中包含对多个相关实体的引用。
    • 引用导航属性: 保存对单个相关实体的引用的导航属性。
    • 反向导航属性: 讨论特定导航属性时,此术语是指关系另一端的导航属性。
  • 自引用关系: 依赖关系和主体实体类型相同的关系。

如下面的一对多关系:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
  • Post 是依赖实体
  • Blog 是主体实体
  • Blog.BlogId 是主体键 (在本例中,它是主密钥而不是备用密钥)
  • Post.BlogId 为外键
  • Post.Blog 是一个引用导航属性
  • Blog.Posts 是集合导航属性
  • Post.Blog (的反向导航属性 Blog.Posts ,反之亦然)

默认情况下,当在某个类型上发现导航属性时,将创建一个关系。 如果当前数据库提供程序无法将其指向的类型映射为标量类型,则该属性被视为导航属性。

完全定义关系

关系最常见的模式是在关系两端定义导航属性,在依赖实体类中定义外键属性。

  • 如果在两个类型之间找到一对导航属性,则这些属性将配置为同一关系的反向导航属性。
  • 如果依赖实体包含名称与其中一种模式相匹配的属性,则该属性将被配置为外键:

    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity name><principal key property name>
    • <principal entity name>Id ```csharp public class Blog { public int BlogId { get; set; } public string Url { get; set; }

      public List Posts { get; set; } }

public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; }

public int BlogId { get; set; }
public Blog Blog { get; set; }

}

<a name="9XyrB"></a>
#### 无外键属性
尽管建议在依赖实体类中定义外键属性,但这并不是必需的。 如果未找到外键属性,则会使用名称引入 阴影外键属性,如果依赖类型上没有导航,则为。
```csharp
//在此示例中,阴影外键是 BlogId 因为预先计算导航名称将是冗余的。
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

其他

对于关系型数据库而言,设置关系会让数据之间的查询速度变慢,在web开发中,最好不要在数据库中显示的设置主外键关系,数据之间的关系要程序来保持,而不是用数据库存储。设计数据库的时候需要心里有关系就好。但是如果要使用的话,可以参考官方文档来进一步学习:
https://docs.microsoft.com/zh-cn/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key

索引

索引是跨多个数据存储区的常见概念。 尽管它们在数据存储中的实现可能会有所不同,但也可用于基于列 (或一组列来进行查找,) 效率更高。

单列索引

主键在数据库中是默认有索引的,称为主键索引,尽量使用主键索引会提高查询效率,但是有时我们也常常用到不是主键的索引,称为单列索引,顾名思义就是将数据表中的某一列设置为索引。

//注解
[Index(nameof(Url))]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
//fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url);
}

复合索引

复合索引在大多数关系型数据库都是存在的,复合索引就是将多个列共同设置成一个索引。

//注解
[Index(nameof(FirstName), nameof(LastName))]
public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
//fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasIndex(p => new { p.FirstName, p.LastName });
}

唯一索引

通常情况下,索引并不是唯一的,但是可以设置索引唯一,设置后当插入数据时,索引重复会报错。

//注解
[Index(nameof(Url), IsUnique = true)]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
//Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url)
        .IsUnique();
}

索引名称

按照约定,在关系数据库中创建的索引命名为 IX 。 对于复合索引, 将成为以下划线分隔的属性名称列表。
可以设置在数据库中创建的索引的名称:

//注解
[Index(nameof(Url), Name = "Index_Url")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
//fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasIndex(b => b.Url)
        .HasDatabaseName("Index_Url");
}

继承的层级关系映射

EF 可以将 .NET 类型层次结构映射到数据库。 这使你可以像平常一样使用基类型和派生类型在代码中编写 .NET 实体,并让 EF 无缝创建适当的数据库架构、发出查询等。类型层次结构的映射方式的实际详细信息与提供程序相关;

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<RssBlog> RssBlogs { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class RssBlog : Blog
{
    public string RssUrl { get; set; }
}

使用的时候注意,层级的基类是否要映射到表结构中。可以使用表配置中的忽略映射基类表或将基类表设置为抽象。但是大多数的时候不推荐层级映射。当两个子类有共同的字段时,这个字段会共享。

值转换

值转换器以和的形式指定 ModelClrType ProviderClrType 。 模型类型是实体类型中的属性的 .NET 类型。 提供程序类型是数据库提供程序理解的 .NET 类型。 例如,若要将枚举作为字符串保存在数据库中,模型类型是枚举的类型,而提供程序类型为 String 。这两种类型可以相同。
使用两个 Func 表达式树来定义转换:一个从 ModelClrType 到 ProviderClrType ,另一个由 ProviderClrType 到 ModelClrType 。 使用表达式树,以便可以将它们编译到数据库访问委托中以便进行有效的转换。 对于复杂转换,表达式树可能包含对转换方法的简单调用。
我们定义类:这里定义了DataTime为string类型。

public class Teacher
{
    [Key]
    public int TId { get; set; }
    [MaxLength(20)]
    public string Name { get; set; }
    public string DataTime { get; set; }
}

配值:这里进行了转换,在数据库中生成的类型为datetime2(7)类型。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //反射获取程序集
    AddAssembly(modelBuilder, "TestClass", "TestClass.Entity");

    modelBuilder.Entity<Teacher>().Property(w => w.DataTime)
        .HasConversion(w => Convert.ToDateTime(w), w => w.ToString("yyyy-MM-dd HH:mm:ss"));
}

使用的时候,存值和取值都是string类型,但是在数据库中存储的就是datetime2(7)类型。

using (MyContext con = new MyContext())
{
    con.Set<Teacher>().Add(new Teacher()
                           {
                               Name = "张三",
                               DataTime = DateTime.Now.ToString()
                           }) ;
    con.SaveChanges();
    Teacher teacher = con.Set<Teacher>().Find(3);
}

当然也可以利用这个特性来将枚举类型转换等等。当数据库与实体的属性类型不一致的时候将数据库的类型与实体类型的进行连接。
提取公共配置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<EquineBeast, string>(
        v => v.ToString(),
        v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));

    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(converter);
}

在efcore中还内置了很多的默认转换:将bool值转换为数据库的数字值

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new BoolToZeroOneConverter<int>();

    modelBuilder
        .Entity<User>()
        .Property(e => e.IsActive)
        .HasConversion(converter);
}

内置转换器的完整列表如下:
转换 bool 属性:

  • BoolToStringConverter -Bool 到字符串(如 “Y” 和 “N”)
  • BoolToTwoValuesConverter -布尔值到任意两个值
  • BoolToZeroOneConverter -布尔值为零和一个

转换字节数组属性:

  • BytesToStringConverter -字节数组到 Base64 编码的字符串

任何只需要类型强制转换的转换

  • CastingConverter -只需要类型强制转换的转换

转换 char 属性:

  • CharToStringConverter -Char 到单字符字符串

转换 DateTimeOffset 属性:

  • DateTimeOffsetToBinaryConverter - DateTimeOffset 到二进制编码的64位值
  • DateTimeOffsetToBytesConverter - DateTimeOffset 到字节数组
  • DateTimeOffsetToStringConverter - DateTimeOffset 到字符串

转换 DateTime 属性:

  • DateTimeToBinaryConverter - DateTime 到64位值(包括 Datetimekind.utc)
  • DateTimeToStringConverter - DateTime 到字符串
  • DateTimeToTicksConverter - DateTime 到刻度

转换枚举属性:

  • EnumToNumberConverter -枚举到基础数字
  • EnumToStringConverter -枚举到字符串

转换 Guid 属性:

  • GuidToBytesConverter - Guid 到字节数组
  • GuidToStringConverter - Guid 到字符串

转换 IPAddress 属性:

  • IPAddressToBytesConverter - IPAddress 到字节数组
  • IPAddressToStringConverter - IPAddress 到字符串

将数值转换 (int、double、decimal 等 ) 属性:

  • NumberToBytesConverter -任何数值到字节数组
  • NumberToStringConverter -任何数值到字符串

转换 PhysicalAddress 属性:

  • PhysicalAddressToBytesConverter - PhysicalAddress 到字节数组
  • PhysicalAddressToStringConverter - PhysicalAddress 到字符串

转换字符串属性:

  • StringToBoolConverter -字符串(例如 “Y” 和 “N”)到 bool
  • StringToBytesConverter -字符串到 UTF8 字节
  • StringToCharConverter -字符串到字符
  • StringToDateTimeConverter -字符串到 DateTime
  • StringToDateTimeOffsetConverter -字符串到 DateTimeOffset
  • StringToEnumConverter -要枚举的字符串
  • StringToGuidConverter -字符串到 Guid
  • StringToNumberConverter -字符串到数值类型
  • StringToTimeSpanConverter -字符串到 TimeSpan
  • StringToUriConverter -字符串到 Uri

转换 TimeSpan 属性:

  • TimeSpanToStringConverter - TimeSpan 到字符串
  • TimeSpanToTicksConverter - TimeSpan 到刻度

转换 Uri 属性:

  • UriToStringConverter - Uri 到字符串

请注意,所有内置的转换器都是无状态的,因此,多个属性可以安全地共享单个实例。

构造函数的实体,注入服务

EF Core 还可以将 “服务” 注入到实体类型的构造函数中。 例如,可以注入以下内容:

  • DbContext -当前上下文实例,也可以类型化为派生的 DbContext 类型
  • ILazyLoader-延迟加载服务-有关更多详细信息,请参阅延迟加载文档
  • Action-延迟加载委托-有关更多详细信息,请参阅延迟加载文档
  • IEntityType -与此实体类型关联的 EF Core 元数据

注入的 DbContext 可用于有选择地访问数据库,以获取相关实体的相关信息,而无需将它们全部加载。 在下面的示例中,这用于在不加载帖子的情况下获取博客中的帖子数:

public class Blog
{
    public Blog()
    {
    }

    private Blog(BloggingContext context)
    {
        Context = context;
    }

    private BloggingContext Context { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; set; }

    public int PostsCount
        => Posts?.Count
           ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId"))
           ?? 0;
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

请注意以下几点:

  • 构造函数是私有的,因为它只是由 EF Core 调用的,另一个公共构造函数用于常规用途。
  • 使用注入的服务的代码 (即,上下文) 防御 null 处理 EF Core 不创建实例的情况。
  • 因为服务存储在读/写属性中,所以当将实体附加到新的上下文实例时,它将被重置。

注意:这种使用会导致实体与efcore高度耦合,请考虑后使用。