9.1DDD之充血模型和贫血模型

9.1充血模型和贫血模型

贫血模型:一个类中只有属性或者成员变量

充血模型:一个类中除了属性和成员变量,还有方法

EF Core对实体类属性的操作

有些时候,EF Core可能会跳过属性的get,set方法,而是直接去操作存储属性值得成员变量,这是因为EF Core在读写实体类对象属性时,会查找类中是否有与属性名字(忽略大小写)一样的成员变量,如果有则EF Core会直接读写这个成员变量,而不通过get,set属性方法。

如果采用string Name{get;set}这种简写的形式,编译器会自动生成名字为<Name>k_BackingField的成员变量来保存属性的值,因此EF Core除了查找与属性名称相同的成员变量还会查找符合<Name>k_BackingField规则的成员变量。

EF Core中实现充血模型

充血模型中的实体类相较于贫血模型实体类相比,有以下特性:

    1. 属性是只读的,或者只能在类内部的代码修改(private)
    • 实现方法:将set属性定义为private或者init,通过构造函数来初始值
    1. 定义了有参的构造函数
    • 解决方法1:实体类中定义无参数构造函数,但要声明为private,EF Core从数据库加载数据到实体类的时候,会调用这个私有的构造方法,然后对各个属性进行赋值
    • 解决方法2:实体类中不定义无参构造函数,但是要求构造方法中的参数名字和属性名字必须一致。
    1. 有的成员变量没有定义属性,但是需要在数据库中有相应的列
    • 解决方法:在配置实体类的时候,使用builder.Property("成员变量名")来配置
    1. 有的属性是只读的,即它的值是从数据库中读取出来且不能修改
    • 解决方法:在配置实体类的时候,使用HasField("成员变量名")来配置
    1. 有的属性不需要映射到数据库
    • 解决方法:使用Ignore来配置
public record User //使用record,自动生成toString方法
{
	public int Id { get; init; }//特征一
	public DateTime CreatedDateTime { get; init; }//特征一
	public string UserName { get; private set; }//特征一
	public int Credit { get; private set; }
	private string? passwordHash;//特征三
	private string? remark;
	public string? Remark //特征四
	{
		get { return remark; }
	}
	public string? Tag { get; set; }//特征五
	private User()//特征二
	{
	}
	public User(string yhm)//特征二
	{
		this.UserName = yhm;
		this.CreatedDateTime = DateTime.Now;
		this.Credit = 10;
	}
	public void ChangeUserName(string newValue)
	{
		this.UserName = newValue;
	}
	public void ChangePassword(string newValue)
	{
		if (newValue.Length < 6)
		{
			throw new ArgumentException("密码太短");
		}
		this.passwordHash = HashHelper.Hash(newValue);
	}
}

对User类进行配置

internal class UserConfig : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.Property("passwordHash");//特征三
        builder.Property(u => u.Remark).HasField("remark");//特征四
        builder.Ignore(u => u.Tag);//特征五
    }
}

EF Core中实现值对象

实体类中实现值对象,就是将类中相关的属性进行封装, 比如某公司类中有经度和维度两个属性,但是这两个属性非常相关,所以将这两个属性进行封装成一个独立的位置坐标类,则在公司类中只要使用位置坐标类即可。

record Region
{
	public long Id { get; init; }
	public MultilingualString Name { get; init; } //值对象 中文名和英文名
	public Area Area { get; init; } //值对象
	public RegionLevel Level { get; private set; }//值对象
	public long? Population { get; private set; }
	public Geo Location { get; init; }//值对象,位置坐标经纬度
	private Region() { }
	public Region(MultilingualString name, Area area, Geo location,
		RegionLevel level)
	{
		this.Name = name;
		this.Area = area;
		this.Location = location;
		this.Level = level;
	}
	public void ChangePopulation(long value)
	{
		this.Population = value;
	}
	public void ChangeLevel(RegionLevel value)
	{
		this.Level = value;
	}
}

值对象

enum AreaType { SquareKM, Hectare, CnMu }
enum RegionLevel { Province, City, County, Town }

record Area(double Value, AreaType Unit);
record MultilingualString(string Chinese, string? English);
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("longitude invalid");
        }
        if (latitude < -90 || latitude > 90)
        {
            throw new ArgumentException("longitude invalid");
        }
        this.Longitude = longitude;
        this.Latitude = latitude;
    }
}

对Region进行配置

class RegionConfig : IEntityTypeConfiguration<Region>
{
    public void Configure(EntityTypeBuilder<Region> builder)
    {
        builder.OwnsOne(c => c.Area, nb => {//设置Area属性,
            nb.Property(e => e.Unit).HasMaxLength(20)//Area中Unit属性最大长度20
            .IsUnicode(false).HasConversion<string>();//Area中Unit属性(枚举)在数据库中按照string存储
        });											//但操作实体类的时候仍然是枚举
        builder.OwnsOne(c => c.Location);
        builder.Property(c => c.Level).HasMaxLength(20)
            .IsUnicode(false).HasConversion<string>();
        builder.OwnsOne(c => c.Name, nb => {
            nb.Property(e => e.English).HasMaxLength(20).IsUnicode(false);
            nb.Property(e => e.Chinese).HasMaxLength(20).IsUnicode(true);
        });
    }
}

简化值对象的比较

在对含有值对象的实体进行筛选时,值对象的属性不能直接进行相等比较。比如不可以ctx.Cities.Where(c=>c.Name == new MulitilingualSting("北京")),这是错误的。我们需要把值对象的各个属性都进行比较,ctx.Cities.Where(c=>c.Name.Chinese == "北京"&& c.Name.English=="Beijing")如果属性值比较多的话就很麻烦,可以通过构建表达式树来生成一个进行相等比较的表达式,可以直接使用var cities = ctx.Cities.Where(ExpressionHelper.MakeEqual((Region c) => c.Name, new MultilingualString("北京", "BeiJing")));来实现数据查询。

using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;

class ExpressionHelper
{
    //第一个参数为待比较属性的表达式,第二个参数为待比较的值对象
	public static Expression<Func<TItem, bool>> MakeEqual<TItem, TProp>
(Expression<Func<TItem, TProp>> propAccessor, TProp? other)
	where TItem : class where TProp : class
	{
		var e1 = propAccessor.Parameters.Single();
		BinaryExpression? conditionalExpr = null;
		foreach (var prop in typeof(TProp).GetProperties())//遍历对象的每个属性
		{
			BinaryExpression equalExpr;
			object? otherValue = null;
			if (other != null)
			{
				otherValue = prop.GetValue(other);//通过反射获取待比较值对象中对应属性的表达式
			}
			Type propType = prop.PropertyType;
			var leftExpr = MakeMemberAccess(propAccessor.Body, prop);//获取待比较属性的表达式
			Expression rightExpr = Convert(Constant(otherValue), propType);//获取对应属性值的常量表达式
			if (propType.IsPrimitive)//基本类型和对象的比较方式不同
			{
				equalExpr = Equal(leftExpr, rightExpr);
			}
			else
			{
				equalExpr = MakeBinary(ExpressionType.Equal,
					leftExpr, rightExpr, false,
					prop.PropertyType.GetMethod("op_Equality")
				);
			}
			if (conditionalExpr == null)
			{
				conditionalExpr = equalExpr;
			}
			else
			{
				conditionalExpr = AndAlso(conditionalExpr, equalExpr);
			}
		}
		if (conditionalExpr == null)
		{
			throw new ArgumentException("There should be at least one property.");
		}
		return Lambda<Func<TItem, bool>>(conditionalExpr, e1);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步、步、为营

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值