9.1充血模型和贫血模型
【摘要】 贫血模型:一个类中只有属性或者成员变量
充血模型:一个类中除了属性和成员变量,还有方法
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
来配置
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);
}
}
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)