《ASP.NET Core技术内幕与项目实战》精简集-DDD准备5.2:贫血模型和充血模型()

本节内容,部分为补充内容,部分涉及到99.3(P311-320)。主要NuGet包:无

领域建模有两种方式,一是贫血模式,二是充血模型。EFCore对充血模型,已经有了非常好的支持,我们应该通过充血模型的方式来设计实体,将有关个体的业务逻辑封装在实体内。

一、贫血模型:又叫POCO类,类中只有属性或成员变量,没有方法。

1、贫血模型实体类UserAnemic

public class UserAnemic
{    public string? UserName { get; set; }
    public string? Password { get; set; }
    public int Credit { get; set; }
}

2、实现以下几个业务逻辑:①UserAnemic对象必须有名称;②初始积分为10;③判断密码是否正常;④如果登陆成功,积分加5分;如果登陆失败,积分减3分,但如果不够减,则提示积分不足。这些业务逻辑,都属于个体行为,但都暴露在类的外面,且约束力不强,如对象的姓名,很可能会忘记赋值,初始积分,也可能出错。

UserAnemic u1 = new UserAnemic();
u1.UserName = "Test";
u1.Credit = 10;
u1.Password ="123456";

string pwd = Console.ReadLine();
if (pwd == u1.Password)
{
    u1.Credit += 5;
    Console.WriteLine("登陆成功");
}
else
{
    if (u1.Credit < 3)
    {
        Console.WriteLine("积分不足,无法扣减");
    }
    else
    {
        u1.Credit -= 3;
        Console.WriteLine("登陆失败");
    }
    Console.WriteLine("登陆失败");
}

二、充血模型:类中即有属性、成员变量,也有方法。属于个体的行为,封装在类中,体现“单一职责原则”。当应用中其它地方需要使用这个类时,让使用者无须关心类的内部实现,直接复用类的业务逻辑即可。同时,也体现了业务驱动设计,如下例中的设置密码、检查密码、增加积分、减少积分,业务、产品、开发等各环节沟通的语言均是这些业务逻辑,而不是具体的技术细节。

public class UserRich
{
    //姓名只可以在对象初始化时设置
    public string UserName { get; init; }
    //积分只可以在类的内部修改
    public int Credit { get; private set; }
    //密码外部不可以直接修改
    private string? password;
    //对象初始化,姓名必填,积分初始化为10分
    public UserRich(string name)
    {
        this.UserName = name;
        this.Credit = 10;
    }
    //设置密码逻辑
    public void SetPassword(string password)
    {
        if (password.Length<6)
        {
            throw new ArgumentException("密码太短了!");
        }
        this.password = password;
    }
    //检查密码逻辑
    public bool CheckPassword(string password)
    {
        return this.password == password;
    }
    //减少积分逻辑
    public void DeductCredit(int credit)
    {
        if (credit<=0)
        {
            throw new ArgumentException("额度不能为负值");
        }
        this.Credit -= credit;
    }
    //增加积分逻辑
    public void AddCredit(int credit)
    {
        this.Credit += credit;
    }
}

2、实现业务逻辑

UserRich u1 = new UserRich("functionMC");
u1.SetPassword("123456");

string pwd = Console.ReadLine();
if (u1.CheckPassword(pwd))
{
    u1.AddCredit(5);
    Console.WriteLine("登陆成功");
}
else
{
    u1.DeductCredit(3);
    Console.WriteLine("登陆失败");
}

三、EFCore对充血模型的支持:EFCore对充血模型的支持非常好,下例中,列举了EFCore对充血模型的七项支持。

1、充血模型案例

//充血模型User实体类,User.cs
public class User
{
    public int Id { get; init; } //EFCore支持①只读或只能在初始化时赋值,Id属性由数据库自动赋值
    public DateTime CreatedDateTime { get; init; } //EFCore支持①只读或只能在初始化时赋值
    public string UserName { get; private set; } //EFCore支持②只能在类内部修改
    public int Credit { get; private set; } //EFCore支持②只能在类内部修改

    private string? password; //EFCore支持③私有成员,不能被外部访问,没有对应属性,但需要映射到数据库,在DbContext中设置

