4

I have following (simplified) classes:

public abstract class BaseSite
{
    public int SiteId { get; set; }
    public string Name { get; set; }
}

public class OptionalSite : BaseSite
{
    public new int? SiteId { get; set; }
}

And the following method:

    public static Expression<Func<T, bool>> PredicateExtension<T>(this IQueryable<T> source, string member, object value, string expression)
    {
        ParameterExpression item = Expression.Parameter(typeof(T), "item");
        Expression memberValue = member.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);
        Type memberType = memberValue.Type;
        if (value != null && value.GetType() != memberType)
            value = Convert.ChangeType(value, memberType);
        Expression condition = null;
        switch (expression)
        {
            case "==":
                condition = Expression.Equal(memberValue, Expression.Constant(value, memberType));
                break;
            case "!=":
                condition = Expression.NotEqual(memberValue, Expression.Constant(value, memberType));
                break;
            case "<":
                condition = Expression.LessThan(memberValue, Expression.Constant(value, memberType));
                break;
            case ">":
                condition = Expression.GreaterThan(memberValue, Expression.Constant(value, memberType));
                break;
            case "<=":
                condition = Expression.LessThanOrEqual(memberValue, Expression.Constant(value, memberType));
                break;
            case ">=":
                condition = Expression.GreaterThanOrEqual(memberValue, Expression.Constant(value, memberType));
                break;
            default:
                break;
        }
        if (condition == null)
            condition = Expression.Equal(memberValue, Expression.Constant(value, memberType));
        var predicate = Expression.Lambda<Func<T, bool>>(condition, item);

        return predicate;
    }

Now when calling the method with the following parameters:

LinqExtentions.PredicateExtension<OptionalSite>(SiteDbSet, "SiteId", 1, "==");

I have the following issue: On the second line of the method there is a Aggregate call but this gives me the AmbiguousMatchException. The reason is that the property SiteIdis defined both in the base class and in the OptionalSite class (public new ...) ...

So the question here is: how can I get the correct Expression using this (or another) method? I will probably need to get the same Expression result but using a different way of getting it so that when properties are found in the base class and the implemented class that I can choose for the property on the class that implements the base class.

EDIT:

The type of SiteId changes from int to int?. Other classes that implement this base class need to have it as a required property (EF) but this class needs it an optional property. Because of this I cannot use the virtual keyword in my base class.

marsze
  • 15,079
  • 5
  • 45
  • 61
lordvlad30
  • 391
  • 1
  • 17
  • Why create a new property, when it can virtual in base and can be overridden in child class – Mrinal Kamboj Oct 31 '18 at 09:57
  • @MrinalKamboj This can not work in my case because the type of the property changes from `int` to `int?`. Thanks – lordvlad30 Oct 31 '18 at 10:10
  • Is it binding for you to keep the same schema, normally it is straight forward to use `int?` in place of `int`, when the value can be null – Mrinal Kamboj Oct 31 '18 at 10:21
  • @MrinalKamboj It is part of my Entity Framwork model, And all my classes are required to have a SiteId but only one class (so far) needs to have it as optional but it still needs to extend from the same class because otherwise all my generic classes will fail for this type. But I am not looking to change my model, I just want another way to get the correct `Expression` ... – lordvlad30 Oct 31 '18 at 10:25

1 Answers1

3

For information on why you get the AmbiguousMatchException and how to solve it, you could look at this answer.

You would have to use a more advanced function:

Expression memberValue = member.Split('.').Aggregate((Expression)item, (expr, name) =>
{
    // get all properties with matching name
    var properties = expr.Type.GetProperties().Where(p => p.Name == name);
    // if only one found, use that, else use the one that is declared in the derived type
    var property = properties.Count() == 1 ? properties.First() : properties.Single(p => p.DeclaringType == expr.Type);
    // make expression from this PropertyInfo
    return Expression.Property(expr, property);
});

Note that is just a basic approach. It doesn't take fields into account (shouldn't be an issue with EF) and there could be several levels of inheritance with the property declared anywhere in-between. But you get the idea.

marsze
  • 15,079
  • 5
  • 45
  • 61
  • Thank you will try this ASAP. I was already aware of the reason, I just did not know how to solve this. – lordvlad30 Oct 31 '18 at 10:46
  • @lordvlad30 Note that is just a basic approach. It doesn't take fields into account (shouldn't be an issue with EF) and there could be several levels of inheritance with the property declared *anywhere* in-between. But you get the idea. – marsze Oct 31 '18 at 10:51
  • Yes I get the idea, I understand how it works. This is the answer I was looking for. Thank you (the code works now, on to the next bug) – lordvlad30 Oct 31 '18 at 10:53