现在的位置: 首页 > 综合 > 正文

nhibernate源码分析之六: Criteria数据加载

2013年12月06日 ⁄ 综合 ⁄ 共 6845字 ⁄ 字号 评论关闭

nhibernate源码分析之六: Criteria数据加载

                                                                                                                               作者:张老三

ICriteria是使用Expression进行数据加载的接口, 提供了设置表达式(Expression), 排序方式(Order), 分页记录等操作.
它使用一种类似于SQL语句where表达表的方式来加载满足条件的数据.

下面以一个查询Username为billy, Password为test的用户为例来分析nh中Criteria数据加载是怎样工作的.

查询代码如下:

Expression ex = Expression.And( Expression.Eq("Username", "billy"),
Expression.Eq("Password", "test") );
IList users = session.CreateCriteria( typeof(Username) ).Add( ex ).List();

这里先建立了一个Expression, 然后加入到ICriteria中. ICriteria通过session.CreateCriteria( type )获得.
注意: ICriteria的实现类CriteriaImpl是一个Internal类, 因此不能在nh程序集外直接创建.

 

SqlString和SqlStringBuilder对象

因后面的代码多次用到SqlString和SqlStringBuilder对象,这里先说明一下。

SqlString对象用于存储伪Sql语句中的各个部分,注意,是伪Sql语句哦~, 不是实际的sql语句,不过实际的sql语句是通过SqlString对象解析而来的。
假如我有这样的一个伪sql语句,
  select * from User u
    where u.Username = @Username and u.Password = @Password
那么由此产生的SqlString将包含一个这样的数组,{"select", "*", "from", "User", "where", "Username", "=", param, "and", "Password", "=", param2 }, 其中param, param2是nh中的Paramenter(参数)对象.

SqlStringBuilder对象专门用于创建SqlString对象,因为SqlString是不能修改它内部的sqlPart的,如要修改sqlPart, 必须使用SqlStringBuilder.

Expression对象

Expression是所有表达式对象的祖先, 它使用Factory Method(工厂模式)来创建具体的表达式(如相等表达式EqExpression).

这里介绍几个最要的表达式对象:
SimpleExpression: 简单表达式对象, 是所有简单操作表达式的父类, 如EqExpression, GtExpression等;
LogicalExpression: 逻辑表达式对象, 用于指定两个表达式的逻辑关系, 有AndExpression和OrExpression两个子类;

最值的关注就是Expression如何产生它的sqlString了, 这个由ToSqlString方法完成.

// *** SimpleExpression.cs 60行***
public override SqlString ToSqlString(ISessionFactoryImplementor factory,
    System.Type persistentClass, string alias) {

   SqlStringBuilder sqlBuilder = new SqlStringBuilder();

   IType propertyType = ((IQueryable)factory.GetPersister(persistentClass)).GetPropertyType(propertyName);
   string[] columnNames = GetColumns(factory, persistentClass, propertyName, alias);
   string[] paramColumnNames = GetColumns(factory, persistentClass, propertyName , null);
   Parameter[] parameters = Parameter.GenerateParameters(factory, alias, paramColumnNames, propertyType);

   for(int i = 0; i < columnNames.Length; i++) {
      if(i > 0) sqlBuilder.Add(" AND ");

      sqlBuilder.Add(columnNames[i])
      .Add(Op)
      .Add(parameters[i]);
   }
   return sqlBuilder.ToSqlString();
}
取得属性对应的数据列名, 并创建Parameter对象, 在解析SqlString时将根据Parameter产生实际的IDbParameter对象.

// *** SimpleExpression.cs 65行***
public override SqlString ToSqlString(ISessionFactoryImplementor factory,
   System.Type persistentClass, string alias) {

   SqlStringBuilder sqlBuilder = new SqlStringBuilder();

   SqlString lhSqlString = lhs.ToSqlString(factory, persistentClass, alias);
   SqlString rhSqlString = rhs.ToSqlString(factory, persistentClass, alias);

   sqlBuilder.Add(new SqlString[] {lhSqlString, rhSqlString},
      "(", Op, ")");

   return sqlBuilder.ToSqlString();
}
先得到逻辑操作两边的表达式SqlString, 然后连同逻辑操作一起加入到sqlBuilder对象中。

另外一个要关注的就是GetTypedValues方法了,它返回表达式中属性的类型和值,用于给参数赋值。

加载过程源码分析

//*** SessionImpl.cs 3743行 ***
public ICriteria CreateCriteria(System.Type persistentClass) {
   return new CriteriaImpl(persistentClass, this);
}

创建一个CriteriaImpl, 并将对象类型和会话传递给去.

//*** CriteriaImpl.cs 43行 ***
public ICriteria Add(NExpression.Expression expression) {
   expressions.Add(expression);
   conjunction.Add(expression);
   return this;
}

将表达式加入到集合中.

 

//*** CriteriaImpl.cs 67行 ***
public IList List() {
  return session.Find(this);
}
调用session.Find, 看来CriteriaImpl没干实事,只是存储一些查询信息.

