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


回顧

  上一篇對模式進行了介紹,並基於ADO.NET進行了實現,雖然現在ORM框架越來越流行,但是很多中小型的公司仍然是使用ADO.NET來進行數據庫操作的,隨着項目的需求不斷增加,業務不斷變化,ADO.NET的實現方式,會使原先簡單的單表操作變得尤為復雜,特別是數據庫表發生改變的情況下,無法像ORM框架那樣,通過修改映射來達到統一的修改,需要靠程序員檢查每一段相關的SQL來排查錯誤,這是非常麻煩的。

  不管什么樣的框架,使用起來不簡單不易用的話,那么就沒有設計的必要了。

  因此今次的文章將會基於ORM框架來進行實現,大致內容如下:

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

基於表達式的實現

  因此在如今在C#領域內,如果沒有Linq風格,對於編碼人員來說就顯得有些復雜了,因此擴展方向上肯定是要支持Linq。上一篇文章並沒有實現對Linq的支持,而是留給大家去實現了,因此文章開頭,就先復習一下吧。

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

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模式,查詢接口是固定的,因此可以在查詢方法內添加數據權限模塊,這樣可以簡化數據權限實現的困難。

  那么這次的文章就到這里了,如有疑問和錯誤請大家留言給我,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM