你的位置:首页 > ASP.net教程

[ASP.net教程]使用AOP改进Fireasy实体模型


      目前Fireasy中的实体模型虽然用CodeBuilder生成,但是还是比较庞大,手工维护起来比较吃力,因此一直想使用比较简单的一种模型,比如属性只有 get 和 set,最多加个ColumnAttribute之类的来做映射。但这样做的话,属性修改这一特性就无法保持了,即更新实体时不知道哪些属性有改动过。

      我们先看一下目前生成的实体类代码:

  /// <summary>  /// 部门 实体类。  /// </summary>  [Serializable]  [EntityMapping("TB_DEPT", Description = "部门")]  public partial class Dept : EntityObject  {    #region Static Property Definition        /// <summary>    /// ID的依赖属性。    /// </summary>    public static readonly IProperty EpId = PropertyUnity.RegisterProperty<Dept>(s => s.Id, new PropertyMapInfo { IsPrimaryKey = true, IsNullable = false, Length = 36, Description = "ID", FieldName = "ID" });    /// <summary>    /// 编码的依赖属性。    /// </summary>    public static readonly IProperty EpNo = PropertyUnity.RegisterProperty<Dept>(s => s.No, new PropertyMapInfo { Length = 50, Description = "编码", FieldName = "NO" });    /// <summary>    /// 名称的依赖属性。    /// </summary>    public static readonly IProperty EpName = PropertyUnity.RegisterProperty<Dept>(s => s.Name, new PropertyMapInfo { Length = 50, Description = "名称", FieldName = "NAME" });    /// <summary>    /// 排序的依赖属性。    /// </summary>    public static readonly IProperty EpOrderNo = PropertyUnity.RegisterProperty<Dept>(s => s.OrderNo, new PropertyMapInfo { Description = "排序", FieldName = "ORDER_NO" });    #endregion    #region Properties        /// <summary>    /// 获取或设置ID。    /// </summary>    public string Id    {      get { return (string)GetValue(EpId); }      set { SetValue(EpId, value); }    }    /// <summary>    /// 获取或设置编码。    /// </summary>    public string No    {      get { return (string)GetValue(EpNo); }      set { SetValue(EpNo, value); }    }    /// <summary>    /// 获取或设置名称。    /// </summary>    public string Name    {      get { return (string)GetValue(EpName); }      set { SetValue(EpName, value); }    }    /// <summary>    /// 获取或设置排序。    /// </summary>    public int? OrderNo    {      get { return (int?)GetValue(EpOrderNo); }      set { SetValue(EpOrderNo, value); }    }    #endregion  }

      以上的实体模型,主要借鉴WPF中的依赖属性原理,定义一个静态的Field实现关系映射,最主要的一点,就是在设置属性的时候,交由基类的SetValue方法来执行,它会把这个修改过的属性记下来,在更新实体的时候,达到只更新修改过的属性的目的。

      而GetValue和SetValue看似有装箱和拆箱操作,实际上属性的值是以PropertyValue这个结构存储的,通过重载强制转换和显式转换操作符来交换数据,所以这里不用担心啦。

      那么,实际上有了EntityObject基类就好办多了,可以再抽象出一个类,让它基于AOP来创建,在设置属性值之后和获取属性值之后分别调用SetValue和GetValue就好了。

      Fireasy本来就提供了AOP,在Fireasy.Common.Aop命名空间中,关键是IInterceptor这个接口,它通过InterceptAttribute特性来绑定要拦截的方法、属性或是整个类。

      还有一个接口IAopSupport,它只起到一个标识,在扩展方法New中,如果类型实现了IAopSupport,那么则使用AspectFactory来创建对象。

      好,接下来我们定义一个 LighEntityObject 类:

  [Intercept(typeof(LighEntityInterceptor))]  public abstract class LighEntityObject<TEntity> : EntityObject,     IAopSupport,     IEntityPropertyInitialize    where TEntity : IEntity  {    /// <summary>    /// 构造一个代理对象。    /// </summary>    /// <returns></returns>    public static TEntity New()    {      return typeof(TEntity).New<TEntity>();    }    void IEntityPropertyInitialize.Initialize()    {      var entityType = this.GetType();      foreach (var property in entityType.BaseType.GetProperties(BindingFlags.Public | BindingFlags.Instance))      {        if (property.DeclaringType == entityType.BaseType)        {          //定义为 virtual          var getMth = property.GetGetMethod();          if (getMth != null && getMth.IsVirtual && !getMth.IsFinal)          {            RegisterProperty(entityType.BaseType, property);          }        }      }    }    private void RegisterProperty(Type entityType, PropertyInfo property)    {      //关联属性,即关联实体或子实体集属性      if (typeof(IEntity).IsAssignableFrom(property.PropertyType) ||        typeof(IEntitySet).IsAssignableFrom(property.PropertyType))      {        var mapping = property.GetCustomAttributes<PropertyMappingAttribute>().FirstOrDefault();        var options = mapping != null && mapping.GetFlag(PropertyMappingAttribute.SetMark.LoadBehavior) ?          new RelationOptions(mapping.LoadBehavior) : null;        PropertyUnity.RegisterSupposedProperty(property.Name, property.PropertyType, entityType, options: options);      }      else      {        var gp = new GeneralProperty()          {            Name = property.Name,            Type = property.PropertyType,            EntityType = entityType,            Info = new PropertyMapInfo { ReflectionInfo = property, FieldName = property.Name }          };        var mapping = property.GetCustomAttributes<PropertyMappingAttribute>().FirstOrDefault();        if (mapping != null)        {          InitMapInfo(mapping, gp.Info);        }        PropertyUnity.RegisterProperty(entityType, gp);      }    }    /// <summary>    /// 根据映射特性设置属性的映射信息。    /// </summary>    /// <param name="mapping"></param>    /// <param name="mapInfo"></param>    private void InitMapInfo(PropertyMappingAttribute mapping, PropertyMapInfo mapInfo)    {      mapInfo.FieldName = mapping.ColumnName;      mapInfo.Description = mapping.Description;      mapInfo.GenerateType = mapping.GenerateType;      if (mapping.GetFlag(PropertyMappingAttribute.SetMark.DataType))      {        mapInfo.DataType = mapping.DataType;      }      if (mapping.GetFlag(PropertyMappingAttribute.SetMark.IsPrimaryKey))      {        mapInfo.IsPrimaryKey = mapping.IsPrimaryKey;      }      if (mapping.GetFlag(PropertyMappingAttribute.SetMark.IsDeletedKey))      {        mapInfo.IsDeletedKey = mapping.IsDeletedKey;      }      if (mapping.DefaultValue != null)      {        mapInfo.DefaultValue = PropertyValue.New(mapping.DefaultValue, mapInfo.ReflectionInfo.PropertyType);      }      if (mapping.GetFlag(PropertyMappingAttribute.SetMark.Length))      {        mapInfo.Length = mapping.Length;      }      if (mapping.GetFlag(PropertyMappingAttribute.SetMark.Precision))      {        mapInfo.Precision = mapping.Precision;      }      if (mapping.GetFlag(PropertyMappingAttribute.SetMark.Scale))      {        mapInfo.Scale = mapping.Scale;      }    }  }

      关键还是拦截器了,看下面代码:

  public sealed class LighEntityInterceptor : IInterceptor  {    void IInterceptor.Initialize(InterceptContext context)    {    }    void IInterceptor.Intercept(InterceptCallInfo info)    {      if (info.InterceptType == InterceptType.AfterGetValue ||        info.InterceptType == InterceptType.AfterSetValue)      {        var entity = info.Target as EntityObject;        var entityType = entity.EntityType;        var property = PropertyUnity.GetProperty(entityType, info.Member.Name);        if (property == null)        {          return;        }        switch (info.InterceptType)        {          case InterceptType.AfterGetValue:            var value = entity.GetValue(property);            if (value != null && !value.IsEmpty)            {              info.ReturnValue = value.GetStorageValue();            }            break;          case InterceptType.AfterSetValue:            entity.SetValue(property, info.Arguments[0]);            break;        }      }    }  }

      其实很简单,在读取属性值和设置属性值的时候,调用GetValue和SetValue就可以了,是不是超简单哈。

      LighEntityObject定义好了,但是在何时使用AOP生成代理类呢。

      在QueryBinder中找到VisitConstant方法,加入下面这一段:

    protected override Expression VisitConstant(ConstantExpression c)    {      if (IsQueryable(c))      {        var rowType = c.Type.GetEnumerableElementType();        if (typeof(IEntity).IsAssignableFrom(rowType))        {          if (typeof(IAopSupport).IsAssignableFrom(rowType))          {            rowType = Fireasy.Common.Aop.InterceptBuilder.BuildType(rowType);          }          return VisitSequence(QueryUtility.GetTableQuery(EntityMetadataUnity.GetEntityMetadata(rowType)));        }        else        {          var q = (IQueryable)c.Value;          return base.Visit(q.Expression);        }      }      return c;    }

 

      好了,改造完成,现在的实体模型比较清爽多了:

  /// <summary>  /// 部门 实体类。  /// </summary>  [Serializable]  [EntityMapping("TB_DEPT", Description = "部门")]  public partial class Dept : LighEntityObject<Dept>  {        /// <summary>    /// 获取或设置ID。    /// </summary>    [PropertyMapping(ColumnName = "ID", Description = "ID", IsPrimaryKey = true, IsNullable = false, Length = 36)]    public virtual string Id { get; set; }    /// <summary>    /// 获取或设置编码。    /// </summary>    [PropertyMapping(ColumnName = "NO", Description = "编码", Length = 50)]    public virtual string No { get; set; }    /// <summary>    /// 获取或设置名称。    /// </summary>    [PropertyMapping(ColumnName = "NAME", Description = "名称", Length = 50)]    public virtual string Name { get; set; }    /// <summary>    /// 获取或设置排序。    /// </summary>    [PropertyMapping(ColumnName = "ORDER_NO", Description = "排序")]    public virtual int? OrderNo { get; set; }        /// <summary>    /// 获取或设置 <see cref="Employee"/> 的子实体集。    /// </summary>    public virtual EntitySet<Employee> Employees { get; set; }  }

      注意啰,属性都要定义成virtual,不然AOP是不会识别的。