Query Object--查詢對象模式(下)

回顧html

  上一篇對模式進行了介紹,並基於ADO.NET進行了實現,雖然如今ORM框架愈來愈流行,可是不少中小型的公司仍然是使用ADO.NET來進行數據庫操做的,隨着項目的需求不斷增長,業務不斷變化,ADO.NET的實現方式,會使原先簡單的單表操做變得尤其複雜,特別是數據庫表發生改變的狀況下,沒法像ORM框架那樣,經過修改映射來達到統一的修改,須要靠程序員檢查每一段相關的SQL來排查錯誤,這是很是麻煩的。
前端

  無論什麼樣的框架,使用起來不簡單不易用的話,那麼就沒有設計的必要了。程序員

  所以今次的文章將會基於ORM框架來進行實現,大體內容以下:web

  • 基於表達式的實現
  • 使用NHibernate查詢
  • 使用BeeGo查詢

基於表達式的實現ajax

  所以在現在在C#領域內,若是沒有Linq風格,對於編碼人員來講就顯得有些複雜了,所以擴展方向上確定是要支持Linq。上一篇文章並無實現對Linq的支持,而是留給你們去實現了,所以文章開頭,就先複習一下吧。數據庫

  首先從簡單的調用方式開始吧,如:express

query.Add<School>(s => s.Name == "一中");
或者
query.Add<School>(s => s.Age > 20);

  分析以上兩個查詢條件表達式,而且跟原先的Criterion來進行對比,依然是能夠經過解析表達式來生成Criterion對象的,可是因爲NHibernate已經支持表達式了,所以須要重構一下Query Object模式,首先刪除Criterion類,並對Query代碼進行修改,代碼以下:服務器

private List<Expression> m_Criterions = new List<Expression>();             
public IEnumerable<Expression> Criterions { get { return m_Expressions; } } 

public void Add<T>(Expression<Func<T, bool>> exp)
{
    Add(exp as Expression);
}

public void Add(Expression exp)
{
    if (exp.NodeType == ExpressionType.Lambda)
        Add((exp as LambdaExpression).Body);
    else
        m_Criterions.Add(exp);
}

  接下來,須要支持and或者or了,因爲and和or涉及到子查詢的問題,當父查詢的QueryOperator與子查詢的QueryOperator不一樣的狀況下,表達式就須要被轉換成一個子查詢了,所以代碼改成:數據結構

public void Add(Expression exp)
{
    if (exp.NodeType == ExpressionType.Lambda)
        Add((exp as LambdaExpression).Body);
    else if (exp.NodeType == ExpressionType.OrElse || exp.NodeType == ExpressionType.AndAlso)
        AddByJunctionExpression(exp);
    else
        m_Expressions.Add(exp);
}

private void AddByJunctionExpression(Expression exp)
{
    var binaryExp = exp as BinaryExpression;
    if ((Operator == QueryOperator.And && exp.NodeType == ExpressionType.AndAlso) ||
        (Operator == QueryOperator.Or && exp.NodeType == ExpressionType.OrElse))
    {
        Add(binaryExp.Left);
        Add(binaryExp.Right);
    }
    else
    {
        Query subQuery = new Query(exp.NodeType == ExpressionType.OrElse ? QueryOperator.Or : QueryOperator.And);
        subQuery.Add(binaryExp.Left);
        subQuery.Add(binaryExp.Right);
        AddSubQuery(subQuery);
    }
}

  到這裏基於表達式的Query Object就改造完成了,那麼接下來就要根據數據層具體的環境來說Query轉化爲對應的API來查詢數據了。框架

使用NHibernate查詢

  先上代碼,而後分析,大體代碼爲:

public static NHibernate.ICriterion ToNHibernateQuery<T>(Query query)
{
    NHibernate.Junction junction;
    if (query.Operator == QueryOperator.And)
        junction = NHibernate.Expression.Conjunction();
    else
        junction = NHibernate.Expression.Disjunction();

    if (expressions.Any())
    {
        foreach (var exp in expressions)
            AppendNHibernateCriterionByExpression(junction, exp);
    }

    this.AppendNHibernateCriterionBySubQueries(junction);
    return junction;
}

private static void AppendNHibernateCriterionByExpression<T>(NHibernate.Junction junction, Expression exp)
{
    var binaryExp = exp as BinaryExpression;
    var expression = Expression.Lambda<Func<T, bool>>(
        exp,
        GetParameterExpressionBy(binaryExp.Left) ?? GetParameterExpressionBy(binaryExp.Right));
    junction.Add(expression);
}

private static ParameterExpression GetParameterExpressionBy(Expression exp)
{
    if (exp.NodeType != ExpressionType.MemberAccess)
        return null;

    var memberExp = exp as MemberExpression;
    return memberExp.Expression as ParameterExpression;
}

private static void AppendNHibernateCriterionBySubQueries<T>(NHibernate.Junction junction, IEnumerable<Query> subQueries)
{
    if (!subQueries.Any())
        return;

    foreach (var subQuery in subQueries)
    {
        var subCriterion = ToNHibernateQuery<T>(subQuery);
        junction.Add(subCriterion);
    }
}

  因爲NHibernate內部已經實現了Query Object模式,所以在轉換的過程中,只須要將And和OR條件轉化爲對應的NHibernate類就好了,而後利用NHibernate對於表達式的支持將條件添加進去,最後使用ICriteria.List<T>()獲取結果就能夠了。

  上一篇文章有提到對於Query Object模式對於外聯的支持是比較麻煩的,可是在NHibernate的基礎下去實現是比較簡單的,這裏就再也不作過多的介紹了,有意向的朋友要對NHbernate作深刻的研究,這裏推薦你們看看李永京的文章學習一下,或者買基本NHibernate的書學習。

使用BeeGo查詢

  GO語言出來也有一段時間了,不少大公司也都在使用它,咱們也不能落後。因爲最近使用BeeGo框架搭建了數據服務器,Query Object模式固然也是必須的,所以藉着這篇文章,也順帶講講BeeGo框架上,Query Object模式的實現。

  雖然如今大部分的公司使用的數據庫依然是關係型數據庫,可是仍然擋不住NoSQL這個新興數據庫的腳步,因爲最近一段時間都是是用Nodejs來進行web開發的,所以使用NOSQL數據庫能更好的進行數據庫交互,畢竟能夠直接使用JSON數據結構,可是因爲同事對於關係型數據庫比較習慣所以不得不改成關係型數據庫,因而就有了上面提到的GO數據服務器了。

  爲了使原先查詢數據的方式不用改變,所以引用了MongoDb的API結構來實現Query Object模式,ajax查詢代碼爲:

$.ajax({
    url: '/school/find',
    data: {
        $or: {
            name: '一中',
            age: {
                $gt: 20
            }
        }
    }
});

  以上結構等同於select * from school where name = '一中' or age > 20,按照之前幾回實現Query Object的經驗,此次要實現這種結構的轉換相對仍是比較簡單的,在Go語言當中,通用類型是interface{},它至關於C#的Object,字典也有一些差異,可是並不妨礙具體的實現,這裏爲了簡便(使用Go的結構太複雜了),所以直接使用switch來轉換成begoo的接口的,大體代碼以下:

func (this *Repository) translateToCriterion(field string, criterion map[string]interface{}, isAnd bool, isNot bool) *orm.Condition {
    cond := orm.NewCondition()
    var value interface{}
    for k, v := range criterion {
        key := field
        value = v
        switch k {
            case "$take":
                continue                                                                                                                   
            case "$skip":
                continue
            case "$sort":
                continue
            case "$like":
                startsWith, endsWith := strings.HasSuffix(v.(string), "%"), strings.HasPrefix(v.(string), "%")
                chars := []byte(v.(string))
                if startsWith && endsWith {
                    key = key + "__icontains";
                    value = string(chars[1:len(chars)-1])
                } else if startsWith {
                    key = key + "__istartswith"
                    value = string(chars[:len(chars)-1])
                } else {
                    key = key + "__iendswith"
                    value = string(chars[1:])
                }
                break
            case "$gt":
                key = key + "__gt"
                break
            case "$lt":
                key = key + "__lt"
                break
            case "$in":
                key = key + "__in"
                break
            case "$not":
                if reflect.TypeOf(v).Kind() == reflect.Map {
                    value = this.translateToCriterion(field, v.(map[string]interface{}), isAnd, true)
                } else {
                    isNot = true
                }
                break
            case "$and":
                value = this.translateToCriterion("", v.(map[string]interface{}), true, isNot)
                break
            case "$or":
                value = this.translateToCriterion("", v.(map[string]interface{}), false, isNot)
                break
            default:
                if v != nil && reflect.TypeOf(v).Kind() == reflect.Map {
                    value = this.translateToCriterion(k, v.(map[string]interface{}), isAnd, isNot)
                } else if v == nil {
                    key = k + "__isnull"
                    value = true
                } else {
                    key = k
                }
                break
        }
        subCond, isCond := value.(*orm.Condition)
        if isAnd {
            if isCond {
                cond = cond.AndCond(subCond)
            } else if isNot {
                cond = cond.AndNot(key, value)
            } else {
                cond = cond.And(key, value)
            }
        } else {
            if isCond {
                cond = cond.OrCond(subCond)
            } else if isNot {
                cond = cond.OrNot(key, value)
            } else {
                cond = cond.Or(key, value)
            }
        }
    }
    return cond
}                                                                                                                                          

  這樣就實現了BeeGo框架下的Query Object模式了,若是對GO有興趣的話,也能夠直接使用BeeGo框架來搭建Web應用,那麼前端能夠直接使用前面那樣的ajax來實現數據的訪問,這樣仍是很方便的。

結尾

  Query Obejct模式雖然能夠簡化數據的查詢,雖然對於數據權限是沒有做用的,可是因爲使用Query Object模式,查詢接口是固定的,所以能夠在查詢方法內添加數據權限模塊,這樣能夠簡化數據權限實現的困難。

  那麼此次的文章就到這裏了,若有疑問和錯誤請你們留言給我,謝謝。

相關文章
相關標籤/搜索