如何对使用存储过程检索的记录在 ToList() 之前执行 Count()?

问题描述 投票:0回答:2

这是我的代码:

// grid query
var data = ctx.spRewards(Month, Year, null, MedicalID).Select(p => new
{
    MedicalID = p.MedicalID,
    DateReward = p.DateReward,
    Medical = p.Medical,
    AmountRefunds = p.AmountRefunds,
    AmountActivitiesStart = p.AmountActivitiesStart,
    AmountActivitiesEnd = p.AmountActivitiesEnd,
    AmountActivities = p.AmountActivities,
    AmountTotal = p.AmountTotal,
    Month = p.Month,
    Year = p.Year
});

// some further filters that will be attached in case

// grid order
data = data.OrderBy(p => p.DateReward).ThenBy(p => p.MedicalID);

// grid data
var dataTotal = data.Count();
if (formData.length >= 0)
{
    data = data.Skip(formData.start).Take(formData.length);
}
var dataFiltered = data.ToList();
return Json(new { data = dataFiltered, recordsFiltered = dataTotal, recordsTotal = dataTotal });

但是一旦我尝试执行

var dataFiltered = data.ToList();
,我就会得到一个查询的结果不能被枚举多次

我的目的是首先仅返回记录数(仅获取过滤后的数据量,而不下载内存中的所有记录,然后进行计数,这需要时间和资源),然后使用 Skip/Take 对其进行分页。

通常这可以在桌子上使用

IQueryable<a>
来实现:

var data = ctx.SomeTable.AsNoTracking().Select(p => new
{
    //
})

但它不会直接调用数据库中的存储过程。我尝试使用

data
IEnumerable<a>
IQueryable<a>
转换为
ctx.spRewards(Month, Year, null, MedicalID).AsQueryable()
,但出现了同样的错误。

我需要配置什么?

编辑:添加了答案建议的整个“实际”代码,但仍然不起作用:

var dataQuery = ctx.spRewards(Month, Year, null, MedicalID).AsQueryable().Select(p => new
{
    MedicalID = p.MedicalID,
    DateReward = p.DateReward,
    Medical = p.Medical,
    AmountRefunds = p.AmountRefunds,
    AmountActivitiesStart = p.AmountActivitiesStart,
    AmountActivitiesEnd = p.AmountActivitiesEnd,
    AmountActivities = p.AmountActivities,
    AmountTotal = p.AmountTotal,
    Month = p.Month,
    Year = p.Year
});

// grid - filters
string searchValue = Request.Form.GetValues("search[value]")?.FirstOrDefault()?.ToLower();
if (!string.IsNullOrEmpty(searchValue))
{
    dataQuery = dataQuery.Where(p =>
          p.Medical.ToLower().Contains(searchValue) ||
          p.AmountRefunds.ToString().ToLower().Contains(searchValue) ||
          p.AmountActivitiesStart.ToString().ToLower().Contains(searchValue) ||
          p.AmountActivitiesEnd.ToString().ToLower().Contains(searchValue) ||
          p.AmountTotal.ToString().ToLower().Contains(searchValue)
    );
}

// grid - order
string orderColumnId = Request.Form.GetValues("order[0][column]")?.FirstOrDefault();
string orderColumn = Request.Form.GetValues("columns[" + orderColumnId + "][data]")?.FirstOrDefault();
string orderDir = Request.Form.GetValues("order[0][dir]")?.FirstOrDefault();
if (!string.IsNullOrEmpty(orderColumn))
{
    if (orderDir == "desc")
    {
        dataQuery = dataQuery.OrderByDescending(orderColumn);
    }
    else
    {
        dataQuery = dataQuery.OrderBy(orderColumn);
    }
}
else
{
    dataQuery = dataQuery.OrderBy(p => p.DateReward).ThenBy(p => p.MedicalID);
}

    // grid - result
var dataClone = dataQuery.CloneQuery();
var dataTotal = dataQuery.Count();
if (formData.length >= 0)
{
    dataClone = dataClone.Skip(formData.start).Take(formData.length);
}
var dataFiltered = dataClone.ToList();
return Json(new { data = dataFiltered, recordsFiltered = dataTotal, recordsTotal = dataTotal });

编辑 2:添加了 spRewards 定义:

