重庆熊猫 Loading

Entity Framework教程-模型(Models)

更新记录
转载请注明出处:
2022年10月16日 发布。
2022年10月10日 从笔记迁移到博客。

模型基础

实体对象模型说明(Entity Data Model)

EF使用实体对象模型进行增删改查(CRUD,Create, Read, Update, Delete) 底层的数据库

EF实体模型主要包含三大部分:
概念模型(conceptual model)
存储模型(storage model)
概念模型和存储模型间的映射(mapping between the conceptual and storage models)

EF中.NET类型与关系型数据库的对应关系

EF让.NET Core应用程序中的对象能够与关系型数据库中的数据进行映射的框架

类 对应 表

类的属性或字段 对应 表中的列

.NET集合中的元素 对应 表中的行

对其他类的引用 对应 外键

data transfer objects(数据传输对象,DTO)

创建自定义业务实体类用于程序中的各层之间或在单独的系统之间传输数据

通常称为数据传输对象(DTO),DTO不包含任何业务逻辑

数据传输对象是模型的一部分

模型约束

说明

EF中使用:默认约定(conventions)、数据注解(annotation attributes)、Fluent API

来实现模型的约束,从而实现与具体数据的关联

EF Core 默认约定(conventions)

表与字段:

数据库中的表名 默认情况下是DbContext 类成员DbSet属性名

数据库中的表列名 默认情况下是模型类的成员名

模型类中名为Id或<类名>Id的属性 将会被推断为数据库表的主键字段

并且如果模型类中的属性类型是int或Guid类型,将会被推断为IDENTITY修饰

数据类型:

模型中的string类型 默认在数据库中对应 nvarchar(max)类型

模型中的int类型 默认在数据中对应 int类型

EF Core 数据注解(annotation attributes)

数据注解 将会覆盖 默认约定

一般在数据约定不能够明确的表示关联情况下,使用数据注解

常用数据注解:

[Required]      //必须填写,不可以为NULL
[StringLength(40)]  //字符串最大长度为40
[Column(TypeName = "money")]  //指定数据库中映射为money类型
[Column(TypeName = "ntext")]   //指定数据库中映射为ntext类型

EF Core Fluent API

在OnModelCreating方法中定义 模型 和 底层数据库 的映射关系

modelBuilder.Entity<Product>()
                .Property(product => product.ProductName)
                .IsRequired()
                .HasMaxLength(40);

定义模型

public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public byte[] Photo { get; set; }
    public decimal Height { get; set; }
    public float Weight { get; set; }
    public int GradeId { get; set; }
    public Grade Grade { get; set; }
}

或者

public class Grade
{
    public int Id { get; set; }
    public string GradeName { get; set; }
    public string Section { get; set; }
    public IList<Student> Students { get; set; }
}

模型映射到数据库

image

Nullable Column

EF Core creates null columns for all reference data type and nullable primitive type properties e.g. string, Nullable, decimal?.

NotNull Column

EF Core creates NotNull columns in the database for all primary key properties, and primitive type properties e.g. int, float, decimal, DateTime etc..

Primary Key

EF Core will create the primary key column for the property named Id or Id (case insensitive). For example, EF Core will create a column as PrimaryKey in the Students table if the Student class includes a property named id, ID, iD, Id, studentid, StudentId, STUDENTID, or sTUdentID.

Index

EF Core creates a clustered index on Primarykey columns and a non-clustered index on ForeignKey columns, by default.

模型状态

使用模型对象的EntityState可以获得模型的状态

对模型进行增删改操作时,模型的状态会发生变化

image

当调用DbContext.SaveChanges()方法时,模型的状态会被给更新

模型的状态使用Microsoft.EntityFrameworkCore.EntityState类型表示

支持以下几种状态:

Added(数据已添加到模型)

Modified(数据被修改)

Deleted(数据被删除)

Unchanged(没有任何修改)

Detached(分离的模型)

Unchanged State

all the entities retrieved using direct SQL query or LINQ-to-Entities queries will have the Unchanged state

public static void Main()
{
    using (var context = new SchoolContext())
    {
        // retrieve entity 
        var student = context.Students.First();
        DisplayStates(context.ChangeTracker.Entries());
    }
}

