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中实现充血模型
充血模型中的实体类相较于贫血模型实体类相比,有以下特性:
- 属性是只读的,或者只能在类内部的代码修改(private)
- 实现方法:将set属性定义为private或者init,通过构造函数来初始值
- 定义了有参的构造函数
- 解决方法1:实体类中定义无参数构造函数,但要声明为private,EF Core从数据库加载数据到实体类的时候,会调用这个私有的构造方法,然后对各个属性进行赋值
- 解决方法2:实体类中不定义无参构造函数,但是要求构造方法中的参数名字和属性名字必须一致。
- 有的成员变量没有定义属性,但是需要在数据库中有相应的列
- 解决方法:在配置实体类的时候,使用
builder.Property("成员变量名")
来配置
- 有的属性是只读的,即它的值是从数据库中读取出来且不能修改
- 解决方法:在配置实体类的时候,使用
HasField("成员变量名")
来配置
- 有的属性不需要映射到数据库
- 解决方法:使用
Ignore
来配置
publicrecordUser//使用record,自动生成toString方法
{
publicintId { get; init; }//特征一
publicDateTimeCreatedDateTime { get; init; }//特征一
publicstringUserName { get; privateset; }//特征一
publicintCredit { get; privateset; }
privatestring?passwordHash;//特征三
privatestring?remark;
publicstring?Remark//特征四
{
get { returnremark; }
}
publicstring?Tag { get; set; }//特征五
privateUser()//特征二
{
}
publicUser(stringyhm)//特征二
{
this.UserName=yhm;
this.CreatedDateTime=DateTime.Now;
this.Credit=10;
}
publicvoidChangeUserName(stringnewValue)
{
this.UserName=newValue;
}
publicvoidChangePassword(stringnewValue)
{
if (newValue.Length<6)
{
thrownewArgumentException("密码太短");
}
this.passwordHash=HashHelper.Hash(newValue);
}
}
对User类进行配置
internalclassUserConfig : IEntityTypeConfiguration<User>
{
publicvoidConfigure(EntityTypeBuilder<User>builder)
{
builder.Property("passwordHash");//特征三
builder.Property(u=>u.Remark).HasField("remark");//特征四
builder.Ignore(u=>u.Tag);//特征五
}
}
EF Core中实现值对象
实体类中实现值对象,就是将类中相关的属性进行封装, 比如某公司类
中有经度和维度两个属性,但是这两个属性非常相关,所以将这两个属性进行封装成一个独立的位置坐标类
,则在公司类
中只要使用位置坐标类
即可。
recordRegion
{
publiclongId { get; init; }
publicMultilingualStringName { get; init; } //值对象 中文名和英文名
publicAreaArea { get; init; } //值对象
publicRegionLevelLevel { get; privateset; }//值对象
publiclong?Population { get; privateset; }
publicGeoLocation { get; init; }//值对象,位置坐标经纬度
privateRegion() { }
publicRegion(MultilingualStringname, Areaarea, Geolocation,
RegionLevellevel)
{
this.Name=name;
this.Area=area;
this.Location=location;
this.Level=level;
}
publicvoidChangePopulation(longvalue)
{
this.Population=value;
}
publicvoidChangeLevel(RegionLevelvalue)
{
this.Level=value;
}
}
值对象
enumAreaType { SquareKM, Hectare, CnMu }
enumRegionLevel { Province, City, County, Town }
recordArea(doubleValue, AreaTypeUnit);
recordMultilingualString(stringChinese, string?English);
recordGeo//经纬度
{
publicdoubleLongitude { get; init; }
publicdoubleLatitude { get; init; }
publicGeo(doublelongitude, doublelatitude)
{
if (longitude<-180||longitude>180)
{
thrownewArgumentException("longitude invalid");
}
if (latitude<-90||latitude>90)
{
thrownewArgumentException("longitude invalid");
}
this.Longitude=longitude;
this.Latitude=latitude;
}
}
对Region进行配置
classRegionConfig : IEntityTypeConfiguration<Region>
{
publicvoidConfigure(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")));
来实现数据查询。
usingSystem.Linq.Expressions;
usingstaticSystem.Linq.Expressions.Expression;
classExpressionHelper
{
//第一个参数为待比较属性的表达式,第二个参数为待比较的值对象
publicstaticExpression<Func<TItem, bool>>MakeEqual<TItem, TProp>
(Expression<Func<TItem, TProp>>propAccessor, TProp?other)
whereTItem : classwhereTProp : class
{
vare1=propAccessor.Parameters.Single();
BinaryExpression?conditionalExpr=null;
foreach (varpropintypeof(TProp).GetProperties())//遍历对象的每个属性
{
BinaryExpressionequalExpr;
object?otherValue=null;
if (other!=null)
{
otherValue=prop.GetValue(other);//通过反射获取待比较值对象中对应属性的表达式
}
TypepropType=prop.PropertyType;
varleftExpr=MakeMemberAccess(propAccessor.Body, prop);//获取待比较属性的表达式
ExpressionrightExpr=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)
{
thrownewArgumentException("There should be at least one property.");
}
returnLambda<Func<TItem, bool>>(conditionalExpr, e1);
}
}