簡體   English   中英

將計算屬性與動態 LINQ 一起使用的正確方法是什么?

[英]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 Idstring 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM