2

I have a Linq-Query to get my EF-Data. My query joins 4 tables and selects the result to a denormalized type. I will need this query very often but with different predicates. The List-ExtensionMethods (e.g. .Where() are working with a Func<T,bool> as a parameter and I wanted to do it the same - but I don´t find a way to access my predicate in my Method.

public DenormalizedType GetData(Func<Thing, bool> predicate)
{
    using (var dbContext = new MyDbContext())
    { 
        var myData = (from some in dbContext.Thing 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        // => HowToWhere ???
        select new DenormalizedType()
        {
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        }).ToList();
    }
}

I have 3 questions regarding this issue.

First (obviously): how to invoke my predicate to use a dynamic where-clause?

Second: If my initial idea doesn´t work (because teovankots answer indicates, that my approach isn´t valid for LinqToEntities) is it somehow possible, to make a method of the join only?

Third: What is the best performing approach to return my results to another software-component?

Joshit
  • 997
  • 10
  • 33

3 Answers3

3

EF query provider needs to translate the LINQ query expression tree to SQL, which is not possible when you pass a Func<...> (and more generally, invocation expression like delegate, unknown method etc.).

Shortly, what you ask is not possible with Func<...> type parameters.

The first thing to consider when working with Queryable methods is to use Expression<Func<...>> whenever you would use Func<..> in Enumerable methods. So change your method argument like this:

public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)

Unfortunately using the supplied expression inside LINQ query syntax is not supported out of the box. To do that, you need some expression tree processing library.

The problem and the possible solution is explained in the LINQKit package page. The solution provided by the package is through AsExpandable and Invoke custom extension methods.

Install the nuget package, add

using LinqKit;

to the source code file, and now you can use something like this to achieve the goal:

public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
{
    using (var dbContext = new MyDbContext())
    { 
        var myData = (from some in dbContext.Thing.AsExpandable() // <= 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        where predicate.Invoke(some) // <=
        select new DenormalizedType()
        {
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        }).ToList();
    }
}
Ivan Stoev
  • 159,890
  • 9
  • 211
  • 258
  • oh no... I worked around a lot (check my answer)... Thank you very much for spending time on this! I need to go home now, but I will check this one tomorrow.. You can check my answer if you like, I´d highly appreciate your feedback! – Joshit Jun 15 '17 at 17:32
  • 1
    Well, everything that works is just fine :) The good thing is that once you realized you need expressions, the approaches can be combined - custom `IQueryable` methods where possible, LinqKit in other places (as it's more general). What about sorting by property path, I have my own finding similar to Marc's (reinventing the wheel) in [How to use a string to create a EF order by expression?](https://stackoverflow.com/questions/39908403/how-to-use-a-string-to-create-a-ef-order-by-expression/39916384#39916384) :) Happy coding and learning. – Ivan Stoev Jun 15 '17 at 17:45
  • Thank you very much! Your extensions are able to take the MethodName too? – Joshit Jun 16 '17 at 08:01
  • Unfortunately no. Methods require parsing, so [DynamicLINQ](https://www.nuget.org/packages/System.Linq.Dynamic) package is more appropriate for such scenarios. – Ivan Stoev Jun 16 '17 at 10:08
1

At least I´ve found a way to solve my issue - but I´d highly appreciate hints, and or ways to make it better, because I can´t imagine that this is the holy grail or even close to...

However, the first step is my join, which I return as IQueryable. Important: No using here, because otherwise the dbContext will be disposed, which is not so nice, while working with the IQueryable:

private static MyDbContext _dbContext;
private static IQueryable<DenormalizedType> SelectType()
{
    _dbContext = new MyDbContext();
    var myData = (from some in dbContext.Thing 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        select new DenormalizedType()
        {
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        };
    return myData;
}

I´ve learned a lot today. For example: IEnumerable and IQueryable have both an extension method .Where(). But only IEnumerable.Where() has a Func<T,bool> as a parameter. IQueryable takes a Expression<Func<T,bool>> for its Where(). If I want my query to be executed, with all of my conditions I need to work with the IQueryable-type, as long as all my wheres aren´t executed. So I needed to take a closer look to the Expression-Type. I didn´t understand what all this actually does, but it works ;)

The first thing I had to do, was writing my Where-Methods.. That was pretty easy after I´ve read this one: Entity Framework Filter "Expression<Func<T, bool>>". The Method looks like this:

public static IQueryable<DenormalizedType> SelectWhereCriteria(IQueryable<DenormalizedType> data, Expression<Func<DenormalizedType, bool>> predicate)
{
    return data.Where(predicate);
}

The Expression itself was a little more complicated, because I have a Selection-Enum which should select specified filters. The Expression looks like:

Expression<Func<DenormalizedType, bool>> FilterBySelection(Selection selection)
{
    switch(selection)
    {
        case Selection.Active:
            return x => x.IsActive == true;
        case Selection.InActive:
            return x => x.IsActive == false;
        case Selection.SomeOtherSelection:
            return x => x.SomeOther == "Criteria"
        default:
            return x => true;
    }
}

This Expression works fine on my IQueryable:

var selectedQuery = DataHandler.SelectWhereCriteria(query, FilterBySelection(selection));

The only thing I needed now, was ordering. I found some very cool stuff from MarcGravell (what a genius btw) Dynamic LINQ OrderBy on IEnumerable<T> where he posted some code as an answer, which you can use, to OrderBy PropertyName. His first piece of code takes an IQueryable, orders it by PropertyName (he provides Extensions for Descending OrderyBy as well) and returns an IOrderedQueryable. The ToList()-Operation is the very last operation I execute.

And one more thing: Don´t forget to Dispose the DbContext:

public static void Dispose()
{
    _dbContext.Dispose();
}
Joshit
  • 997
  • 10
  • 33
0

You can call it easily. Install this package, add:

public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
{
    using (var dbContext = new MyDbContext())
    { 
        var myData = (from some in dbContext.Thing 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        where predicate.Invoke(some) //check this
        select new DenormalizedType()
        {
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        }).ToList();
    }
}

You just should know that Func<T1,T2> is a method. With this signature:

T2 predicate(T1 parameter) { /*...*/ }

Your second question depends on how you connect your conponent. But as long as you get DenormalizedType not DbEntity your example looks ok.

teo van kot
  • 12,031
  • 10
  • 37
  • 66
  • That was too easy ;) nevertheless: Thank you!! Why should I avoid returning DbEntity? What is with return of IQueryable? – Joshit Jun 15 '17 at 11:06
  • 1
    *check this* sounds to me like `NotSupportedException` at runtime. – Ivan Stoev Jun 15 '17 at 11:07
  • @Joshit it's a good practice to return `IQueryable` interface. It's not good to return DbEntity becouse you can get problems with serialization in complex cases. – teo van kot Jun 15 '17 at 11:07
  • @IvanStoev as i understand OP using linq-to-entities – teo van kot Jun 15 '17 at 11:08
  • 1
    Indeed - that's exactly what I meant. What you suggest works in L2O, but would generate the aforementioned exception in L2E. – Ivan Stoev Jun 15 '17 at 11:11
  • I was a little to fast with accepting.. @IvanStoev was right (thank you btw), that piece of code throws a NotSupportedException: 'The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.' – Joshit Jun 15 '17 at 11:13
  • @teovankot is it only a good practice with returning the interface or can you provide a little more details about that? – Joshit Jun 15 '17 at 11:14