    private string? remark;
    public string? Remark { get; } //EFCore支持④只读属性,只能从数据库中读出值,但不能修改属性值

    public string? Tag { get; set; } //EFCore支持⑤不需要映射到数据库,在DbContext中设置

    private User() //EFCore支持⑥私有无参构造函数,一方面外部无法调用、防止空对象;另一方面,EFCore默认调用,将各个属性与数据库映射
    {

    }

    public User(string name) //EFCore支持⑦公共有参构造函数,外部调用这个构造函数进行对象的初始化
    {
        this.UserName = name;
        this.CreatedDateTime = DateTime.Now;
        this.Credit = 10;
    }

    //EFCore支持②只能在类内部修改,外部调用方法修改姓名
    public void ChangeUserName(string newName) 
    {
        this.UserName = newName;
    }

    //EFCore支持③私有成员,不能被外部访问,没有对应属性,但需要映射到数据库,在DbContext中设置
    public void ChangePassword(string newPassword) 
    {
        if (newPassword.Length<6)
        {
            throw new ArgumentException("密码长度不能低于6位");
        }
        this.password = newPassword;
    }
}



//DbContext设置MyDbContext.cs
public class MyDbContext:DbContext
{
    public DbSet<User> Users { get; set; }
    ......
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(b =>
        {
            b.ToTable("T_Users");
            b.Property("password"); //EFCore支持③私有成员映射到数据库
            b.Property(u => u.Remark).HasField("remark"); //EFCore支持④只读属性映射
            b.Ignore(u=>u.Tag); //EFCore支持⑤不映射到数据库
        });
    }
}

2、测试代码和结果

using var ctx = new MyDbContext();
//User u1 = new User("functionMC");
//u1.Tag = "TestTag";
//u1.ChangePassword("123456");
//ctx.Users.Add(u1);
//ctx.SaveChanges();

var u1 = ctx.Users.FirstOrDefault();
Console.WriteLine(u1); //如要直接打印对象的字符串,将User类改为record。

打印结果:
============================================================================================================
User { Id = 1, CreatedDateTime = 2022/11/25 16:52:14, UserName = functionMC, Credit = 10, Remark = , Tag =  }

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

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

————————

本节内容,部分为补充内容,部分涉及到99.3(P311-320)。主要NuGet包:无

领域建模有两种方式,一是贫血模式,二是充血模型。EFCore对充血模型,已经有了非常好的支持,我们应该通过充血模型的方式来设计实体,将有关个体的业务逻辑封装在实体内。

一、贫血模型:又叫POCO类,类中只有属性或成员变量,没有方法。

1、贫血模型实体类UserAnemic

public class UserAnemic
{    public string? UserName { get; set; }
    public string? Password { get; set; }
    public int Credit { get; set; }
}

2、实现以下几个业务逻辑:①UserAnemic对象必须有名称;②初始积分为10;③判断密码是否正常;④如果登陆成功,积分加5分;如果登陆失败,积分减3分,但如果不够减,则提示积分不足。这些业务逻辑,都属于个体行为,但都暴露在类的外面,且约束力不强,如对象的姓名,很可能会忘记赋值,初始积分,也可能出错。

UserAnemic u1 = new UserAnemic();
u1.UserName = "Test";
u1.Credit = 10;
u1.Password ="123456";

string pwd = Console.ReadLine();
if (pwd == u1.Password)
{
    u1.Credit += 5;
    Console.WriteLine("登陆成功");
}
else
{
    if (u1.Credit < 3)
    {
        Console.WriteLine("积分不足,无法扣减");
    }
    else
    {
        u1.Credit -= 3;
        Console.WriteLine("登陆失败");
    }
    Console.WriteLine("登陆失败");
}

二、充血模型:类中即有属性、成员变量,也有方法。属于个体的行为,封装在类中,体现“单一职责原则”。当应用中其它地方需要使用这个类时,让使用者无须关心类的内部实现,直接复用类的业务逻辑即可。同时,也体现了业务驱动设计,如下例中的设置密码、检查密码、增加积分、减少积分,业务、产品、开发等各环节沟通的语言均是这些业务逻辑,而不是具体的技术细节。

