原文:[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇)

.NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇)

前言:接着上篇来。

  系列文章链接:

[原创].NET 分布式架构开发实战之一 故事起源

[原创].NET 分布式架构开发实战之二 草稿设计

[原创].NET 分布式架构开发实战之三 数据访问深入一点的思考

[原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇)

[原创].NET 分布式架构开发实战五 Framework改进篇

[原创].NET 业务框架开发实战之六 DAL的重构

[原创].NET 业务框架开发实战之七 业务层初步构想

[原创].NET 业务框架开发实战之八 业务层Mapping的选择策略

[原创].NET 业务框架开发实战之九 Mapping属性原理和验证规则的实现策略

[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(前篇)

[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇)

3. 再次借鉴.NET Framework设计思想

自己实现其实不难,关键看怎么做了。在实现的时候,如果自己单独去搞一套方式,如果设计的不好,可能到后来别人不好理解,甚至连自己也忘记当初自己为什么这样设计。所以,要充分的借鉴已有的好的实现思想。分析了IQueryable,其实最大的区别就是,我们不希望去立刻操作数据源,但是在实现IQueryable过程中是操作数据源的。除此之外,如记录对于实现了IQueryable接口的类的上的操作,这是我们需要的,也就是说我们自己的实现的条件对象也要记录对它对象的操作,然后把这些操作在服务器那边解析执行。

所以,条件对象的接口实现如下:

代码

 /// <summary>
    /// 所有的查询对象都要从这个接口继承
    /// </summary>
    public interface ICriteria
    {
        #region Property

        Type ObjectType { get; }
        
        ICriteriaProvider Provider { get; }

        #endregion

    }

代码 

 /// <summary>    /// 泛型版的条件对象    /// </summary>    /// <typeparam name="TSource"></typeparam>    public interface ICriteria<T> : ICriteria    {        Dictionary<string, List<Expression>> ExpressionDictionary { get; }        ICriteria<T> Where(Expression<Func<T, bool>> predicate);        ICriteria<T> OrderBy<K>(Expression<Func<T, K>> predicate);        ICriteria<T> OrderByDescending<K>(Expression<Func<T, K>> predicate);        ICriteria<T> ThenBy<K>(Expression<Func<T, K>> predicate);        ICriteria<T> ThenByDescending<K>(Expression<Func<T, K>> predicate);        ICriteria<T> Skip(int count);        ICriteria<T> Take(int count);        ICriteria<T> First();        ICriteria<T> First(Expression<Func<T, bool>> predicate);        ICriteria<T> Distinct<K>(Expression<Func<T, K>> predicate);        ICriteria<T> All(Expression<Func<T, bool>> predicate);        ICriteria<T> Any(Expression<Func<T, bool>> predicate);        ICriteria<T> GroupBy<K>(Expression<Func<T, K>> predicate);        ICriteria<T> Max<K>(Expression<Func<T, K>> predicate);        ICriteria<T> Min<K>(Expression<Func<T, K>> predicate);    }
    public interface ICriteriaProvider    {               object Execute<T>(ICriteria<T> condition);    }

大家可以看到,上面的接口的声明和IQueryable合QueryProvider的声明很相似。

大家看到了,在上面的ICritera接口声明中,有一些方法,如Where,First等等,其实这些方法在IQueryable也是有的,只不过是以扩展方法的形式出现了。我们这里就直接写在这里了,如果以后要加入更多的方法,我们也可以利用扩展方法的方式。

在IQueryable中,使用 Expression Tree表达式树来记录操作的,那么我们这里也采用这种方式。

还有一点比较重要:在上面的自己的实现linq to sql的例子中,或者微软官方实现的linq to sql中,操作的都是数据实体和ADO.NET对象之间的关系,例如下面的代码:

代码

  public bool MoveNext() {
            if (this.reader.Read()) {
                if (this.fieldLookup == null) {
                    this.InitFieldLookup();
                }
                T instance = new T();
                for (int i = , n = this.fields.Length; i < n; i++) {
                    int index = this.fieldLookup[i];
                    if (index >= ) {
                        FieldInfo fi = this.fields[i];
                        if (this.reader.IsDBNull(index)) {
                            fi.SetValue(instance, null);
                        }
                        else {
                            fi.SetValue(instance, this.reader.GetValue(index));
                        }
                    }
                }
                this.current = instance;
                return true;
            }
            return false;
        }
 

从代码中可以看出,在解析表达式树后,执行返回的数据实体的属性的名字和查询出的表的字段名字是相同的。

但是:我们在客户端构造出来的条件对象中使用的字段是业务类的属性,业务类中的属性名字和数据库表字段的名字可能不是一样的,甚至有可能业务类中的一个属性的值是几个表中字段计算而来的。如Age>3,其中在Age属性的值,可能来自己数据库中UserDetail表中age字段。所以,对于在业务类上生成的条件对象,通过解析最后要生成对应表的查询,例如在User类上生成的条件对象,最后生成的sql语句要是:select * from UserDetail where age>3 ,而不是select * from User where Age>3. 也就是说,User业务类要清楚的知道自己的数据来自哪个数据库表,而且User类中的属性也要清楚的知道自己的数据到底来自数据表中的哪个字段。

下面就来讲述如何解决上面提到的问题。

Mapping属性

代码

public static readonly PropertyInfo<int> UserIdProperty = RegisterProperty<User>(
            new PropertyInfo<int>("UserId",typeof(M_Product)","Id")); 

    public string UserId
    {

      get { return ReadProperty(UserIdProperty ); }
      set { LoadProperty(UserIdProperty , value); }
    }

从代码中可以看出,业务类的属性的声明和之前有点不一样了。可能一直以来,业务类中的属性都是像下面这样声明的:

 public string UserId { get; set; }

但是,为了解决上面的问题:即每个属性都要清楚的知道自己对应数据表中的那个字段,那么每个属性就要保存一些数据库中表的字段的信息,这样也便于条件对象最后生成正确的sql语句。

在框架中,有一个接口的声明:

代码

 public interface IPropertyInfo
    {
        /// <summary>
        /// Gets the property name value.
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Gets the type of the property.
        /// </summary>
        Type Type { get; }

        /// <summary>
        /// Gets the friendly display name
        /// for the property.
        /// </summary>
        string FriendlyName { get; }

        /// <summary>
        /// Mapping data entity type
        /// </summary>
        Type DataEntityTyoe { get; set; }

        /// <summary>
        /// Mapping to data entity name
        /// </summary> 
        string DataEntityPropertyName { get; set; }

        /// <summary>
        /// Gets the default initial value for the property.
        /// </summary>
        /// <remarks>
        /// This value is used to initialize the property's
        /// value, and is returned from a property get
        /// if the user is not authorized to 
        /// read the property.
        /// </remarks>
        object DefaultValue { get; }
           }

这个接口就为是为用来描述业务类中每一个属性的信息的,例如,属性对应哪个数据表中哪个字段。

框架中存在一个全局的字典,用来保存所有业务类的一些属性的信息:如下

Dictionary<Type,List<IPropertyInfo>> propertyInfomationDictionary;

在条件对象中的表达式树遍历解析的时候,就会使用这个字典中保存的信息来生成正确的sql语句。看看下面条件对象解释器的代码:大家只看Translate方法就行了。

代码

 public class CriteriaTranslator : YYT.Core.ExpressionVisitor
    {
        #region Fields

        StringBuilder sb;
        Dictionary<string, string> resultDictionary = null;
        private Type objectType;

        #endregion

        #region Constructor

        internal CriteriaTranslator(Type type)
        {
            objectType = type;
        }

        #endregion

        #region Main Methods

        internal Dictionary<string, string> Translate(Dictionary<string, List<Expression>> expressionDictionary)
        {
            resultDictionary = new Dictionary<string, string>();

            foreach (var expressionKeyValePair in expressionDictionary)
            {
                this.sb = new StringBuilder();
                foreach (Expression expression in expressionKeyValePair.Value)
                {
                    this.Visit(Evaluator.PartialEval(expression));
                }
                AddExpressionResult(expressionKeyValePair.Key, sb.ToString());

            }
            return resultDictionary;
        }

        #endregion

        #region Override Methods

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(string))
            {
                switch (m.Method.Name)
                {
                    case "StartsWith":
                        sb.Append("(");
                        this.Visit(m.Object);
                        return m;
                    case "Contains":
                        sb.Append("(");
                        this.Visit(m.Object);
                        sb.Append(" LIKE '%' + ");
                        this.Visit(m.Arguments[]);
                        sb.Append(" + '%')");
                        return m;
                }
            }

            throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            switch (u.NodeType)
            {
                case ExpressionType.Not:
                    sb.Append(" NOT ");
                    this.Visit(u.Operand);
                    break;
                default:
                    throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
            }
            return u;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
            {
                if (this.objectType.IsAssignableFrom(typeof(BusinessBase)))
                {
                    List<IPropertyInfo> propertyInfoList = PropertyInfoManager.GetRegisteredProperties(this.objectType);
                    var property = propertyInfoList.Where(u => u.Name == m.Member.Name).SingleOrDefault();
                    if (property != null)
                    {
                        var dataEntityName = property.DataEntityPropertyName;
                        sb.Append(dataEntityName);
                        AddExpressionResult("Type", property.DataEntityTyoe.Name);
                    }
                }
                return m;
            }
            else
                throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));

        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            sb.Append("(");
            this.Visit(b.Left);
            switch (b.NodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    sb.Append(" AND ");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    sb.Append(" OR");
                    break;
                case ExpressionType.Equal:
                    sb.Append(" = ");
                    break;
                case ExpressionType.NotEqual:
                    sb.Append(" <> ");
                    break;
                case ExpressionType.LessThan:
                    sb.Append(" < ");
                    break;
                case ExpressionType.LessThanOrEqual:
                    sb.Append(" <= ");
                    break;
                case ExpressionType.GreaterThan:
                    sb.Append(" > ");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    sb.Append(" >= ");
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
            }
            this.Visit(b.Right);
            sb.Append(")");
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            IQueryable q = c.Value as IQueryable;
            if (q != null)
            {
                // assume constant nodes w/ IQueryables are table references
                sb.Append("SELECT * FROM ");
                sb.Append(q.ElementType.Name);
            }
            else if (c.Value == null)
            {
                sb.Append("NULL");
            }
            else
            {
                switch (Type.GetTypeCode(c.Value.GetType()))
                {
                    case TypeCode.Boolean:
                        sb.Append(((bool)c.Value) ?  : );
                        break;
                    case TypeCode.String:
                        sb.Append("'");
                        sb.Append(c.Value);
                        sb.Append("'");
                        break;
                    case TypeCode.Object:
                        throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
                    default:
                        sb.Append(c.Value);
                        break;
                }
            }
            return c;
        }

        #endregion

        #region Assistant Methods

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        private void AddExpressionResult(string key, string value)
        {
            string tempValue = string.Empty;
            if (!resultDictionary.TryGetValue(key, out tempValue))
            {
                lock (resultDictionary)
                {
                    if (!resultDictionary.TryGetValue(key, out tempValue))
                    {
                        resultDictionary.Add(key, value);
                    }
                }
            }
        }

        #endregion
    }