private static void DisplayStates(IEnumerable<EntityEntry> entries)
{
    foreach (var entry in entries)
    {
        Console.WriteLine($"Entity: {entry.Entity.GetType().Name},
                             State: {entry.State.ToString()} ");
    }
}

Added State

All the new entities without key property value, added in the DbContext using the Add() or Update() method will be marked as Added

using (var context = new SchoolContext())
{              
    context.Add(new Student() { FirstName = "Bill", LastName = "Gates" });
    
    DisplayStates(context.ChangeTracker.Entries());
}

Modified State

If the value of any property of an entity is changed in the scope of the DbContext, then it will be marked as Modified state

using (var context = new SchoolContext())
{
    var student = context.Students.First();
    student.LastName = "LastName changed";
              
    DisplayStates(context.ChangeTracker.Entries());
}

Deleted State

If any entity is removed from the DbContext using the DbContext.Remove or DbSet.Remove method, then it will be marked as Deleted

using (var context = new SchoolContext())
{
    var student = context.Students.First();
    context.Students.Remove(student);
    
    DisplayStates(context.ChangeTracker.Entries());
}

Detached State

All the entities which were created or retrieved out of the scope of the current DbContext instance, will have the Detached state. They are also called disconnected entities and are not being tracked by an existing DbContext instance

var disconnectedEntity = new Student() {StudentId = 1, Name = "Bill"};
using (var context = new SchoolContext())
{
    Console.Write(context.Entry(disconnectedEntity).State);
}

模型数据生成(Data Seeding)

可以使用Fluent API生成初始化的测试数据

在OnModelCreating中进行创建

modelBuilder.Entity<Product>()
            .HasData(new Product
                {
                    ProductID = 1,
                    ProductName = "Chai",
                    UnitPrice = 8.99M
                });

模型投影属性(Shadow Property)

投影属性说明

注意:目前投影属性仅支持EF Core,不支持EF 6.x

投影属性并不直接定义在实体的类型中

需要在上下文对象中的OnModelCreating()方法中定义投影属性

投影属性同样会在数据库的表中生成列

image

投影属性的使用场景(When to use shadow properties)

不想在模型上显示某些属性

外键导航属性,所有的外键导航属性默认都是投影属性

投影属性的使用

定义实体的投影属性(Defining Shadow Property)

先定义一个实体进行测试使用:

public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
    public DateTime? DateOfBirth { get; set; }
    public decimal Height { get; set; }
    public float Weight { get; set; }
}

在OnModelCreating中定义投影属性

public class SchoolContext : DbContext
{
    public SchoolContext() : base()
    {

    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        //定义投影属性CreateDate
        modelBuilder.Entity<Student>().Property<DateTime>("CreatedDate");
        //定义投影属性UpdateDate
        modelBuilder.Entity<Student>().Property<DateTime>("UpdatedDate");
    }

    public DbSet<Student> Students { get; set; }
}

当使用迁移命令后,就可以看到投影属性生成在数据库中

image

读取/设置实体的投影属性的值(Access Shadow Property)

using (var context = new SchoolContext())
{
    var std = new Student(){ StudentName = "Bill" };
    
    //设置某个实体对象的投影属性
    context.Entry(std).Property("CreatedDate").CurrentValue = DateTime.Now;

    //获得某个实体对象的投影属性
    var createdDate = context.Entry(std).Property("CreatedDate").CurrentValue; 
}

自动填充投影属性的值

可以重写上下文对象的SaveChanges()方法来达到自动填充实体的投影属性值的目的

public override int SaveChanges()
{
    //筛选出被修改 或者 新添加的实体对象
    var entries = ChangeTracker
        .Entries()
        .Where(e =>
                e.State == EntityState.Added
                || e.State == EntityState.Modified);

    //遍历实体对象,为其投影属性赋值
    foreach (var entityEntry in entries)
    {
        entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;

        if (entityEntry.State == EntityState.Added)
        {
            entityEntry.Property("CreatedDate").CurrentValue = DateTime.Now;
        }
    }

    return base.SaveChanges();
}

在使用模型对象的使用直接调用SaveChanges()方法即可

using (var context = new SchoolContext())
{
    var std = new Student(){ StudentName = "Bill" };
    context.Add(std);
    context.SaveChanges();
}

为所有的模型设置投影属性(Configuring Shadow Properties on All Entities)

可以一次性为所有模型实体配置投影属性,不用手动再去设置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var allEntities = modelBuilder.Model.GetEntityTypes();

    foreach (var entity in allEntities)
    {
        entity.AddProperty("CreatedDate",typeof(DateTime));
        entity.AddProperty("UpdatedDate",typeof(DateTime));
    }
}

获得投影属性有关的信息

using (var context = new SchoolContext())
{
    var std = new Student(){StudentName = "Bill"  };

    var properties = context.Entry<Student>(std).Properties;

    foreach (var property in properties)
    {
        if (property.Metadata.IsShadowProperty)
        {
            Console.WriteLine(property.Metadata.Name);
        }
    }
}

还可以在ChangeTracker中获得对应模型实体的投影属性信息

using (var context = new SchoolContext())
{
    var properties = context.ChangeTracker.Entries<Student>().FirstOrDefault().Properties;
    foreach (var property in properties)
    {
        if (property.Metadata.IsShadowProperty)
        {
            Console.WriteLine(property.Metadata.Name);
        }
    }
}

在EF创建模型到数据库时同时设置初始数据

定义初始化数据操作类型

只需要继承IEntityTypeConfiguration接口,然后定义Configure方法即可

每次只能定义一个Model的操作

如果要对多个Model定义初始数据,创建多个数据操作类型即可

public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
{
    public void Configure(EntityTypeBuilder<Employee> builder)
    {
        builder.HasData
        (
        new Employee
        {
            Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"),
            Name = "Sam Raiden",
            Age = 26,
            Position = "Software developer",
            CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870")
        },
        new Employee
        {
            Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"),
            Name = "Jana McLeaf",
            Age = 30,
            Position = "Software developer",
            CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870")
        },
        new Employee
        {
            Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"),
            Name = "Kane Miller",
            Age = 35,
            Position = "Administrator",
            CompanyId = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3")
        }
        );
    }
}

在数据库上下文文件中的OnModelCreating方法中

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //应用初始化操作1
    modelBuilder.ApplyConfiguration(new CompanyConfiguration());
    //应用初始化操作2
    modelBuilder.ApplyConfiguration(new EmployeeConfiguration());
    //...其他操作
}
posted @ 2022-10-16 08:51  重庆熊猫  阅读(237)  评论(0编辑  收藏  举报