• 9.1充血模型和贫血模型


    9.1充血模型和贫血模型

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

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

    EF Core对实体类属性的操作

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

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

    对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);//特征五
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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;
    	}
    }
    
    • 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

    值对象

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    对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);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    简化值对象的比较

    在对含有值对象的实体进行筛选时,值对象的属性不能直接进行相等比较。比如不可以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);
    	}
    }
    
    • 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
  • 相关阅读:
    JVM常用的一些参数
    Spring中@Autowired注解的工作原理
    合工大-人工智能原理实验报告
    Javascript知识【JS方法和事件&正则&JS注册案例】
    蜂鸣器电路设计中选用注意事项--【电路设计】
    JavaScript——创建对象的三种方法
    python---字典
    [附源码]计算机毕业设计JAVA校园网学生成绩查询系统
    实时 Path Tracing 实现
    Python每日一练——第44天:大厂真题练习
  • 原文地址:https://blog.csdn.net/weixin_44064908/article/details/126657715