实体模型


  Fireasy 的实体没有使用 POCO 风格,它们必须实现 IEntity 接口。使用接口的好处有以下几点:

  • 能够使用通过用方法对实体对象进行操作。

  • 实体对象拥有状态,也能跟踪属性的变化。

  • 可以建立其他的实体模型进行替换。


  EntityObject 类是实现了 IEntity 接口的实体类基类。EntityObject 使用类似于依赖属性的模型,所有属性的 get 和 set 均使用 GetValue 和 SetValue 方法,这有点像 LINQ to SQL 和早期 Entity Framework 模型。使用 SetValue 的目的是能够跟踪属性的修改,使得持久化时可以按需更新变动的字段。

  以下是一个典型的实体模型,基于 EntityObject 基类:

public class Products : EntityObject
{
    public readonly static IProperty _ProductId = 
        PropertyUnity.RegisterProperty<Product>(s => s.ProductId,
        new PropertyMapInfo
        {
            ColumnName = "product_id",
            GenerateType = IdentityGenerateType.AutoIncrement,
            IsPrimaryKey = true,
            IsNullable = false
        });
    public readonly static IProperty _ProductName = 
        PropertyUnity.RegisterProperty<Product>(s => s.ProductName,
        new PropertyMapInfo
        {
            Length = 10,
            ColumnName = "product_name"
        });
    public readonly static IProperty _SupplierID = 
        PropertyUnity.RegisterProperty<Product>(s => s.SupplierID,
        new PropertyMapInfo
        {
            ColumnName = "suppler_id"
        });
    public readonly static IProperty _CategoryID = 
        PropertyUnity.RegisterProperty<Product>(s => s.CategoryID,
        new PropertyMapInfo
        {
            ColumnName = "category_id"
        });
    public readonly static IProperty _QuantityPerUnit = 
        PropertyUnity.RegisterProperty<Product>(s => s.QuantityPerUnit,
        new PropertyMapInfo
        {
            ColumnName = "quantity_pre_unit"
        });
    public readonly static IProperty _UnitPrice = 
        PropertyUnity.RegisterProperty<Product>(s => s.UnitPrice,
        new PropertyMapInfo
        {
            ColumnName = "unit_price"
        });
    public readonly static IProperty _UnitsInStock = 
        PropertyUnity.RegisterProperty<Product>(s => s.UnitsInStock,
        new PropertyMapInfo
        {
            ColumnName = "units_in_stock"
        });
    public readonly static IProperty _UnitsOnOrder = 
        PropertyUnity.RegisterProperty<Product>(s => s.UnitsOnOrder,
        new PropertyMapInfo
        {
            ColumnName = "units_on_order"
        });
    public readonly static IProperty _ReorderLevel = 
        PropertyUnity.RegisterProperty<Product>(s => s.ReorderLevel,
        new PropertyMapInfo
        {
            ColumnName = "reorder_level"
        });
    public readonly static IProperty _Discontinued = 
        PropertyUnity.RegisterProperty<Product>(s => s.Discontinued,
        new PropertyMapInfo
        {
            ColumnName = "discontinued",
            DefaultValue = true
        });
    public readonly static IProperty _OrderDetails = 
        PropertyUnity.RegisterSupposedProperty<Product>(s => s.OrderDetails);