VisitMemberAccess方法就是用来把业务属性的名字换成对应数据库中表字段的名字的。

代码

   protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
            {
                if (this.objectType.IsAssignableFrom(typeof(BusinessBase)))
                {
                    List<IPropertyInfo> propertyInfoList = PropertyInfoManager.GetRegisteredProperties(this.objectType);
                    var property = propertyInfoList.Where(u => u.Name == m.Member.Name).SingleOrDefault();
                    if (property != null)
                    {
                        var dataEntityName = property.DataEntityPropertyName;
                        sb.Append(dataEntityName);
                        AddExpressionResult("Type", property.DataEntityTyoe.Name);
                    }
                }
                return m;
            }
            else
                throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));

        }

上面的代码中,并没有立刻就把所有操作拼接成sql语句,而且把操作分类的记录下来在一个字典中,这个字典最后会被数据层那里使用。例如,字典中可能保存的值如下:

操作

生成的sql语句

Where

age>18 and name like ’%xiaoyang%’

OrderBy

age

GroupBy

age

...............

.........................

最后这个字典会在数据层那边被使用:ICriteria包含CriteriaProvider,然后DAL代码调用Icriteria.CriteriaProvider.Execute(),获得字典,然后执行真正的数据库操作。

下面就看看数据层的一些接口声明就清楚了。

