2016-12-19 7 views
1

.NET Framework 4.0のためServiceStack.OrmLite v4.0.62を使用しています。私は検索機能を実行する必要があります。私のPOCOクラスは階層構造が深いので、ネストしたサブクエリでLINQクエリを使用する必要があります。例えば:サブクエリがあるとServiceStack.OrmLiteが失敗する

// some code is omitted 
Expression<Func<Person,bool>> condition = p => p.Name.Contains("John") && 
               p.Addreses.Any(adr => adr.City.Contains("Moscow") && 
                    adr.Street.Contains("Yuri Gagarin") && 
                    adr.Building == 23); // and so on... 

// some code is omitted 

// Gets the quantity of records were found 
public int GetCountBy(Expression<Func<T,bool>> condition) 
{ 
    // here it throws an Exception 
    return db.Persons.Count(condition); 
} 

そして今、それは例外がスローされます。我々は、パラメータとして式をサポートする別の方法を使用する場合

variable 'person' of type 'Reestr.DAL.Entities.Person' referenced from scope '', but it is not defined 

同じ例外がスローされます。 私の質問は次のとおりです: どうすれば問題を解決できますか? OrmLiteはこのようなクエリをサポートしていますか?

+0

私の質問は理解しやすくて簡単ですか? – Dmitry

答えて

0

私が選んだOrmLiteバージョンには、このような可能性はありません。しかし、OrmLiteがSqlクラスを使って表現されていれば、内部lambdaをサポートしていることが分かりました。また、我々はまた、住所の一部のネストされたラムダを追加することができます

Expression<Func<Person,bool>> condition = p => p.Name.Contains("John") && 
               Sql.In(p.Id, 
                 connection.From<Address>() 
                   .Where(adr => adr.City.Contains("Moscow") && 
                     adr.Street.Contains("Yuri Gagarin") && 
                     adr.Building == 23) 
                   .SelectDistinct(adr => adr.PersonId)); 

そして、それが正常に動作します):私たちは、このような方法で、私たちの前の例を書き換えることができます。だから私はラムダを再マップするためにExpressionVisitorクラスを実装することに決めました。まず、私は標準IRepositoryインタフェースの一般的な実装を継承し、私のPersonRepositoryクラス表示したいすべての:

using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 
using Reestr.DAL.DB; 
using Reestr.DAL.Entities; 
using Reestr.DAL.Helpers; 
using ServiceStack.OrmLite; 

namespace Reestr.DAL.Repositories 
{ 
    internal class PersonRepository : Repository<Person> 
    { 
     private ReestrContext db; 
     private readonly MethodFinder finder; 

     /// <summary> 
     /// Инициализирует новый экземпляр класса <see cref="T:System.Object"/>. 
     /// </summary> 
     public PersonRepository(ReestrContext db) : base(db) 
     { 
      this.db = db; 
      finder = new MethodFinder(db); 
     } 

     public override Person GetById(long id) 
     { 
      Person p = base.GetById(id); 
      List<Dic> dics = db.Connection.Select<Dic>(); 
      foreach (Subject subject in p.Subjects) 
      { 
       subject.Parent = dics.FirstOrDefault(dic => dic.DicCode == subject.ParentDicCode); 
       subject.Child = dics.FirstOrDefault(dic => dic.DicCode == subject.ChildDicCode); 
      } 
      return p; 
     } 

     public override long CountByCondition(Expression<System.Func<Person, bool>> predicate) 
     { 
      ResolveNestedConditions(predicate); 
      if (finder.IsNestedPocoExist) 
      { 
       return base.CountByCondition(finder.GetRegeneratedPredicate<Person>()); 
      } 
      return base.CountByCondition(predicate); 
     } 

