在LINQ中使用自定义表达式会导致查询每次使用

时间:2011-07-11 12:03:53

标签: linq-to-sql expression-trees iqueryable

我遇到以下问题:在我们的数据库中,我们记录了帮助台门票,我们在门票下预订了几小时。这些之间是访问报告。所以它是:ticket => visitreport =>小时。

小时有一定的'种类',这不是由小时记录中的类型指示器确定的,而是通过检查一小时的各种属性来编译的。例如,有一个客户但不是服务时间的小时始终是发票时间。

我想要的最后一件事就是那些“种类”的定义在代码中无处不在。他们必须在一个地方。其次,我希望能够从各种小时集合中计算小时数。例如,具有特定日期和特定客户的折旧票证集合。或者所有注册都标记为“解决方案”。

我决定使用'分层'数据库访问方法。相同的功能可以为屏幕表示提供数据,也可以为.pdf中的报告提供数据。因此,第一步收集所有相关数据。这可以用于.pdf创建,也可以用于屏幕表示。在这种情况下,必须在第二步中对其进行分页和排序。这样我就不需要基本上使用相同数据的单独查询。

数据量可能很大,例如年份总数的创建。因此,第一步中的数据应该是可查询的,而不是可枚举的。为了确保即使我在结果中添加小时总和,我仍然可以查询,我做了以下功能:

    public static decimal TreeHours(this IEnumerable<Uren> h, FactHourType ht)
{
    IQueryable<Uren> hours = h.AsQueryable();
    ParameterExpression pe = Expression.Parameter(typeof(Uren), "Uren");
    Expression left = Expression.Property(pe, typeof(Uren).GetProperty("IsOsab"));
    Expression right = Expression.Constant(true, typeof(Boolean));
    Expression isOsab = Expression.Equal(Expression.Convert(left, typeof(Boolean)), Expression.Convert(right, typeof(Boolean)));

    left = Expression.Property(pe, typeof(Uren).GetProperty("IsKlant"));
    right = Expression.Constant(true, typeof(Boolean));
    Expression isCustomer = Expression.Equal(Expression.Convert(left, typeof(Boolean)), Expression.Convert(right, typeof(Boolean)));
    Expression notOsab;
    Expression notCustomer;
    Expression final;
    switch (ht)
    {
        case FactHourType.Invoice:
            notOsab = Expression.Not(isOsab);
            final = Expression.And(notOsab, isCustomer);
            break;
        case FactHourType.NotInvoice:
            notOsab = Expression.Not(isOsab);
            notCustomer = Expression.Not(isCustomer);
            final = Expression.And(notOsab, notCustomer);
            break;
        case FactHourType.OSAB:
            final = Expression.And(isOsab, isCustomer);
            break;
        case FactHourType.OsabInvoice:
            final = Expression.Equal(isCustomer, Expression.Constant(true, typeof(Boolean)));
            break;
        case FactHourType.Total:
            final = Expression.Constant(true, typeof(Boolean));
            break;
        default:
            throw new Exception("");
    }
    MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { hours.ElementType },
hours.Expression,
Expression.Lambda<Func<Uren, bool>>(final, new ParameterExpression[] { pe })
);
    IQueryable<Uren> result = hours.Provider.CreateQuery<Uren>(whereCallExpression);
    return result.Sum(u => u.Uren1);

}

这个函数背后的想法是它应该是可查询的,这样我就不会将数据的数量转换为可枚举的。

我设法保持可查询直到结束。在第1步中,我收集原始数据。在第2步中,我订购数据,然后将其分页。在步骤3中,数据被转换为JSon并发送到客户端。它总计数小时。

问题是:我为每张票的小时收到一个查询。这是数以百计的查询!那太多了......

我尝试了以下方法:

DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Ticket>(t => t.Bezoekrapport);
options.LoadWith<Bezoekrapport>(b => b.Urens);
dc.LoadOptions = options;