public class UserRich
{
    //姓名只可以在对象初始化时设置
    public string UserName { get; init; }
    //积分只可以在类的内部修改
    public int Credit { get; private set; }
    //密码外部不可以直接修改
    private string? password;
    //对象初始化,姓名必填,积分初始化为10分
    public UserRich(string name)
    {
        this.UserName = name;
        this.Credit = 10;
    }
    //设置密码逻辑
    public void SetPassword(string password)
    {
        if (password.Length<6)
        {
            throw new ArgumentException("密码太短了!");
        }
        this.password = password;
    }
    //检查密码逻辑
    public bool CheckPassword(string password)
    {
        return this.password == password;
    }
    //减少积分逻辑
    public void DeductCredit(int credit)
    {
        if (credit<=0)
        {
            throw new ArgumentException("额度不能为负值");
        }
        this.Credit -= credit;
    }
    //增加积分逻辑
    public void AddCredit(int credit)
    {
        this.Credit += credit;
    }
}

2、实现业务逻辑

UserRich u1 = new UserRich("functionMC");
u1.SetPassword("123456");

string pwd = Console.ReadLine();
if (u1.CheckPassword(pwd))
{
    u1.AddCredit(5);
    Console.WriteLine("登陆成功");
}
else
{
    u1.DeductCredit(3);
    Console.WriteLine("登陆失败");
}

三、EFCore对充血模型的支持:EFCore对充血模型的支持非常好,下例中,列举了EFCore对充血模型的七项支持。

1、充血模型案例

//充血模型User实体类,User.cs
public class User
{
    public int Id { get; init; } //EFCore支持①只读或只能在初始化时赋值,Id属性由数据库自动赋值
    public DateTime CreatedDateTime { get; init; } //EFCore支持①只读或只能在初始化时赋值
    public string UserName { get; private set; } //EFCore支持②只能在类内部修改
    public int Credit { get; private set; } //EFCore支持②只能在类内部修改

    private string? password; //EFCore支持③私有成员,不能被外部访问,没有对应属性,但需要映射到数据库,在DbContext中设置

    private string? remark;
    public string? Remark { get; } //EFCore支持④只读属性,只能从数据库中读出值,但不能修改属性值

    public string? Tag { get; set; } //EFCore支持⑤不需要映射到数据库,在DbContext中设置

    private User() //EFCore支持⑥私有无参构造函数,一方面外部无法调用、防止空对象;另一方面,EFCore默认调用,将各个属性与数据库映射
    {

    }

    public User(string name) //EFCore支持⑦公共有参构造函数,外部调用这个构造函数进行对象的初始化
    {
        this.UserName = name;
        this.CreatedDateTime = DateTime.Now;
        this.Credit = 10;
    }

    //EFCore支持②只能在类内部修改,外部调用方法修改姓名
    public void ChangeUserName(string newName) 
    {
        this.UserName = newName;
    }

    //EFCore支持③私有成员,不能被外部访问,没有对应属性,但需要映射到数据库,在DbContext中设置
    public void ChangePassword(string newPassword) 
    {
        if (newPassword.Length<6)
        {
            throw new ArgumentException("密码长度不能低于6位");
        }
        this.password = newPassword;
    }
}



//DbContext设置MyDbContext.cs
public class MyDbContext:DbContext
{
    public DbSet<User> Users { get; set; }
    ......
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(b =>
        {
            b.ToTable("T_Users");
            b.Property("password"); //EFCore支持③私有成员映射到数据库
            b.Property(u => u.Remark).HasField("remark"); //EFCore支持④只读属性映射
            b.Ignore(u=>u.Tag); //EFCore支持⑤不映射到数据库
        });
    }
}

2、测试代码和结果

using var ctx = new MyDbContext();
//User u1 = new User("functionMC");
//u1.Tag = "TestTag";
//u1.ChangePassword("123456");
//ctx.Users.Add(u1);
//ctx.SaveChanges();

var u1 = ctx.Users.FirstOrDefault();
Console.WriteLine(u1); //如要直接打印对象的字符串,将User类改为record。

打印结果:
============================================================================================================
User { Id = 1, CreatedDateTime = 2022/11/25 16:52:14, UserName = functionMC, Credit = 10, Remark = , Tag =  }

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

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