     public override IQueryable<Person> GetByCondition(Expression<System.Func<Person, bool>> predicate) 
     { 
      ResolveNestedConditions(predicate); 
      if (finder.IsNestedPocoExist) 
      { 
       return base.GetByCondition(finder.GetRegeneratedPredicate<Person>()); 
      } 
      return base.GetByCondition(predicate); 
     } 

     public override IQueryable<Person> GetByCondition(Expression<System.Func<Person, bool>> predicate, int rows) 
     { 
      ResolveNestedConditions(predicate); 
      if (finder.IsNestedPocoExist) 
      { 
       return base.GetByCondition(finder.GetRegeneratedPredicate<Person>(), rows); 
      } 
      return base.GetByCondition(predicate, rows); 
     } 

     public override IQueryable<Person> GetByCondition(Expression<System.Func<Person, bool>> predicate, int? skip, int? rows) 
     { 
      ResolveNestedConditions(predicate); 
      if (finder.IsNestedPocoExist) 
      { 
       return base.GetByCondition(finder.GetRegeneratedPredicate<Person>(), skip, rows); 
      } 
      return base.GetByCondition(predicate, skip, rows); 
     } 

     private void ResolveNestedConditions(Expression<System.Func<Person, bool>> predicate) 
     { 
      finder.DoVisiting(predicate); 
     } 
    } 
} 

を再マッピングする責任があるクラス:

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
using Reestr.DAL.DB; 
using Reestr.DAL.DataAnnotations; 
using Reestr.DAL.Entities; 
using ServiceStack.DataAnnotations; 
using ServiceStack.OrmLite; 

namespace Reestr.DAL.Helpers 
{ 
    /// <summary> 
    /// Класс для обработки входящего предиката на предмет генерации новых предикатов и выделения вложенных предикатов 
    /// </summary> 
    internal class MethodFinder : ExpressionVisitor 
    { 
     private Expression reGenNode; 
     // not best solution to put into this class this object...but...crutch variant 
     private ReestrContext db; 

     public MethodFinder(ReestrContext _db) 
     { 
      db = _db; 
      IsNestedPocoExist = false; 
     } 

     public void DoVisiting(Expression node) 
     { 
      reGenNode = Visit(node); 
     } 

     /// <summary> 
     /// Получает значение указывающее, что есть вложенные POCO объекты 
     /// </summary> 
     public bool IsNestedPocoExist { get; private set; } 

     /// <summary> 
     /// Получает новосгенерированный предикат, без использования вложенных предикатов 
     /// </summary> 
     /// <returns></returns> 
     public Expression<Func<T,bool>> GetRegeneratedPredicate<T>() 
     { 
      LambdaExpression le = reGenNode as LambdaExpression; 
      return Expression.Lambda<Func<T, bool>>(le.Body, le.Parameters); 
     } 

     /// <summary> 
     /// Просматривает дочерний элемент выражения <see cref="T:System.Linq.Expressions.MethodCallExpression"/>. 
     /// </summary> 
     /// <returns> 
     /// Измененное выражение в случае изменения самого выражения или любого его подвыражения; в противном случае возвращается исходное выражение. 
     /// </returns> 
     /// <param name="node">Выражение, которое необходимо просмотреть.</param> 
     protected override Expression VisitMethodCall(MethodCallExpression node) 
     { 
      // статический метод расширения, возвращающий bool 
      if (node.Object == null && node.Method.IsGenericMethod && node.Method.ReturnType == typeof (bool)) 
      { 
       var member = node.Arguments.FirstOrDefault(expression => expression.NodeType == ExpressionType.MemberAccess) as MemberExpression; 
       if (member != null) 
       { 
        var ePrimary = GenPrimaryKeyProperty(member); 
        // получаем вложенную лямбду с рекурсивным проходом 
        foreach (LambdaExpression lambda in node.Arguments.Where(expression => expression.NodeType == ExpressionType.Lambda)) 
        { 
         if (lambda.Parameters[0].Type == typeof(Subject)) 
         { 
          return GenerateSqlInExpression(ePrimary, lambda, GetFieldLambda<Subject>()); 
         } 
         if (lambda.Parameters[0].Type == typeof(Photo)) 
         { 
          return GenerateSqlInExpression(ePrimary, lambda, GetFieldLambda<Photo>()); 
         } 
         if (lambda.Parameters[0].Type == typeof(Dic)) 
         { 
          return GenerateSqlInExpression(ePrimary, lambda, GetFieldLambda<Dic>()); 
         } 
        } 
       } 
       // "Вырезаем" вызов метода и заменяем на простейшее условие 1 == 1 
       //return Expression.Equal(Expression.Constant(1), Expression.Constant(1)); 
      } 

      // обращение к вложенному POCO типу 
      MemberExpression me = node.Object as MemberExpression; 
      if (me != null) 
      { 
       LambdaExpression lambda = GenLambdaFromMethod(node); 
       if (lambda != null) 
       { 
        Type tExpr = GetInnerPocoExpressionType(me); 
        if (tExpr == typeof(Subject)) 
        { 
         return GenerateSqlInExpression(GenPrimaryKeyProperty(me), lambda, GetFieldLambda<Subject>()); 
        } 
        if (tExpr == typeof(Photo)) 
        { 
         return GenerateSqlInExpression(GenPrimaryKeyProperty(me), lambda, GetFieldLambda<Photo>()); 
        } 
        if (tExpr == typeof(Dic)) 
        { 
         return GenerateSqlInExpression(GenPrimaryKeyProperty(me), lambda, GetFieldLambda<Dic>()); 
        } 
       } 
      } 
      Visit(node.Arguments); 
      return node; 
     } 

     /// <summary> 
     /// Получает лямбду из свойства пользовательского типа внутреннего объекта 
     /// </summary> 
     /// <param name="method"></param> 
     /// <returns></returns> 
     private LambdaExpression GenLambdaFromMethod(MethodCallExpression method) 
     { 
      MemberExpression me = method.Object as MemberExpression; 
      Type tExpr = GetInnerPocoExpressionType(me); 
      ParameterExpression pe = Expression.Parameter(me.Expression.Type, me.Expression.Type.Name.ToLower()); 
      Expression property = Expression.Property(pe, pe.Type.GetProperties().First(pi => pi.Name == me.Member.Name)); 
      if (tExpr == typeof (Subject) || 
       tExpr == typeof(Photo) || 
       tExpr == typeof(Dic)) 
      { 
       Expression regenMember = Expression.Call(property, method.Method, method.Arguments); 
       LambdaExpression le = Expression.Lambda(regenMember, pe); 
       return le; 
      } 
      return null; 
     } 

     /// <summary> 
     /// Получает корневой тип объекта 
     /// </summary> 
     /// <param name="me"></param> 
     /// <returns></returns> 
     private Type GetInnerPocoExpressionType(MemberExpression me) 
     { 
      MemberExpression meNested = me.Expression as MemberExpression; 
      if (meNested == null) 
      { 
       return me.Type; 
      } 
      return GetInnerPocoExpressionType(meNested); 
     } 

     /// <summary> 
     /// Получает последний вложенный POCO объект, например {person} или {dic}, если является свойством POCO класса 
     /// </summary> 
     /// <param name="me"></param> 
     /// <returns></returns> 
     private Expression GetInnerPocoExpression(MemberExpression me) 
     { 
      MemberExpression meNested = me.Expression as MemberExpression; 
      if (meNested == null) 
      { 
       return me.Expression; 
      } 
      return GetInnerPocoExpression(meNested); 
     } 

