9.1DDD之充血模型和贫血模型
9.1充血模型和贫血模型
贫血模型:一个类中只有属性或者成员变量
充血模型:一个类中除了属性和成员变量,还有方法
EF Core对实体类属性的操作
有些时候,EF Core可能会跳过属性的get,set方法,而是直接去操作存储属性值得成员变量,这是因为EF Core在读写实体类对象属性时,会查找类中是否有与属性名字(忽略大小写)一样的成员变量,如果有则EF Core会直接读写这个成员变量,而不通过get,set属性方法。
如果采用string Name{get;set}这种简写的形式,编译器会自动生成名字为的成员变量来保存属性的值,因此EF Core除了查找与属性名称相同的成员变量还会查找符合规则的成员变量。
EF Core中实现充血模型
充血模型中的实体类相较于贫血模型实体类相比,有以下特性:
-
- 属性是只读的,或者只能在类内部的代码修改(private)
- 实现方法:将set属性定义为private或者init,通过构造函数来初始值
-
- 定义了有参的构造函数
- 解决方法1:实体类中定义无参数构造函数,但要声明为private,EF Core从数据库加载数据到实体类的时候,会调用这个私有的构造方法,然后对各个属性进行赋值
- 解决方法2:实体类中不定义无参构造函数,但是要求构造方法中的参数名字和属性名字必须一致。
-
- 有的成员变量没有定义属性,但是需要在数据库中有相应的列
- 解决方法:在配置实体类的时候,使用
builder.Property("成员变量名")来配置
-
- 有的属性是只读的,即它的值是从数据库中读取出来且不能修改
- 解决方法:在配置实体类的时候,使用
HasField("成员变量名")来配置
-
- 有的属性不需要映射到数据库
- 解决方法:使用
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);}
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