代码


    /// <summary>
    /// 数据提供者要实现的借口
    /// </summary>
    public interface IDataProvider
    {
        DataResult<TEntity> Add<TEntity>(TEntity entity) where TEntity : IDataEntity;
        DataResult<TEntity> Add<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;

        DataResult<TEntity> Update<TEntity>(TEntity entity) where TEntity : IDataEntity;
        DataResult<TEntity> Update<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;
        bool Update(ICriteria condiftion, object value);

        DataResult<TEntity> Delete<TEntity>(TEntity entity) where TEntity : IDataEntity;
        DataResult<TEntity> Delete<TEntity>(List<TEntity> entityList) where TEntity : IDataEntity;
        bool Delete(ICriteria condiftion);

        int GetCount(ICriteria condition);

        DataResult<TEntity> GetOne<TEntity>(ICriteria condition) where TEntity : IDataEntity;
        DataResult<TEntity> GetList<TEntity>(ICriteria condition) where TEntity : IDataEntity;
        DataResult<TEntity> GetPageData<TEntity>(ICriteria condition, int pageIndex, int pageSize, ref int entityCount) where TEntity : IDataEntity;
        List<object> GetCustomData(ICriteria condiftion);
    }

4. 水到渠成

到这里,一切就很清楚了。如果有不明白的地方或者需要讨论的,大家可以给我留言。我会及时的解答。