     /// <summary> 
     /// Получаем свойство класса, которое является POCO классом по внешнему ключу например для Person: p => p.Id 
     /// </summary> 
     /// <param name="me"></param> 
     /// <returns></returns> 
     private MemberExpression GenPrimaryKeyProperty(MemberExpression me) 
     { 
      // POCO выражение 
      var mConverted = GetInnerPocoExpression(me); 
      // берем свойство с атрибутом PrimaryKey или костыль для типа Subject 
      // для предмета у нас не id используется а ChildDicCode (PCode), который является ключом к DicCode таблицы Dic 
      var primaryProperty = mConverted.Type.GetProperties() 
       .FirstOrDefault(info => info.GetCustomAttributes(mConverted.Type == typeof(Subject) ? typeof(CustomPrimaryKeyAttribute) : typeof(PrimaryKeyAttribute), false).Any()); 
      // формируем обращение к свойству, используя уже имеющийся параметр 
      var ePrimary = Expression.Property(mConverted, mConverted.Type.GetProperty(primaryProperty.Name, primaryProperty.PropertyType)); 
      return ePrimary; 
     } 

     /// <summary> 
     /// Возвращает сгенерированную лямбду по внешнему ключу например для Subject: subj => subj.PersonId 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <returns></returns> 
     private Expression<Func<T, object>> GetFieldLambda<T>() 
     { 
      ParameterExpression pePoco = Expression.Parameter(typeof (T), typeof (T).Name.ToLower()); 
      var referProperty = 
       typeof (T).GetProperties() 
          .FirstOrDefault(
           info => info.GetCustomAttributes(typeof (CustomForeignKeyAttribute), false).Any()); 
      var eReference = Expression.Property(pePoco, 
               typeof (T).GetProperty(referProperty.Name, referProperty.PropertyType)); 
      return Expression.Lambda<Func<T, object>>(Expression.Convert(eReference, typeof (object)), pePoco); 
     } 

     /// <summary> 
     /// Возвращает перегенерированную лямбду в виде обращения к статическому методу класса Sql: Sql.In([nested POCO lambda]) 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="primary">обращение к свойству POCO объекта</param> 
     /// <param name="whereL">сгенерированная лямбда работы с внутренним обїектом</param> 
     /// <param name="field">лямба доступа к полю внешнего ключа объекта</param> 
     /// <returns></returns> 
     private Expression GenerateSqlInExpression<T>(MemberExpression primary, LambdaExpression whereL, Expression<Func<T,object>> field) 
     { 
      IsNestedPocoExist = true; 
      // ищём вложенные лямбды и если нашли добавляем в коллекцию классов новосозданный класс 
      var finder = new MethodFinder(db); 
      var rebuiltE = finder.Visit(whereL); 
      Expression<Func<T, bool>> inWhere = Expression.Lambda<Func<T, bool>>((rebuiltE as LambdaExpression).Body, whereL.Parameters); 
      SqlExpression<T> eSqlbody = db.Connection.From<T>().Where(inWhere).SelectDistinct(field); 
      MethodInfo mIn = typeof (Sql).GetMethods() 
             .Where(mi => mi.Name == "In") 
             .First(mi => mi.GetParameters().Any(pi => pi.ParameterType.Name == typeof (SqlExpression<>).Name)) 
             .MakeGenericMethod(primary.Type, typeof(T)); 
      return Expression.Call(mIn, primary, Expression.Constant(eSqlbody)); 
     } 
    } 
} 

だから、また、私は2つの属性を作成しましたあなたが上のコードで見ることができたのは、CustomPrimaryKeyAttributeCustomForeignKeyAttributeです。それらは空のクラスで、標準でないプロパティやフィールドに使用されます OrmLiteはキーの意味を持っています(OrmLiteはカスタム外部キーやカスタムプライマリキーをサポートしていないため、名前のコンベンションやIDに従わないためです)。 すべて正常に動作します。もちろん、私はこれが最高の解決策ではないことを理解しています。私はそれが欲しいほどエレガントではありません。私はあなたがこの解決策を見て、私が何を改善したり、いくつかのアドバイスをくれたら教えてください。私は彼らからあなたを聞いてうれしいでしょう。

関連する問題