添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
耍酷的枕头  ·  java-class-tools - npm·  1 月前    · 
心软的黑框眼镜  ·  Gali-抖音百科·  2 月前    · 
年轻有为的葫芦  ·  GitHub - ...·  3 月前    · 

What is LINQKit?

LINQKit is a free set of extensions for LINQ to SQL and Entity Framework power users. It comprises the following:

  • An extensible implementation of AsExpandable()
  • A public expression visitor base class ( ExpressionVisitor )
  • PredicateBuilder
  • Linq.Expr and Linq.Func shortcut methods
  • With LINQKit, you can:

  • Plug expressions into EntitySet s and EntityCollection s
  • Use expression variables in subqueries
  • Combine expressions (have one expression call another)
  • Dynamically build predicates
  • Leverage AsExpandable to add your own extensions.
  • AsExpandable is based on a very clever project by Tomas Petricek. ExpressionVisitor comes from a sample by Matt Warren.

    LINQKit includes full source code, and is released under a permissive free license .

    Plugging Expressions into EntitySets / EntityCollections: The Problem

    We'll assume two very simple LINQ to SQL entities: Customer and Purchase , in a one-to-many relationship. (Entity Framework works in exactly the same way in these examples).

    Suppose we wanted to write a method called QueryCustomers that returned the names of all customers who'd made at least one purchase that satisfied a particular criteria.  So, if we wanted all customers who'd made a purchase over $1000, we'd call this hypothetical QueryCustomer method as follows:

    string[] bigSpenders = QueryCustomers (p => p.Price > 1000);

    Here's what the method's signature might look like:

    static string[] QueryCustomers (some-type purchaseCriteria)
    

    Because we're querying a database, some-type must be Expression<Func<>> rather than just Func<>. This ensures our query will end up as an expression tree that LINQ to SQL or Entity Framework can traverse and convert to a SQL statement. In other words:

    static string[] QueryCustomers (Expression<Func<Purchase,bool>> purchaseCriteria)
    

    We chose Expression<Func<Purchase,bool>> because our pluggable criteria will accept a Purchase object and return true or false, depending on whether or not to include that Purchase object. Here's how we might write the whole method:

    static string[] QueryCustomers (Expression<Func<Purchase, bool>> purchaseCriteria)
      var data = new MyDataContext();    // or MyObjectContext()
      var query =
        from c in data.Customers
        where c.Purchases.Any (purchaseCriteria)  // will not compile
        select c.Name;
      return query.ToArray();
    

    But there's a problem: Customer.Purchases is of type EntitySet<> (or EntityCollection<> with EF) neither of which implements IQueryable<>. This means that we can't call Queryable's Any method (the one that accepts an Expression<Func<>>) and our query won't compile!

    It would be a different story if we were querying the Purchases table directly (rather the via the Customer.Purchases association property). The Purchases property is of type Table<Purchase> which implements IQueryable, allowing us to do the following:
    bool any = data.Purchases.Any (purchaseCriteria);

    Of course, we could rewrite QueryCustomers to accept a Func<Purchase,bool> instead:

    static string[] QueryCustomers (Func<Purchase,bool> purchaseCriteria)
    

    Everything would then compile, but LINQ to SQL or Entity Framework would throw an exception because it wouldn't be able to understand what was inside the Func delegate. And fair enough too: the query pipeline would have to disassemble IL code to work around that!

    Plugging Expressions into EntitySets / EntityCollections: The Solution

    Here's how to solve the above problem with LINQKit:

  • Call AsExpandable() on the Table<> object
  • Call Compile() on the expression variable, when used on an EntitySet or EntityCollection.
  • static string[] QueryCustomers (Expression<Func<Purchase, bool>> purchaseCriteria)
      var data = new MyDataContext();
      var query =
        from c in data.Customers.AsExpandable()
        where c.Purchases.Any (purchaseCriteria.Compile())
        select c.Name;
      return query.ToArray();
    

    Compile is an inbuilt method in the Expression class. It converts the Expression<Func<Purchase,bool> into a plain Func<Purchase,bool> which satisfies the compiler. Of course, if this method actually ran, we'd end up with compiled IL code instead of an expression tree, and LINQ to SQL or Entity Framework would throw an exception. But here's the clever part: Compile never actually runs; nor does LINQ to SQL or Entity Framework ever get to see it. The call to Compile gets stripped out entirely by a special wrapper that was created by calling AsExpandable, and substituted for a correct expression tree.

    You can find out more about how AsExpandable works in Tomas Petricek's blog.

    Using Expression Variables in Subqueries

    Suppose we want to write our previous example without using the Customer.Purchases association property. (This might happen in real life if querying an ad-hoc relationship.) To recap, our query is to retrieve the names of all customers who have had made at least one purchase satisfying a particular criteria. Here's how we might proceed:

    static string[] QueryCustomers (Expression<Func<Purchase, bool>> purchaseCriteria)
      var data = new MyDataContext();
      var query =
        from c in data.Customers
        let custPurchases = data.Purchases.Where (p => p.CustomerID == c.ID)
        where custPurchases.Any (purchaseCriteria)
        select c.Name;
      return query.ToArray();
    

    Seem reasonable enough? Entity Framework handles this query without error but LINQ to SQL throws an exception:

        Unsupported overload used for query operator 'Any'.

    The problem is that LINQ to SQL cannot handle references to expressions (such as purchaseCriteria) within subqueries. "But where is the subquery," you might ask! The answer lies in the compiler: C# generates a subquery when it translates the let clause into lambda/method syntax.

    The solution, with LINQKit, is simply to call AsExpandable() on the first table in the query:

    static string[] QueryCustomers (Expression<Func<Purchase, bool>> purchaseCriteria)
      var data = new MyDataContext();
      var query =
        from c in data.Customers.AsExpandable()
        let custPurchases = data.Purchases.Where (p => p.CustomerID == c.ID)
        where custPurchases.Any (purchaseCriteria)
        select c.Name;
      return query.ToArray();
    

    Nothing else needs to be changed. The wrapper that AsExpandable generates looks specifically for references to expressions, and substitutes the expression in place of the reference. Voila!

    Combining Expressions

    The AsExpandable wrapper also lets you write expressions that call other expressions. All you need to do is:

  • Call Invoke to call the inner expression
  • Call Expand on the final result.
  • For example:

    Expression<Func<Purchase,bool>> criteria1 = p => p.Price > 1000;
    Expression<Func<Purchase,bool>> criteria2 = p => criteria1.Invoke (p)
                                                     || p.Description.Contains ("a");
    Console.WriteLine (criteria2.Expand().ToString());

    (Invoke and Expand are extension methods in LINQKit.) Here's the output:

    p => ((p.Price > 1000) || p.Description.Contains("a"))

    Notice that we have a nice clean expression: the call to Invoke has been stripped away.

    If you're using an Invoked expression within a LINQ to SQL or Entity Framework query, and have called AsExpandable on the Table, you can optionally skip step 2. This is because AsExpandable automatically calls Expand on expressions. This means either of the following is valid:

    var query = data.Purchases.AsExpandable().Where (criteria2);
    var query = data.Purchases.Where (criteria2.Expand());

    AsExpandable() works on IQueryable<T>
    Expand() works on Expression<TDelegate>

    The one thing to watch is recursive expressions: these cannot be Expanded! Recursive expressions usually happen by accident when you reuse a variable. It's an easy mistake to make:

    Expression<Func<Purchase,bool>> criteria = p => p.Price > 1000;
    criteria = p => criteria.Invoke (p) || p.Description.Contains ("a");

    That last line recursively calls itself and the original predicate (p.Price>1000) is lost!

    PredicateBuilder

    Click here for information on how to use PredicateBuilder.

    When applying expressions built with PredicateBuilder to an Entity Framework query, remember to call AsExpandable on the first table in the query.

    Licensing

    LINQKit is free. The source code is issued under a permissive free license, which means you can modify it as you please, and incorporate it into your own commercial or non-commercial software.

    As a power user, I appreciate that you'll have your own ideas on how LINQKit should be changed or extended! For this reason, I've kept the source code simple—so it's easy to modify. Enjoy!

    Using LINQKit within LINQPad

    With LINQPad, you can develop your queries much faster than with Visual Studio's build/run/debug cycle. To use LINQKit within LINQPad:

  • Press F4 for the Query Properties dialog
  • Uncheck 'Include PredicateBuilder' (if checked) and add a reference to the LinqKit.Core NuGet package.
  • Download

    The easiest way to download LINQKit is via the LinqKit.Core NuGet package.

    Click here to download the original LinqKit.dll (.NET Framework only)

    Click here to download original source code and demo project.