如何在特定条件下在 LINQ 中的键上使用多个位置

  • 本文关键字:位置 条件下 LINQ c# asp.net linq
  • 更新时间 :
  • 英文 :


我正在处理一个查询,我想在其中检索包含多个 int? 键的数据。 我的输出 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
}

我使用了以下查询

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;

我想要实现的目标:

我想制作一种方法,如果任何属性具有某些值,我想根据该特定属性过滤数据,如果没有任何值,我想根据具有某些值的另一个属性过滤数据。 如果任何属性的值为 0,我想跳过过滤器,因为如果任何属性的值为 0,那么数据将不匹配,并且我不会使用 || 接收任何数据数据未按照所需条件进行过滤。

编辑 1 有三种可能性,要么所有属性都有一些值,一些属性关心值,所有属性关心值。 所需的逻辑应该像第一个在哪里执行,然后另一个应该在更新的值上执行,依此类推......

举个例子,像这样做

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

根据需要添加任意数量的条件。

要按第一个可用的过滤器属性进行过滤,您可以编写(我用filter替换了dto,用d替换了y以使其更清晰):

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;

要按所有可用的过滤器进行筛选,请执行以下操作:

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;

测试(d.CountryId ?? d.IntakeId) == filter.CountryIdd.CountryId是否不为空进行比较,否则d.IntakeIdfilter.CountryId

根据 ?? 和 ??= 运算符(C# 参考):

null 合并运算符 ?? 如果它不为 null,则返回其左侧操作数的值;否则,它将计算右侧操作数并返回其结果。

好吧,如果你有很多像上面这样的属性,并且有多个版本的这种过滤操作,我建议构建一个动态表达式来过滤你的集合。此方法有一个假设:查询的实体和 dto 具有相同的属性名称和相似的类型。

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;
}

小提琴

最新更新