//*** CriteriaImpl.cs 3748行 ***
public IList Find(CriteriaImpl criteria) {
  System.Type persistentClass = criteria.PersistentClass;

  ILoadable persister = (ILoadable) GetPersister(persistentClass);
  CriteriaLoader loader = new CriteriaLoader(persister, factory, criteria);

   // 省略若干行...
   try {
      return loader.List(this);
   }
   catch (Exception e) {
      throw new ADOException("problem in find", e);
   }
      // 省略若干行...
}
先取得要查询对象的持久化类, 然后创建一个CriteriaLoader, 最后返回CriteriaLoader.List的结果.
CriteriaLoader是数据加载类Loader族中的一员, 用于实现Criteria数据加载.

//*** CriteriaLoader.cs 28行 ***
public CriteriaLoader(ILoadable persister, ISessionFactoryImplementor factory,
   ICriteria criteria) : base(persister, factory) {
   this.criteria = criteria;

   // 此句无用, 不知为何保留?
   IEnumerator iter = criteria.IterateExpressions();

   StringBuilder orderByBuilder = new StringBuilder(60);

   bool commaNeeded = false;
   iter = criteria.IterateOrderings();

   while ( iter.MoveNext() ) {
      Order ord = (Order) iter.Current;

      if(commaNeeded) orderByBuilder.Append(StringHelper.CommaSpace);
      commaNeeded = true;

      orderByBuilder.Append(ord.ToStringForSql(factory, criteria.PersistentClass, alias));
   }

   RenderStatement(criteria.Expression.ToSqlString(factory, criteria.PersistentClass, alias),
   orderByBuilder.ToString(), factory);

   PostInstantiate();
}
首先枚举排序对象, 并组合成orderby子句(ToStringForSql比较简单, 请参考Order对象);
然后调用父类AbstractEntityLoad的RenderStatement方法生成SqlString对象;
最后调用父类Loader的PostInstantiate方法.

//*** AbstractEntityLoader.cs 72行 ***
protected void RenderStatement(SqlString condition, string orderBy,
   ISessionFactoryImplementor factory) {

   SqlSelectBuilder sqlBuilder = new SqlSelectBuilder(factory);

   sqlBuilder.SetFromClause(
      persister.FromTableFragment(alias).Append(
         persister.FromJoinFragment(alias, true, true)
      )
   );

   sqlBuilder.AddWhereClause(condition);
   sqlBuilder.SetOrderByClause(orderBy);

   this.sqlString = sqlBuilder.ToSqlString();

   int joins=0; // I added.
   classPersisters = new ILoadable[joins+1];
   classPersisters[joins] = persister;
}
创建一个SqlSelectBuilder对象, 这是一个用于建立select语句的对象, 然后加入form子句、where子句和排序字符串。
注:代码中省略了OuterJoin的处理代码, 关于OuterJoin数据加载, 将在后续文章中单独分析.

//*** SqlSelectBuilder.cs 167行 ***
public SqlString ToSqlString() {

   SqlStringBuilder sqlBuilder = new SqlStringBuilder();

   sqlBuilder.Add("SELECT ")
            .Add(selectClause)
            .Add(" FROM ")
            .Add(fromClause)
            .Add(outerJoinsAfterFrom);

   sqlBuilder.Add(" WHERE ");

   if(whereSqlStrings.Count > 1) {
      sqlBuilder.Add((SqlString[])((ArrayList)whereSqlStrings).ToArray(typeof(SqlString)),
               null, "AND", null, false);
   }
   else {
      sqlBuilder.Add((SqlString)whereSqlStrings[0], null, null, null, false);
   }

   sqlBuilder.Add(outerJoinsAfterWhere);

   if (orderByClause != null && orderByClause.Trim().Length > 0) {
      sqlBuilder.Add(" ORDER BY ")
               .Add(orderByClause);
   }

   return sqlBuilder.ToSqlString();
}
创建一个SqlStringBuilder对象并将SQL关键字和给定的查询信息组装成一个SqlString对象。

//*** CriteriaLoader.cs 55行 ***
public IList List(ISessionImplementor session) {
   ArrayList values = new ArrayList();
   ArrayList types = new ArrayList();

   IEnumerator iter = criteria.IterateExpressions();
   while ( iter.MoveNext() ) {
      Expression.Expression expr = (Expression.Expression) iter.Current;
      TypedValue[] tv = expr.GetTypedValues( session.Factory, criteria.PersistentClass );
      for ( int i=0; i<tv.Length; i++ ) {
         values.Add( tv[i].Value );
         types.Add( tv[i].Type );
      }
   }
   object[] valueArray = values.ToArray();
   IType[] typeArray = (IType[]) types.ToArray(typeof(IType));

   return Find(session, valueArray, typeArray, true, criteria.Selection, null, null);
}
先枚举表达式对象,取得所有表达式中参数的值和类型,然后调用Loader.Find方法。
Loader对象的Find方法是所有数据加载的最终方法,将在后续的文章中单独分析。

 

抱歉!评论已关闭.