[英]What is the correct way to use linq type methods with IAsyncEnumerable?
[英]What is the correct way to use computed properties with Dynamic LINQ?
我有這些模型。
public class Ticket
{
public int Id {get;set;}
public List<History> History {get;set;}
}
public class History
{
public Employee Employee {get;set;}
public string Comments {get;set;}
public DateTime Timestamp {get;set;}
}
Employee
只是一個帶有int Id
和string Name
的 class 。
我的想法是,我需要允許用戶通過Tickets
中的計算屬性查詢Tickets
,例如
public Employee LatestEmployee
{
get => History.MaxBy(x=>x.Timestamp).Employee;
}
從用戶那里,我得到了查詢屬性/列的名稱、搜索詞以及用戶是否希望它包含、開頭或等於用戶應用進一步的過濾器。 基本上我得到一個像這樣的 JSON :
{
"column":"Employee",
"query":"Batman",
"type":"equals"
}
將其轉換為動態 LINQ "Employee == Batman"
。
但是在數據庫本身上,它仍然創建一個帶有外鍵的 EmployeeId 列,所以我需要用一個隨機(存在,因為 FK)值來填充它。
然后,我將其查詢為
var filteredListQuery =
_context.Tickets.Include(x=>x.History)
.ThenInclude(y=>y.Employee)
.Where("Employee.Name == \"Batman\"");
之后,我必須返回一個 JSON,其中包含要顯示的門票信息。 所以我嘗試
var filteredListQueryDTO = filteredListQuery
.Select(x=>new TicketDTO()
{
Id = x.Id.ToString(),
Employee = x.Employee.Name,
Timestamp = x.Timestamp.ToString("g")
}).ToListAsync();
但隨后它會讀取保存在不必要列中的員工,而不是使用計算屬性。 如果我嘗試Employee = x.History.MaxBy(x=>x.Timestamp).Employee.Name
我得到一個LINQ expression could not be translated
。
但是,如果我按照有關異常的鏈接 KB 文章,它確實有效,首先在filteredQueryList()
的QueryList() 上調用ToList()
) :
var filteredListQueryDTO = (await filteredListQuery.ToListAsync())
.Select(x=>new TicketDTO()
{
Id = x.Id.ToString(),
Employee = x.Employee.Name,
Timestamp = x.Timestamp.ToString("g")
}).ToList();
或者,如果我使用 foreach 循環(我認為幾乎相同)遍歷過濾列表查詢。
有沒有更簡單(不那么復雜和更專業)的方法來做到這一點(並且沒有數據庫上額外未使用的列)?
基本上,您將使用參數對象而不是“動態”,並且可以選擇“如果”填充參數,您將添加IQueryable
的可選部分。
public class OrganizationSearchArgs
{
public string OrganizationNameNotLike { get; set; } = null;
public ICollection<int> OrgStatuses { get; set; } = new List<int>();
public ICollection<string> OrganizationNamesIn { get; set; } = new List<string>();
public ICollection<string> OrganizationNamesNotIn { get; set; } = new List<string>();
}
(下面將是一個在您的 dbcontext 上公開功能的類)
public async Task<int> GetListByArgsCount(OrganizationSearchArgs args, CancellationToken token)
{
IQueryable<OrganizationEntity> qry = this.GetGetListByArgsBaseIQueryable(args);
int returnValue = await qry.CountAsync(token);
return returnValue;
}
public async Task<IEnumerable<OrganizationEntity>> GetListByArgs(
OrganizationSearchArgs args,
CancellationToken token)
{
IQueryable<OrganizationEntity> qry = this.GetGetListByArgsBaseIQueryable(args);
List<OrganizationEntity> entities = await qry.AsNoTracking().ToListAsync(token);
return entities;
}
private IQueryable<OrganizationEntity> GetGetListByArgsBaseIQueryable(OrganizationSearchArgs args)
{
IQueryable<OrganizationEntity> qry = this.entityDbContext.Organizations
.Include(org => org.SomeNavigationProperty).AsNoTracking();
if (!string.IsNullOrEmpty(args.OrganizationNameNotLike))
{
qry = qry.Where(o => !o.OrganizationName.Contains(args.OrganizationNameNotLike));
}
if (null != args.OrgStatuses && args.OrgStatuses.Any())
{
qry = qry.Where(org => args.OrgStatuses.Contains(org.OrgStatus));
}
if (null != args.OrganizationNamesIn && args.OrganizationNamesIn.Any())
{
qry = qry.Where(org => args.OrganizationNamesIn.Contains(org.OrganizationName));
}
if (null != args.OrganizationNamesNotIn && args.OrganizationNamesNotIn.Any())
{
qry = qry.Where(org => !args.OrganizationNamesNotIn.Contains(org.OrganizationName));
}
return qry;
}
}
我發現最好讓我的實際實體模型盡可能地反映數據庫結構,並在很大程度上將它們用於 CRUD 操作。 您可以創建一個單獨的類來表示實體的其他可查詢方面,但不要使用計算屬性,而是使用映射邏輯為這些屬性創建投影。
public class TicketSummary
{
public int TicketId {get;set;}
public Employee LatestEmployee {get;set;}
}
public IQueryable<TicketSummary> BuildSummaryQuery()
{
return _context.Tickets.Select(t => new TicketSummary
{
TicketId = t.Id,
LatestEmployee = t.History.MaxBy(x=>x.Timestamp).Employee
});
}
然后,您可以根據需要應用您的查詢條件。 使用上面的動態 LINQ 示例,例如:
var filteredListQuery = BuildSummaryQuery()
.Where("LatestEmployee.Name == \"Batman\"");
不帶動態 LINQ 庫:
public static class ExpressionBuilder
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, object value)
{
if (propertyName == null) return t => true;
var parameter = Expression.Parameter(typeof(T));
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.PropertyOrField);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
static Expression MakeComparison(Expression left, string comparison, object value)
{
var constant = Expression.Constant(value, left.Type);
switch (comparison)
{
case "==":
return Expression.MakeBinary(ExpressionType.Equal, left, constant);
case "!=":
return Expression.MakeBinary(ExpressionType.NotEqual, left, constant);
case ">":
return Expression.MakeBinary(ExpressionType.GreaterThan, left, constant);
case ">=":
return Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, constant);
case "<":
return Expression.MakeBinary(ExpressionType.LessThan, left, constant);
case "<=":
return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, constant);
case "Contains":
case "StartsWith":
case "EndsWith":
if (value is string)
{
return Expression.Call(left, comparison, Type.EmptyTypes, constant);
}
throw new NotSupportedException($"Comparison operator '{comparison}' only supported on string.");
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
}
用法:
var data = querable.Where(ExpressionBuilder.BuildPredicate("Name", "==", "Batman"));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.