Home > Mobile >  How can I use multiple where on keys in LINQ with certain condition
How can I use multiple where on keys in LINQ with certain condition

Time:08-31

I am working on a query where I want to retrieve data including multiple int? keys. My output Dto:

 public class FilterParamsDto {
    public int? StudentId { get; set; }
    public int? NationalityId { get; set; }
    public int? CountryId { get; set; }
    public int? SchoolCountryId { get; set; }
    public int? SchoolStateId { get; set; }
    public int? SchoolCityId { get; set; }

    .... More keys
}

I used following queries

var value = from y in data
            where dto.CountryId == null
                ? y.CountryId != null
                : y.IntakeId == dto.CountryId && dto.StudentId == null
                    ? y.StudentId != null
                    : y.StudentId == dto.StudentId && dto.SchoolCityId == null
                        ? y.SchoolCityId != null
                        : y.SchoolCityId == dto.SchoolCityId
            select y;

What I want to Achieve:

I want to make a method where if any property have some value I want to filter data based on that particular property and if there is not any value I want to filter data based on another properties who do have some value. if any property have 0 value I want to skip filter because if any property have 0 value so the data wont' match and i am not going to receive any data using || the data is not getting filtered as per required condition.

EDIT 1 There are three possibilities either all properties have some values, some properties caring values, all the properties caring values. the required logic should be like if first where executed then another where should be executed on updated values and so on...

CodePudding user response:

To give you just one example, do it like this

.Where(y => (dto.CountryId == null || y.CountryId == dto.CountryId))

Add as many conditions as you want.

CodePudding user response:

To filter by the first available filter property, you can write (I replaced dto by filter and y by d to make it clearer):

var value = from d in data
   where
      filter.CountryId != null && (d.CountryId ?? d.IntakeId) == filter.CountryId ||
      filter.StudentId != null && d.StudentId == filter.StudentId ||
      filter.SchoolCityId != null && d.SchoolCityId == filter.SchoolCityId
   select d;

To filter by all available filters:

var value = from d in data
   where
     (filter.CountryId == null || (d.CountryId ?? d.IntakeId) == filter.CountryId) &&
     (filter.StudentId == null || d.StudentId == filter.StudentId) &&
     (filter.SchoolCityId == null || d.SchoolCityId == filter.SchoolCityId)
   select d;

The test (d.CountryId ?? d.IntakeId) == filter.CountryId compares d.CountryId if it is not null and otherwise d.IntakeId with filter.CountryId.

According to the documentation of ?? and ??= operators (C# reference):

The null-coalescing operator ?? returns the value of its left-hand operand if it isn't null; otherwise, it evaluates the right-hand operand and returns its result.

CodePudding user response:

Well, if you have many properties like the above and have multiple versions of this kind of filter operation I would suggest building a dynamic expression to filter your collections. This approach has one assumption: the entity that is queried and the dto has the same property names and similar types.

 public static void Main()
    {
        var dto = new FilterParamsDto { CountryId = 3, StudentId = 5 };
        var data = new List<Dummy> { new() { CountryId = 3, StudentId = 1 }, new() { CountryId = 4, StudentId = 5 }, new() { CountryId = 1, StudentId = 2 }, new() { CountryId = 3, StudentId = 5 } };
        var expAnd = GenerateFilterExpression<Dummy, FilterParamsDto>(dto);
        var expOr = GenerateFilterExpression<Dummy, FilterParamsDto>(dto, false);

        var filteredWithAnd = data.AsQueryable().Where(expAnd).ToArray();
        var filteredWithOr = data.AsQueryable().Where(expOr).ToArray();
    }

    public static PropertyInfo[] GetNonNullProperties<T>(T item) where T : class
    {
        var properties = typeof(T)
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(r => r.GetValue(item) != null)
            .Select(r => r).ToArray();

        return properties;
    }

    public static Expression<Func<T, bool>> GenerateFilterExpression<T, R>(R dto, bool and = true) where T : class where R : class
    {
        var p = Expression.Parameter(typeof(T), "p");
        var nonnullProps = GetNonNullProperties(dto);
        var constExp = Expression.Constant(dto);

        // here we decide how to join conditions
        Func<Expression, Expression, BinaryExpression> operatorExp = and ? Expression.AndAlso : Expression.OrElse;
        Expression? exp = null;

        var sourceType = typeof(T);

        foreach (var item in nonnullProps)
        {
            var sourceProp = sourceType.GetProperty(item.Name);
            var prop = Expression.Property(p, sourceProp!);
            Expression dtoProp = Expression.Property(constExp, item);

            // we need this trick otherwise we will have runtime error that says you can not have an expression like : int? == int
            if (sourceProp!.PropertyType != item.PropertyType)
            {
                var underlyingType = Nullable.GetUnderlyingType(item.PropertyType);
                dtoProp = Expression.Convert(dtoProp, underlyingType!);
            }

            if (exp == null)
            {
                exp = Expression.Equal(prop, dtoProp);
            }
            else
            {
                exp = operatorExp(exp, Expression.Equal(prop, dtoProp));
            }
        }

        var result = Expression.Lambda<Func<T, bool>>(exp!, p);

        return result;
    }

Fiddle

  • Related