5. 代码版本的说明

大家之后可以下载到代码的 V1.0版本,下载代码之后可以看到,这个V1.0的版本的代码中存在着CSLA的影子,确实是这样的。本框架在V1.0中确实融合了很多的开源框架,所以不要奇怪。对CSLA不懂,也是没有问题的。

下面我就讲述一下以后版本的一些特性和本系列文章的走向:

V1.0: 存在CLSA的影子。

V2.0:去掉CLSA的影子,重构,并且使用VS开发出DSL工具,图形化的业务类开发和代码的生成

V3.0: 设计和开发出一种基于领域的语言和这个语言的编译器。

V4.0: 设计和开发领域语言的代码IDE。

V5.0: 使用领域语言开发项目

其中使用领域的语言开发时 目标:例如,可能最后的代码是这样的写的:

Define a use who name is “xiaoyang”and email is “yangyang4502@yahoo.com.cn

然后我们的领域语言编译器把上面的语句解释为代码,然后进行操作,最终的结果就是在数据库中的UserDetail表中添加了一条记录。

也就是我们梦寐以求的那种“自然语言编程”,是的业务人员来写程序。

文章以后的难度和深度会越来越大,我们一起加油,也希望大家及时的反馈。

呵呵,现在比较的兴奋,说句”胡话”:咱们搞开发的,不仅仅只是懂得使用别人的开源框架和开源技术,咱们自己也可以成为技术的创建者。共勉!

版权为小洋和博客园所有,欢迎转载,转载请标明出处给作者。

     http://www.cnblogs.com/yanyangtian

     Code