Bezoekrapport简直就是“visitreport”的荷兰语。当我查看检索票证的查询时,我看到它加入了Bezoekrapport / visitreport,但没有加入它的小时数。

我使用的第二种方法是手动加入LINQ中的小时数,但这不起作用。

我必须做错事。这里最好的方法是什么?

以下代码片段是我检索数据的方式。在最后一步中调用strHours上的toList()时,我得到了一个充满疑问的问题。我已经尝试了两天来解决它但它只是不起作用......我的方法或函数TreeHours中的某些东西一定是错的。

第1步:

IQueryable<RelationHoursTicketItem> HoursByTicket =
            from Ticket t in allTickets
            let RemarkSolved = t.TicketOpmerkings.SingleOrDefault(tr => tr.IsOplossing)
            let hours = t.Bezoekrapport.Urens.
                Where(h =>
                      (dateFrom == null || h.Datum >= dateFrom)
                      && (dateTo == null || h.Datum <= dateTo)
                      && h.Uren1 > 0)
            select new RelationHoursTicketItem
                       {
                           Date = t.DatumCreatie,
                           DateSolved = RemarkSolved == null ? (DateTime?)null :                                                                                              RemarkSolved.Datum,
                           Ticket = t,
                           Relatie = t.Relatie,
                           HoursOsab = hours.TreeHours(FactHourType.OSAB),
                           HoursInvoice = hours.TreeHours(FactHourType.Invoice),
                           HoursNonInvoice = hours.TreeHours(FactHourType.NotInvoice),
                           HoursOsabInvoice = hours.TreeHours(FactHourType.OsabInvoice),
                           TicketNr = t.Id,
                           TicketName = t.Titel,
                           TicketCategorie = t.TicketCategorie,
                           TicketPriority = t.TicketPrioriteit,
                           TicketRemark = RemarkSolved
                       };

第2步

        sort = sort ?? "TicketNr";
        IQueryable<RelationHoursTicketItem> hoursByTicket = GetRelationHours(relation,     dateFrom, dateTo, withBranches);
        IOrderedQueryable<RelationHoursTicketItem> orderedResults;

        if (dir == "ASC")
        {
            orderedResults = hoursByTicket.OrderBy(sort);
}
        else
        {
            orderedResults = hoursByTicket.OrderByDescending(sort);
        }
        IEnumerable<RelationHoursTicketItem> pagedResults = orderedResults.Skip(start ?? 0).Take(limit ?? 25);
        records = hoursByTicket.Count();
        return pagedResults;

第3步:

 IEnumerable<RelationHoursTicketItem> hours = _hourReportService.GetRelationReportHours(relation, dateFrom, dateTo, metFilialen, start, limit, dir, sort, out records);

        var strHours = hours.Select(h => new
               {
                   h.TicketNr,
                   h.TicketName,
                   RelationName = h.Relatie.Naam,
                   h.Date,
                   TicketPriority = h.TicketPriority.Naam,
                   h.DateSolved,
                   TicketCategorie = h.TicketCategorie == null ? "" : h.TicketCategorie.Naam,
                   TicketRemark = h.TicketRemark == null ? "" : h.TicketRemark.Opmerking,
                   h.HoursOsab,
                   h.HoursInvoice,
                   h.HoursNonInvoice,
                   h.HoursOsabInvoice
               });

1 个答案:

答案 0 :(得分:1)

我不认为你的TreeHours扩展方法可以一次性由LINQ转换为SQL。因此在行的每个构造函数的执行时进行评估,在这种情况下每行导致对数据库的4调用。

我会简化您的LINQ查询,以便从SQL返回原始数据,使用简单的JOIN获取所有票证并在那里花费数小时。然后我会按内存类型对小时数进行分组和过滤。否则,如果确实需要在SQL中执行操作,请查看CompiledQuery.Compile方法。这应该能够处理不在每行进行查询。我不确定你是否会获得switch,但您可以使用?:运算符对其进行转换。