实体模型
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>
类型。
使用 代码生成器 生成实体模型。