[原创].NET 业务框架开发实战之十 第一阶段总结,深入浅出,水到渠成(后篇)的更多相关文章

  1. [原创].NET 业务框架开发实战之九 Mapping属性原理和验证规则的实现策略

    原文:[原创].NET 业务框架开发实战之九 Mapping属性原理和验证规则的实现策略 .NET 业务框架开发实战之九 Mapping属性原理和验证规则的实现策略 前言:之前的讨论一直关注在怎么从D ...

  2. [原创].NET 业务框架开发实战之八 业务层Mapping的选择策略

    原文:[原创].NET 业务框架开发实战之八 业务层Mapping的选择策略 .NET 业务框架开发实战之八 业务层Mapping的选择策略 前言:在上一篇文章中提到了mapping,感觉很像在重新实 ...

  3. [原创].NET 业务框架开发实战之七 业务层初步构想

    原文:[原创].NET 业务框架开发实战之七 业务层初步构想 .NET 业务框架开发实战之七 业务层初步构想 前言:本篇主要讲述如何把DAL和BLL衔接起来. 本篇议题如下: 1.       DAL ...

  4. [原创].NET 业务框架开发实战之六 DAL的重构

    原文:[原创].NET 业务框架开发实战之六 DAL的重构 .NET 业务框架开发实战之六 DAL的重构 前言:其实这个系列还是之前的".NET 分布式架构开发实战 ",之所以改了 ...

  5. [原创].NET 分布式架构开发实战五 Framework改进篇

    原文:[原创].NET 分布式架构开发实战五 Framework改进篇 .NET 分布式架构开发实战五 Framework改进篇 前言:本来打算这篇文章来写DAL的重构的,现在计划有点改变.之前的文章 ...

  6. [原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇)

    原文:[原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) .NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) 前言:上一篇文章讲述了一些实现DAL的理论,本 ...

  7. [原创].NET 分布式架构开发实战之三 数据访问深入一点的思考

    原文:[原创].NET 分布式架构开发实战之三 数据访问深入一点的思考 .NET 分布式架构开发实战之三 数据访问深入一点的思考 前言:首先,感谢园子里的朋友对文章的支持,感谢大家,希望本系列的文章能 ...

  8. [原创].NET 分布式架构开发实战之二 草稿设计

    原文:[原创].NET 分布式架构开发实战之二 草稿设计 .NET 分布式架构开发实战之二 草稿设计 前言:本篇之所以称为草稿设计,是因为设计的都是在纸上完成的.反映了一个思考的过程. 本篇的议题如下 ...

  9. [原创].NET 分布式架构开发实战之一 故事起源

    原文:[原创].NET 分布式架构开发实战之一 故事起源 .NET 分布式架构开发实战之一 故事起源 前言:本系列文章主要讲述一个实实在在的项目开发的过程,主要包含:提出问题,解决问题,架构设计和各个 ...

随机推荐

  1. hdu 4529 Double Dealing (置换群)

    # include <stdio.h> # include <algorithm> # include <string.h> using namespace std ...

  2. poj3252(数位dp)

    题目连接:http://poj.org/problem?id=3252 题意:拆成2进制,在记录0和1的个数 求区间[a,b]中,满足传化成2进制后,0的个数>=1的个数的数字的个数... 分析 ...

  3. 一个linux常见命令的列表

    这是一个linux常见命令的列表. 那些有• 标记的条目,你可以直接拷贝到终端上而不需要任何修改,因此你最好开一个终端边读边剪切&拷贝. 所有的命令已在Fedora和Ubuntu下做了测试 命 ...

  4. Mvc 异常处理 ajax的 和 不是ajax的!

    using ImageUpload.Auth; using System; using System.Collections.Generic; using System.Linq; using Sys ...

  5. Ubuntu12.04下载Repo

    操作系统:Ubuntu12.04LTS 64bit "#"号后面表示凝视内容 $cd ~ #进入下载文件夹 $mkdir bin #创建bin文件夹用于存储Repo脚本 $PATH ...

  6. auto property synthesis will not synthesize proterty ;it will be implementedby its superclass, use @

    Auto property synthesis will not synthesize property 'title'; it will be implemented by its supercla ...

  7. Android学习之 AChartEngine 图表绘制

    Android 开源图表绘制工具AChartEngine地址:http://code.google.com/p/achartengine/ AChartEngine Android实现图表绘制和展示( ...

  8. android 如何分析java.lang.IllegalArgumentException: Cannot draw recycled bitmaps异常

    这类问题的分析,通常你需要找到bitmap对象已经在那个位置recyle,然后检查代码. 如何定位的位置,其中代码具有对bitmap 目的recyle.能够 Bitmap.java的recycle方法 ...

  9. IIS6,IIS7中查看w3wp进程

    当我们服务器创建了很多应用程序池,然后某个w3wp.exe进程占用CPU和内存过高,我们怎么查找这个w3wp.exe进程属于哪一个网站呢.其实微软为我们提供了很好的查看工具: 首先打开windows任 ...

  10. Windows+Atlassian-Jira-6.0.4+MySql5.0安装破解汉化

     Windows+Atlassian-Jira-6.0.4+MySql5.0安装破解汉化 一:整理的安装程序 例如以下图: 文件太大.上传不到csdn上.有须要的联系. 新增的百度云盘下载:链接: ...