Из динамического столбца пользовательского интерфейса поступают в качестве параметра в API и на основе параметра я должен получать данные из базы данных. Пример. В приведенном ниже коде на основе столбца выполняется условие linq-запроса. Теперь я хочу сделать его универсальным, чтобы он служил, если в будущем появится новое условие столбца.
public List<string> GetFilteredTypeAhead(string searchText,string searchForRole,int fiscalyear,int fiscalPeriod)
{
if (searchForRole == "column1")
{
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column2")
{
var accounts = (from a in _context.Account
where a.column2.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column2 by a.column2 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column3")
{
var accounts = (from a in _context.Account
where a.column3.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear
group a.column3 by a.column3 into g
select g.Key).ToList();
return accounts;
}
else if (searchForRole == "column4")
{
var accounts = (from a in _context.Account
where a.column4.StartsWith(searchText) && a.FiscalPeriod.Equals(fiscalPeriod) && a.FiscalYear.Equals(fiscalyear)
group a.column4 by a.column4 into g
select g.Key).ToList();
return accounts;
}
else
{
return new List<string>();
}
}
Чтобы преобразовать его в универсальный. Я создал дерево выражений.
static IQueryable<T> ConvertToExpression<T>(IQueryable<T> query, string propertyValue, PropertyInfo propertyInfo, int fiscalyear, int fiscalPeriod)
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(propertyValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
PropertyInfo propertyInfoFiscalPeriod = typeof(T).GetProperty("FiscalPeriod");
MemberExpression memberPropertyFiscalPeriod = Expression.Property(e, propertyInfoFiscalPeriod);
ConstantExpression right = Expression.Constant(fiscalPeriod);
Expression equalsFiscalPeriod = Expression.Equal(memberPropertyFiscalPeriod, Expression.Convert(right, typeof(Int16)));
PropertyInfo propertyInfoFiscalYear = typeof(T).GetProperty("FiscalYear");
MemberExpression memberPropertyFiscalYear = Expression.Property(e, propertyInfoFiscalYear);
right = Expression.Constant(fiscalyear);
Expression equalsFiscalYear = Expression.Equal(memberPropertyFiscalYear, Expression.Convert(right, typeof(Int16)));
Expression combineExpression = Expression.And(equalsFiscalPeriod, equalsFiscalYear);
Expression predicateBody = Expression.And(call, combineExpression);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(predicateBody, e);
return query.Where(lambda);
}
И чтобы назвать это, я использовал код, как показано ниже «searchForRole» входит в качестве параметра в «column1», «column2» и т. д.
PropertyInfo propertyInfo = typeof(Account).GetProperty(searchForRole);
IQueryable<Account> query = _context.Account;
query = ConvertToExpression(query, searchText, propertyInfo,fiscalyear,fiscalPeriod);
var list = query.ToList();
Теперь это работает нормально, но в результате дублируются записи. Я хотел иметь какой-то отдельный или сгруппированный по столбцу переданных параметров. Простыми словами я хотел удалить условие if и сделать мой метод поиска универсальным. Пожалуйста помоги.
Это возможно, но ИМХО лучше свести динамические части к минимуму и максимально использовать безопасность времени компиляции С#.
Рассматриваемый пример запроса
var accounts = (from a in _context.Account
where a.column1.StartsWith(searchText) && a.FiscalPeriod == fiscalPeriod && a.FiscalYear ==fiscalyear
group a.column1 by a.column1 into g
select g.Key).ToList();
можно переписать следующим образом
var accounts = _context.Account
.Where(a => a.FiscalPeriod == fiscalPeriod && a.FiscalYear == fiscalyear)
.Select(a => a.column1)
.Where(c => c.StartsWith(searchText))
.Distinct()
.ToList();
Как видите, единственная динамическая часть — это a => a.column1
типа Expression<Func<Account, string>>
. Итак, все, что вам нужно, это такой метод:
static Expression<Func<T, M>> MemberSelector<T>(string name)
{
var parameter = Expression.Parameter(typeof(T), "e");
var body = Expression.PropertyOrField(name);
return Expression.Lambda<Func<T, M>>(body, parameter);
}
и заменить
.Select(a => a.column1)
с участием
.Select(MemberSelector<Account, string>(searchForRole))
Спасибо. Он выглядит работающим. Просто хотел проверить тип возврата func "M" в выражении - Expression<Func<T, M>> , можем ли мы преобразовать его в пользовательский возвращаемый DTO, например ``` public class FilterDto { public string Value { get; задавать; } открытый текст строки { получить; задавать; } }``` и затем преобразовать в toList()
Такие требования лучше предоставлять заранее, потому что они могут потребовать совершенно другого решения. Непонятно, что относится к Value
, а что к Text
свойству DTO и т. д. Также ограничено место для комментариев. Подумайте о том, чтобы опубликовать еще один вопрос, включая именно то, что вам нужно. Ваше здоровье.
Лучший способ динамически запрашивать — использовать библиотеку System.Linq.Dynamic, которую вы можете найти в Nuget.