public virtual ObjectResult<spRewards_Result> spRewards(Nullable<int> month, Nullable<int> year, Nullable<int> clinicID, Nullable<int> medicalID)
{
    var monthParameter = month.HasValue ?
        new ObjectParameter("Month", month) :
        new ObjectParameter("Month", typeof(int));

    var yearParameter = year.HasValue ?
        new ObjectParameter("Year", year) :
        new ObjectParameter("Year", typeof(int));

    var clinicIDParameter = clinicID.HasValue ?
        new ObjectParameter("ClinicID", clinicID) :
        new ObjectParameter("ClinicID", typeof(int));

    var medicalIDParameter = medicalID.HasValue ?
        new ObjectParameter("MedicalID", medicalID) :
        new ObjectParameter("MedicalID", typeof(int));

    return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<spRewards_Result>("spRewards", monthParameter, yearParameter, clinicIDParameter, medicalIDParameter);
}
c# sql-server entity-framework linq ienumerable
2个回答
2
投票

我认为你的问题是你正在使用带有实体框架的存储过程,它创建了一个仅向前的结果集。

使用扩展方法,您可以创建具有相同条件的新查询:

public static class IQueryableExt 
{
    public static IQueryable<T> CloneQuery<T>(this IQueryable<T> q) => q.Provider.CreateQuery<T>(q.Expression);
}

然后您可以使用原始查询来获取总计数,使用克隆查询来获取过滤后的记录:

// grid data
var data2 = data.CloneQuery();
var dataTotal = data.Count();

if (formData.length >= 0)
{
    data2 = data2.Skip(formData.start).Take(formData.length);
}

var dataFiltered = data2.ToList();

0
投票

可能最简单的方法是在 SP 末尾添加

select @@ROWCOUNT
并查询 第二个结果集(其中包含行数)。

正如您在上面的链接中看到的,这可以通过调用来实现

reader.NextResult();

这会将读取器的上下文切换到下一个结果集。如果您这样做而不读取第一个结果集,不应该传输任何行数据。

如果您只是调用它而不前进到第二个结果集,它将默认返回存储过程执行中的数据。


示例 - Northwind 数据库。 在这里,我们在 Northwind 数据库中创建一个新的存储过程用于演示目的,调用

[dbo][Employee Sales by Country]
并添加第二个结果集:

CREATE PROCEDURE [dbo].[GetEmployeeSalesCount]  
    @Beginning_Date DateTime, @Ending_Date DateTime
AS
BEGIN
    SET NOCOUNT ON;

    -- first resultset
    EXECUTE [dbo].[Employee Sales by Country] 
       @Beginning_Date,@Ending_Date

    -- 2nd resultset
    select @@ROWCOUNT as count
END

注意: 您只需将

select @@ROWCOUNT as count
添加到现有存储过程 (SP) 的末尾,就在要作为数据返回的最后一个选择之后。 无需在生产代码中创建第二个 SP,这仅用于演示。

现在您只想根据传递的参数获取 GetEmployeeSalesCount 的行数。

这是检索行数的 C# 代码:

// use LinqPad to try it out, connecting with the Northwind database
void Main()
{
    var conn = this;

    var dtFrom = new DateTime(1998, 1, 1);
    var dtTo = new DateTime(1999, 1, 1);
    var count = GetSalesRowCount(conn.Connection, dtFrom, dtTo);
    Console.WriteLine($"Rowcount: {count}");

    // now you can do filtering based on the count value
    // This will read from the 1st resultset
    var skipNrRows = 260;
    var query = conn.GetEmployeeSalesCount(dtFrom, dtTo).AsDynamic()
                .Skip(skipNrRows).Take(count - skipNrRows);
    query.Dump();
}

int GetSalesRowCount(IDbConnection conn, DateTime fromDate, DateTime toDate)
{
    var cmd = conn.CreateCommand();
    cmd.CommandText = "[dbo].[GetEmployeeSalesCount]";
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add(new SqlParameter("@Beginning_Date", SqlDbType.DateTime) { Value = new DateTime(1998, 1, 1) });
    cmd.Parameters.Add(new SqlParameter("@Ending_Date", SqlDbType.DateTime) { Value = new DateTime(1999, 1, 1) });
    cmd.Connection.Open();

    var reader = cmd.ExecuteReader();
    // this will read from the 2nd resultset
    reader.NextResult(); reader.Read();
    var count = (int)reader["count"];
    
    cmd.Connection.Close();
    
    return count;
}

打印:


注意: 您还可以通过这种方式访问第二个结果集:

var queryForRS2 = conn.GetEmployeeSalesCount(dtFrom, dtTo).CreateDataReader();
queryForRS2.NextResult(); queryForRS2.Read();
var count = (int)queryForRS2.GetValue(0);
© www.soinside.com 2019 - 2024. All rights reserved.