Ja zrobiłem sobie do tego kiedyś taki mechanizm:
public enum OrderDirection
{
NoOrder,
Ascending,
Descending
}
public class OrderInfo
{
/// <summary>
/// Direction of order. NoOrder means that no ordering is done.
/// </summary>
public OrderDirection Direction { get; set; }
/// <summary>
/// Ordering field
/// </summary>
public string FieldName { get; set; }
}
/// <summary>
/// Use this class to filter through WebAPI.
/// </summary>
/// <typeparam name="T">Model that is going to be filtered</typeparam>
public class QueryFilter<T> where T: class
{
/// <summary>
/// "Where" condition. This is lambda expression.
/// </summary>
public Expression<Func<T, bool>> Where { get; set; }
/// <summary>
/// Order by
/// </summary>
public OrderInfo OrderBy { get; set; }
/// <summary>
/// How many records should be skipped. Mainly for pagination
/// </summary>
public int Skip { get; set; }
/// <summary>
/// How many records should be retrieved. Mainly for pagination
/// </summary>
public int Max { get; set; }
public QueryFilter()
{
OrderBy = new OrderInfo();
OrderBy.Direction = OrderDirection.NoOrder;
}
public static QueryFilter<T> FromDto(QueryFilterDto dto, Expression<Func<T, bool>> where)
{
QueryFilter<T> result = new QueryFilter<T>();
result.Max = dto.Max;
result.OrderBy = dto.OrderBy;
result.Skip = dto.Skip;
result.Where = where;
return result;
}
public void CreateWhere(Expression<Func<T, bool>> where)
{
Where = where;
}
public void CreateOrder(OrderDirection direction, string fieldName)
{
OrderBy.Direction = direction;
OrderBy.FieldName = fieldName;
}
}
/// <summary>
/// This is helper class that is send between client and WebApi. Use generic QueryFilter instead
/// </summary>
public class QueryFilterDto
{
public string ModelName { get; set; }
public string Where { get; set; }
public OrderInfo OrderBy { get; set; }
public int Skip { get; set; }
public int Max { get; set; }
}
Potem mogę sobie pobrać listę wartości np. tak - po stronie klienta:
public virtual async Task<ListResponse<T>> GetList(QueryFilter<T> filter)
{
QueryFilterDto filterDto = new QueryFilterDto();
filterDto.Max = filter.Max;
filterDto.OrderBy = filter.OrderBy;
filterDto.Skip = filter.Skip;
filterDto.Where = filter.Where.ToString();
filterDto.ModelName = typeof(T).Name;
var response = await client.PostJsonAsync(filter, GetListUri);
if (!response.IsSuccessStatusCode)
return await CreateListResponse(response, null);
string content = await response.Content.ReadAsStringAsync();
var items = JsonConvert.DeserializeObject<IList<T>>(content);
return await CreateListResponse(response, items);
}
A po stronie serwera jest coś takiego:
kontroler:
[HttpPost]
public async Task<IActionResult> Get([FromBody]QueryFilterDto filterDto)
{
Expression<Func<Order, bool>> where = await Utils.ExpressionFromStr<Order>(filterDto.Where);
QueryFilter<Order> filter = QueryFilter<Order>.FromDto(filterDto, where);
var result = await orderService.Get(filter);
if (result.ErrorResult != ApiError.OK)
return BadRequest(result.ErrorResult);
else
return Ok(result.Items);
}
Teraz cała tajemnica tkwi w Utils.ExpressionFromStr, które ze stringa robi z powrotem całe linq expression, które już w bardzo łatwy sposób możesz wykorzystać w zapytaniach SQL.
To wygląda tak:
public static class Utils
{
public async static Task<Expression<Func<T, bool>>> ExpressionFromStr<T>(string expressionStr)
{
var options = ScriptOptions.Default.AddReferences(typeof(T).Assembly);
return await CSharpScript.EvaluateAsync<Expression<Func<T, bool>>>(expressionStr, options);
}
}
I kolejna tajemnica - wymaga Roslyn.Potrzebujesz takich dwóch nugetów:
Microsoft.CodeAnalysis.Scripting
Microsoft.VisualStudio.Web.CodeGeneration.Design