0

I'm trying to construct an expression that ultimately results in a query like

SELECT p.*
FROM MyEntity p
WHERE EXISTS(SELECT * 
             FROM filters
             WHERE (filter.type = 1 
                    AND filter.objectid = p.id 
                    AND filter.value = 1
                     OR filter.type = 1 
                    AND filter.objectid = p.id 
                    AND filter.value = 2))
  AND EXISTS(...)

Obviously it won't look exactly like that, but that's the general idea. I'm using PredicateBuilder to build the query based on the filters passed in, so I have something like this:

var query = context.Set<MyEntity>().AsExpandable();

var predicate = PredicateBuilder.New<MyEntity>(true);

//loop through the group filters.  The filters in a group have an or relationship
foreach (FilterGroup group in filters)
{
    predicate = predicate.And(
                p => context.Set<FilteringValue>().AsExpandable().Any(getFilteringPredicate(p,group )) 
    );
}

return query.Where(predicate);

And the getFilteringPredicateMethod:

Expression<Func<FilteringValue,bool>> getFilteringPredicate(MyEntity p, FilterGroup filters) {

     var fPredicate = PredicateBuilder.New<FilteringValue>(true);
     foreach(var filter in filters.FilterList)
     {
         fPredicate= fPredicate.Or(fv => fv.objectid == p.Id && fv.Type== 1 && fv.value == filter.Value);
     }

     return fPredicate
}

This seems relatively simple, however I'm getting the error

variable 'p' of type 'Models.MyEntity' referenced from scope '', but it is not defined.

Is there no way to pass the product object into the getFilteringPredicate() method? MyEntity and Filter are not related in Entity Framework.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
maembe
  • 1,270
  • 1
  • 13
  • 25
  • Not a complete answer, but you should be initializing the predicate builder `fPredicate` in `getFilteringPredicate` to false when using `Or`s. `var fPredicate = PredicateBuilder.New(true);` should be `var fPredicate = PredicateBuilder.New(false);` – Robert McKee Dec 09 '16 at 18:00
  • Also `MyEntity and Filter are not related in Entity Framework.` -- they probably should be related. – Robert McKee Dec 09 '16 at 18:08
  • I will analyze this in more detail, my initial testing looked fine and I thought I had the answer, but it is not so. I'll give it a sencond shot and post an answer – Andrés Robinet Dec 12 '16 at 01:15
  • Can you please post "at least in part" how your `MyEntity`, `FilterGroup` and `FilterValue` look like? Are they all entities mapped in the EF model? – Andrés Robinet Dec 12 '16 at 02:30
  • 1
    Please, see my answer, I thought of a much better approach by using joins (yes, Linq and EF support them), but don't really have the time right now. If my answer works for you, let me know and I'll post the alternative approach (I love Linq, Expressions, and LinqKit btw :)) – Andrés Robinet Dec 12 '16 at 06:21

1 Answers1

0

So... I think I finally got it, you want to relate two expression parameters and build up a composite query (what I mean by the informal definition of "composite" is a subquery having a reference to the main query parameter(s)):

Unfortunately, LinqKit does not support multi-parameter expressions 'AFAIK', which is something that would be a perfect match for your case:

Well, anyway... here it goes. By the way FilteringValues and MyEntities are just two DbSets, I just happen to be using LinqPad to test this out ATM (Questions?):

void Main(string[] args)
{
    var entityQuery = MyEntities.AsExpandable();

    var filterGroups = GetFilterGroups();

    // Initialize with TRUE since no group filter implies Everything matches
    var predicate = PredicateBuilder.New<MyEntity>(true);

    var filteringValueQuery = FilteringValues.AsExpandable();

    foreach (var g in filterGroups)
    {
        if (!g.FilterList.Any())
        {
            // If we have no filters in the group, skip
            continue;
        }
        var expressionForGroupFilters = BuildExpressionForGroupFilters(g.FilterList);
        predicate = predicate.And(entity => filteringValueQuery.Any(filteringValue => expressionForGroupFilters.Invoke(entity, filteringValue)));
    }

    entityQuery = entityQuery.Where(predicate);

    var data = entityQuery.ToList();

    data.Dump();
}

public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForSingleFilter(Filter groupFilter)
{
    var value = groupFilter.Value;
    return (entity, filteringValue) =>
        filteringValue.Type == 1
        && filteringValue.ObjectId == entity.Id
        && filteringValue.Value == value;
}

public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForGroupFilters(IReadOnlyCollection<Filter> groupFilters)
{
    Expression<Func<MyEntity, FilteringValue, bool>> result = null;

    foreach (var groupFilter in groupFilters)
    {
        var expression = BuildExpressionForSingleFilter(groupFilter);
        if (result == null)
        {
            result = expression;
            continue;
        }

        var tempResult = result.Expand();
        result = (entity, filteringValue) => tempResult.Invoke(entity, filteringValue) || expression.Invoke(entity, filteringValue);
    }

    return result.Expand();
}

public static FilterGroup CreateFilterGroupWithValues(params int[] values)
{
    var filterList = values
        .Select(x => new Filter { Value = x })
        .ToList();

    return new FilterGroup { FilterList = filterList };
}

public static IEnumerable<FilterGroup> GetFilterGroups()
{
    return new[] {CreateFilterGroupWithValues(0, 2, 4), CreateFilterGroupWithValues(1)};
}

public class Filter
{
    public int Value { get; set; }
}

public class FilterGroup
{
    public FilterGroup()
    {
        FilterList = new List<Filter>();
    }

    public List<Filter> FilterList { get; set; }
}
Andrés Robinet
  • 1,527
  • 12
  • 18