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("成员变量名")
来配置
- 有的属性不需要映射到数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public record User { 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类进行配置
1 2 3 4 5 6 7 8 9
| 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中实现值对象
实体类中实现值对象,就是将类中相关的属性进行封装, 比如某公司类
中有经度和维度两个属性,但是这两个属性非常相关,所以将这两个属性进行封装成一个独立的位置坐标类
,则在公司类
中只要使用位置坐标类
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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; } }
|
值对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class RegionConfig : IEntityTypeConfiguration<Region> { public void Configure(EntityTypeBuilder<Region> builder) { builder.OwnsOne(c => c.Area, nb => { nb.Property(e => e.Unit).HasMaxLength(20) .IsUnicode(false).HasConversion<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")));
来实现数据查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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); } }
|