4

說我有,我想和排名應用於查詢實體:我該如何轉換這個linq表達式?

public class Person: Entity 
{ 
    public int Id { get; protected set; } 
    public string Name { get; set; } 
    public DateTime Birthday { get; set; } 
} 

在我的查詢我有以下幾點:

Expression<Func<Person, object>> orderBy = x => x.Name; 

var dbContext = new MyDbContext(); 

var keyword = "term"; 
var startsWithResults = dbContext.People 
    .Where(x => x.Name.StartsWith(keyword)) 
    .Select(x => new { 
     Rank = 1, 
     Entity = x, 
    }); 
var containsResults = dbContext.People 
    .Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id)) 
    .Where(x => x.Name.Contains(keyword)) 
    .Select(x => new { 
     Rank = 2, 
     Entity = x, 
    }); 

var rankedResults = startsWithResults.Concat(containsResults) 
    .OrderBy(x => x.Rank); 

// TODO: apply thenby ordering here based on the orderBy expression above 

dbContext.Dispose(); 

我曾嘗試與選擇匿名對象之前對結果進行排序Rank屬性,但排序結束了迷路。似乎linq實體放棄了單獨集合的排序,並在ConcatUnion期間轉換回自然排序。

我想我也許能夠做的是動態變換的orderBy變量定義從x => x.Namex => x.Entity.Name表達,但我不知道如何:

if (orderBy != null) 
{ 
    var transformedExpression = ??? 
    rankedResults = rankedResults.ThenBy(transformedExpression); 
} 

我如何能夠使用Expression.Lambdax => x.Name換成x => x.Entity.Name?當我將x => x.Entity.Name硬編碼到ThenBy中時,我得到了我想要的順序,但orderBy由查詢的調用類提供,所以我不想將其硬編碼。我在上面的示例中對其進行了硬編碼僅爲解釋的簡單。

+1

刪除我的答案,以便您可以嘗試回收賞金。如果您在授予Aron後會發佈後續評論,我會重新發布我的答案,以防將來幫助任何人。順便說一句,你是否嘗試我的解決方案,如果是的話,它的工作? –

+0

@MikeStrobel謝謝,我真的很感激。您可以繼續並單獨重新發布您的答案,請不要取消刪除您刪除的那個。我仍然在等待賞金,我可以重新授予獎勵。 – danludwig

+0

在這裏使用強類型的佔位符類會節省很多麻煩。 –

回答

4

這應該有所幫助。然而,你將不得不具體化匿名類型才能工作。我的LinqPropertyChain將無法使用它,因爲它很難創建Expression<Func<Anonymous, Person>>,而它仍然是匿名的。

Expression<Func<Person, object>> orderBy = x => x.Name; 

using(var dbContext = new MyDbContext()) 
{ 
var keyword = "term"; 
var startsWithResults = dbContext.People 
    .Where(x => x.Name.StartsWith(keyword)) 
    .Select(x => new { 
     Rank = 1, 
     Entity = x, 
    }); 
var containsResults = dbContext.People 
    .Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id)) 
    .Where(x => x.Name.Contains(keyword)) 
    .Select(x => new { 
     Rank = 2, 
     Entity = x, 
    }); 


var rankedResults = startsWithResults.Concat(containsResults) 
    .OrderBy(x => x.Rank) 
    .ThenBy(LinqPropertyChain.Chain(x => x.Entity, orderBy)); 

// TODO: apply thenby ordering here based on the orderBy expression above 

} 

public static class LinqPropertyChain 
{ 

    public static Expression<Func<TInput, TOutput>> Chain<TInput, TOutput, TIntermediate>(
     Expression<Func<TInput, TIntermediate>> outter, 
     Expression<Func<TIntermediate, TOutput>> inner 
     ) 
    { 

     Console.WriteLine(inner); 
     Console.WriteLine(outter); 
     var visitor = new Visitor(new Dictionary<ParameterExpression, Expression> 
     { 
      {inner.Parameters[0], outter.Body} 
     }); 

     var newBody = visitor.Visit(inner.Body); 
     Console.WriteLine(newBody); 
     return Expression.Lambda<Func<TInput, TOutput>>(newBody, outter.Parameters); 
    } 

    private class Visitor : ExpressionVisitor 
    { 
     private readonly Dictionary<ParameterExpression, Expression> _replacement; 

     public Visitor(Dictionary<ParameterExpression, Expression> replacement) 
     { 
      _replacement = replacement; 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (_replacement.ContainsKey(node)) 
       return _replacement[node]; 
      else 
      { 
       return node; 
      } 
     } 
    } 
} 

想出了一個用較少的明確泛型來做到這一點的方法。

Expression<Func<Person, object>> orderBy = x => x.Name; 
Expression<Func<Foo, Person>> personExpression = x => x.Person; 

var helper = new ExpressionChain(personExpression); 
var chained = helper.Chain(orderBy).Expression; 


// Define other methods and classes here 
public class ExpressionChain<TInput, TOutput> 
{ 
    private readonly Expression<Func<TInput, TOutput>> _expression; 
    public ExpressionChain(Expression<Func<TInput, TOutput>> expression) 
    { 
     _expression = expression; 
    } 

    public Expression<Func<TInput, TOutput>> Expression { get { return _expression; } } 

    public ExpressionChain<TInput, TChained> Chain<TChained> 
     (Expression<Func<TOutput, TChained>> chainedExpression) 
    { 
     var visitor = new Visitor(new Dictionary<ParameterExpression, Expression> 
     { 
      {_expression.Parameters[0], chainedExpression.Body} 
     }); 
     var lambda = Expression.Lambda<Func<TInput, TOutput>>(newBody, outter.Parameters); 
     return new ExpressionChain(lambda); 
    } 

    private class Visitor : ExpressionVisitor 
    { 
     private readonly Dictionary<ParameterExpression, Expression> _replacement; 

     public Visitor(Dictionary<ParameterExpression, Expression> replacement) 
     { 
      _replacement = replacement; 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (_replacement.ContainsKey(node)) 
       return _replacement[node]; 
      else 
      { 
       return node; 
      } 
     } 
    } 
} 
+0

剛剛編輯,因爲我只是寫了一些測試... – Aron

+0

真棒,這絕對有效。除了具體化匿名類型外,我還必須明確說明鏈式方法的泛型參數。它只有當我做了'.ThenBy(LinqPropertyChain.Chain (x => x.Entity,orderBy))' – danludwig

1

既然你通過Rank排序第一,和Rank值各序列中是相同的,你應該能夠只是有點獨立,然後串聯。這聽起來像是在這裏打嗝,根據你的帖子,實體框架並沒有維持整理ConcatUnion操作。您應該能夠解決這個問題通過強制級聯發生客戶端:

var rankedResults = startsWithResults.OrderBy(orderBy) 
            .AsEnumerable() 
            .Concat(containsResults.OrderBy(orderBy)); 

這應該使Rank財產引起不必要的和可能簡化對數據庫執行的SQL查詢,它不需要用表情樹咕mu。

不足之處在於,一旦您調用AsEnumerable(),就不再有選擇額外添加數據庫操作的選項(即,如果您在Concat之後鏈接其他LINQ操作符,則它們將使用LINQ-to-collections實現) 。看看你的代碼,我認爲這不會對你造成問題,但值得一提的是。

+0

爲了記錄,我知道我可以通過點擊數據庫,然後分析結果。在排序之後還有額外的數據庫端操作 - 即分頁('.Skip'和'.Take'),所以afaik,在敲db之前我必須使用表達式樹。 – danludwig