    public int ProductId
    {
        get { return (int)GetValue(_ProductId); }
        set { SetValue(_ProductId, value); }
    }
    public string ProductName
    {
        get { return (string)GetValue(_ProductName); }
        set { SetValue(_ProductName, value); }
    }
    public int? SupplierID
    {
        get { return (int?)GetValue(_SupplierID); }
        set { SetValue(_SupplierID, value); }
    }
    public int? CategoryID
    {
        get { return (int?)GetValue(_CategoryID); }
        set { SetValue(_CategoryID, value); }
    }
    public string QuantityPerUnit
    {
        get { return (string)GetValue(_QuantityPerUnit); }
        set { SetValue(_QuantityPerUnit, value); }
    }
    public decimal? UnitPrice
    {
        get { return (decimal?)GetValue(_UnitPrice); }
        set { SetValue(_UnitPrice, value); }
    }
    public short? UnitsInStock
    {
        get { return (short?)GetValue(_UnitsInStock); }
        set { SetValue(_UnitsInStock, value); }
    }
    public short? UnitsOnOrder
    {
        get { return (short?)GetValue(_UnitsOnOrder); }
        set { SetValue(_UnitsOnOrder, value); }
    }
    public short? ReorderLevel
    {
        get { return (short?)GetValue(_ReorderLevel); }
        set { SetValue(_ReorderLevel, value); }
    }
    public bool? Discontinued
    {
        get { return (bool?)GetValue(_Discontinued); }
        set { SetValue(_Discontinued, value); }
    }
    public EntitySet<OrderDetails> OrderDetails
    {
        get { return PropertyValue.GetValue(GetValue(_OrderDetails)) as EntitySet<OrderDetails>; }
        set { SetValue(_OrderDetails, PropertyValue.NewValue(value)); }
    }
}

public class OrderDetails : EntityObject
{
    //省略
}

  GetValue 和 SetValue 方法操作的是一个 PropertyValue 的结构体,它的存在是为了解决装箱/拆箱的性能问题。基元类型和 PropertyValue 构造之间通过显式和隐式转换。

  以上的模型过于繁琐,不利于维护,因此在此基础上提供了 LightEntity<TEntity> 基类,它是在 EntityObject 的基础上通过 AOP 技术进行重构,将 GetValue 和 SetValue 方法注入到属性的前后,因此它看上去比较简洁。如下所示:

public partial class Products : LightEntity<Products>
{
    [PropertyMapping(ColumnName = "product_id", IsPrimaryKey = true, GenerateType = IdentityGenerateType.AutoIncrement, IsNullable = false)]
    public virtual int Id { get; set; }

    [PropertyMapping(ColumnName = "product_name", Length = 40, IsNullable = false)]
    public virtual string ProductName { get; set; }

    [PropertyMapping(ColumnName = "supplier_id", IsNullable = true)]
    public virtual int? SupplierID { get; set; }

    [PropertyMapping(ColumnName = "category_id", IsNullable = true)]
    public virtual int? CategoryID { get; set; }

    [PropertyMapping(ColumnName = "quantity_pre_unit", Length = 20, IsNullable = true)]
    public virtual string QuantityPerUnit { get; set; }

    [PropertyMapping(ColumnName = "unit_price", IsNullable = true)]
    public virtual decimal? UnitPrice { get; set; }

    [PropertyMapping(ColumnName = "units_in_stock", IsNullable = true, DefaultValue = 34)]
    public virtual short? UnitsInStock { get; set; }

    [PropertyMapping(ColumnName = "units_on_order", IsNullable = true)]
    public virtual short? UnitsOnOrder { get; set; }

    [PropertyMapping(ColumnName = "reorder_level", IsNullable = true)]
    public virtual short? ReorderLevel { get; set; }

    [PropertyMapping(ColumnName = "discontinued", IsNullable = false)]
    public virtual bool? Discontinued { get; set; }

    public virtual EntitySet<OrderDetails> OrderDetailses { get; set; }
}

  因为是基于 AOP,所以只有 virtual 修饰的属性,才会被重写。如果你直接 new Products() 得到的对象是达不到我们想要的效果的,它不会记录属性的修改。你需要使用 New 方法来创建对象。如下所示:

[TestMethod]
public void TestNewEntity()
{
    var entity = new Products();
    entity.ProductName = "fireasy";
    Assert.IsFalse(entity.IsModified("ProductName"));

    entity = Products.New();
    entity.ProductName = "fireasy";
    Assert.IsTrue(entity.IsModified("ProductName"));
    
    //类型不一样
    Assert.AreNotEqual(entity.GetType(), typeof(Products));
}

  New 方法实际上是使用 EntityProxyManager 类所构造的代理实体类来创建对象,因此该对象的类型并不是我们定义的类型。


💡 多说两句

  Fireasy 里的子实体属性使用的是 EntitySet<T> 类型。

  使用 代码生成器 生成实体模型。