zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

《ASP.NET Core技术内幕与项目实战》精简集-DDD准备5.3:值对象

2023-03-20 15:33:41 时间

本节内容,部分为补充内容,部分涉及到9.3(P321-326)。主要NuGet包:无

 

一、使用值对象的两种情形及其EFCore映射配置

1、没有标识符的从属实体类

  • 如商店的地理位置,包含经度和纬度,可以定义一个包含Longitude(经度)和Latitude(纬度)两个属性的Geo类型。
  • 如商品的质量,除了double类型的数值外,还隐含着一个质量单位,可以定义一个包含Value(数值)和Unit(单位)两个属性的Weight类型。
  • EFCore中,通过OwnsOne来进行配置

 

2、枚举类型

  • 如员工的等级,除了定义为int类型(如1表示初级,2表示中级,3表示高级)外,可以定义一个枚举类型属性,来表示固定可选值范围。
  • 枚举类型在数据库中默认是以int类型来保存的,但对于直接操作数据库的人员来说,可读性较差。
  • EFCore中,通过HasConversion<string>将枚举类型配置为字符串。

 

 

二、使用值对象的案例

1、Region实体类及相关值对象类型和枚举类

 

//实体类-充血模型================================================================================
public class Region
{
    public long Id { get; init; }  //Id标识符
    public MultiName Name { get; init; } //名称
    public Area Area { get; init; } //面积
    public Geo Location { get; init; } //地理位置
    public RegionLevel Level { get; private set; } //地区级别
    public long? Population { get; private set; } //人口
    
    //私有无参构造函数,EFCore默认使用
    private Region(){}
    //公有有参构造函数,外部创建对象时初始化
    public Region(MultiName name, Area area, Geo location, RegionLevel level)
    {
        Name = name;
        Area = area;
        Location = location;
        Level = level;
    }

    public void ChangeLevel(RegionLevel level)
    {
        this.Level= level;
    }

    public void ChangePopulation(long population)
    {
        this.Population = population;
    }
}


//相关的值对象和枚举类型。值对象类型为不可变类型,推荐使用record==========================
public record MultiName(string Chinese, string? English); //多语言名称
public record Area(double Value, AreaType Unit); //面积
public enum AreaType { SquareKM, Hectare, CnMu}; //面积单位,平方公里、公顷、亩
public enum RegionLevel { Province, City, County, Town}; //地区级别
public record Geo //地理位置
{
    public double Longitude { get; init; }
    public double Latitude { get; init; }
    public Geo(double longitude, double latitude)
    {
        if (longitude<-180 || longitude>180)
        {
            throw new ArgumentException("纬度值无效!");
        }
        if (latitude < -90 || latitude > 90)
        {
            throw new ArgumentException("经度值无效!");
        }
        this.Longitude= longitude;
        this.Latitude= latitude;
    }
}

 

 

 

2、EFCore的DbContext映射配置

public class MyDbContext:DbContext
{
    public DbSet<Region> Regions { get; set; }
    ......
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {     
        modelBuilder.Entity<Region>(b =>
        {
            b.ToTable("T_Regions");            
            //设置值对象Name
            b.OwnsOne(r => r.Name, vo =>
            {
                vo.Property(n => n.English).HasMaxLength(20).IsUnicode(false);
                vo.Property(n => n.Chinese).HasMaxLength(20).IsUnicode(true);
            });
            //设置值对象Area,同时将值对象Area类中Unit属性的枚举值映射为字符串
            b.OwnsOne(r => r.Area, vo =>
            {
                vo.Property(a => a.Unit).HasMaxLength(20).IsUnicode(false).HasConversion<string>();
            });
            //设置值对象Location
            b.OwnsOne(r => r.Location);
            //设置枚举值Level,映射为字符串
            b.Property(r => r.Level).HasMaxLength(20).IsUnicode(false).HasConversion<string>();
        });
    }
}

 

 3、数据库迁移结果:①值对象按属性映射为多个字段,字段名称为“实体类属性名_值对象属性名”;②枚举类映射为字符类型字段

 

 

4、数据库操作及结果:

 

using var ctx = new MyDbContext();

//新增一条数据
//var region1 = new Region(name1,area1,geo1,RegionLevel.City);
//region1.ChangePopulation(20000000);
//ctx.Regions.Add(region1);
//ctx.SaveChanges();
//查询数据
var region = ctx.Regions.FirstOrDefault(x => x.Name.Chinese == "广州");
Console.WriteLine(region); //将Region类修改为record类型,可打印字符串

 

 

 

 

 

特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