【问题标题】:Linq calling a method in select statement very slowLinq在select语句中调用方法非常慢
【发布时间】:2017-02-15 23:54:31
【问题描述】:

我正在尝试查找是否有一种方法可以从我的 linq select 语句中调用一个方法来构建一个不会显着减慢它的对象列表。这背后的原因是我也想在尝试仅获取一个对象时调用相同的方法并且不想维护两个版本(即,如果我将另一个字段添加到对象或想要渲染其中一个字段不同,我不必在多个地方更改它)。

在下面的示例中,TEST 1 的运行速度比 TEST 2 快 100 倍:

// Start timer
var timer = new Stopwatch();
timer.Start();

var test = (from job in dc.Jobs
        where !job.archived
        select new JobExtended()
        {
            JobId = job.jobId,
            NodeId = job.nodeId,
            JobName = job.name != string.Empty ? job.name : "TBC",
            Salary = job.salary,
            RecruiterId = job.fkRecruiterId,
            RecruiterNodeId = job.JobRecruiter != null ? job.JobRecruiter.recruiterNodeId : null,
            RecruiterName = job.JobRecruiter != null ? job.JobRecruiter.name : string.Empty,
            LocationId = job.fkLocationId,
            Location = job.refJobLocation != null ? job.refJobLocation.jobLocation : "",
            ContractTypeId = job.fkContractTypeId,
            ContractType = job.refJobContractType != null ? job.refJobContractType.contractType : "",
            CategoryId = job.fkCategoryId,
            Category = job.refJobCategory != null ? job.refJobCategory.category : "",
            ClosingDate = job.closingDate,
            Featured = job.featured,
            JobOfTheWeek = job.jobOfTheWeek,
            PublishedDate = job.publishedDate,
            Url = "/jobs/" + job.name.Replace(" ", "-").Replace("&", "and").Replace("'", "") + (job.fkLocationId.HasValue ? "-in-" + job.refJobLocation.jobLocation.Replace(" ", "-").Replace("&", "and").Replace("'", "") : "") + "-jn" + job.jobId,
            CreatedOn = job.createdOnDate,
            PrintWidth = job.printWidth,
            PrintHeight = job.printHeight,
            UntilFilled = (job.untilFilled != null && job.untilFilled.Value),
            AdvertCost = job.advertCost,
            DatesToShow = job.relJobDates.Where(x => x.fkJobId == job.jobId).Select(x => x.date).ToList(),
            IsParentJob = job.relLinkedJobs != null && job.relLinkedJobs.Any(x => x.fkParentJobId == job.jobId),
            IsAlternateWeekJob = job.alternateWeek != null && job.alternateWeek.Value,
            Archived = job.archived,
            LastModifiedDate = job.lastModifiedDate,
            RecruiterContactDetails = job.recruiterContactDetails
        }).ToList();

// Stop timer
timer.Stop();

// Output info
litTest.Text = "TEST 1 in " + timer.Elapsed.TotalSeconds + " seconds<br/>";

//Start timer
timer = new Stopwatch();
timer.Start();

var test2 = (from job in dc.Jobs
            where !job.archived
            select GetJobDetails(job)).ToList();

//Stop timer
timer.Stop();

//Output info
litTest.Text += "TEST 2 in " + timer.Elapsed.TotalSeconds + " seconds<br/>";

这是 TEST 2 调用的方法,它应该创建与 TEST 1 中返回的对象相同的对象:

public static JobExtended GetJobDetails(Data.Job job)
{
    return new JobExtended()
    {
        JobId = job.jobId,
        NodeId = job.nodeId,
        JobName = job.name != string.Empty ? job.name : "TBC",
        Salary = job.salary,
        RecruiterId = job.fkRecruiterId,
        RecruiterNodeId = job.JobRecruiter != null ? job.JobRecruiter.recruiterNodeId : null,
        RecruiterName = job.JobRecruiter != null ? job.JobRecruiter.name : string.Empty,
        LocationId = job.fkLocationId,
        Location = job.refJobLocation != null ? job.refJobLocation.jobLocation : "",
        ContractTypeId = job.fkContractTypeId,
        ContractType = job.refJobContractType != null ? job.refJobContractType.contractType : "",
        CategoryId = job.fkCategoryId,
        Category = job.refJobCategory != null ? job.refJobCategory.category : "",
        ClosingDate = job.closingDate,
        Featured = job.featured,
        JobOfTheWeek = job.jobOfTheWeek,
        PublishedDate = job.publishedDate,
        Url = "/jobs/" + job.name.Replace(" ", "-").Replace("&", "and").Replace("'", "") + (job.fkLocationId.HasValue ? "-in-" + job.refJobLocation.jobLocation.Replace(" ", "-").Replace("&", "and").Replace("'", "") : "") + "-jn" + job.jobId,
        CreatedOn = job.createdOnDate,
        PrintWidth = job.printWidth,
        PrintHeight = job.printHeight,
        UntilFilled = (job.untilFilled != null && job.untilFilled.Value),
        AdvertCost = job.advertCost,
        DatesToShow = job.relJobDates.Where(x => x.fkJobId == job.jobId).Select(x => x.date).ToList(),
        IsParentJob = job.relLinkedJobs != null && job.relLinkedJobs.Any(x => x.fkParentJobId == job.jobId),
        IsAlternateWeekJob = job.alternateWeek != null && job.alternateWeek.Value,
        Archived = job.archived,
        LastModifiedDate = job.lastModifiedDate,
        RecruiterContactDetails = job.recruiterContactDetails
    };
}

这样做的原因是因为我希望能够调用“GetJobDetails”来返回单个作业,例如:

    public JobExtended GetJobDetails(int jobId)
    {
        using (DataContext dc = new DataContext())
        {
            return dc.Jobs.Where(x => x.jobId == jobId).Select(j => GetJobDetails(j)).FirstOrDefault();
        }
    }

这样做只会让我只需要更新“GetJobDetails”方法,例如,如果我决定添加一个新字段来更改“Url”值的生成方式,但这样做会慢很多.有没有办法解决这个问题,我已经尝试了以下似乎没有帮助的方法:

var test3 = (from job in dc.Jobs
                where !job.archived
                select job).AsEnumerable()
                .Select(GetJobDetails).ToList();

var test4 = (from job in dc.Jobs
                where !job.archived
                select GetJobDetails(job));
var test4a = test4.ToList();

【问题讨论】:

  • JobExtended 的构造函数应该只接受一个参数,那就是Data.Job。分配属性和检查其他东西应该在构造函数中完成。我这么说是因为你有很多属性。你的代码将减少到return new JobExtended(job);....你在一行中做的太多了。这就是我想说的。
  • @M.kazemAkhgary 您的解决方案是将 JobExtended 类型构造函数与 Job 类型紧密耦合,这可能是不可取的。

标签: c# linq


【解决方案1】:

TEST 1 更快的原因是查询在服务器上执行一次并且只返回选定的字段。

var test = (from job in dc.Jobs
    where !job.archived
    select new JobExtended()
    {
        JobId = job.jobId,
        NodeId = job.nodeId,
        ...
    }).ToList();

当您在 TEST 2 中调用 GetJobDetails 时,参数 j 需要先具体化,然后才能作为参数发送到 GetJobDetails。所以有多个完整对象的调用。

return dc.Jobs.Where(x => x.jobId == jobId).Select(j => GetJobDetails(j)).FirstOrDefault();

为了实现你想要的东西,你应该使用扩展方法。这个扩展了 IQueryable。

    public static IEnumerable<JobExtended> SelectJobExtended(this IQueryable<Data.Job> query)
    {
        return query
            .Select(o => new JobExtended()
            {
                JobId = job.jobId,
                NodeId = job.nodeId,
                ...
            }
    }

然后你可以调用:

dc.Jobs.Where(x => x.jobId == jobId).SelectJobExtended().FirstOrDefault();

【讨论】:

  • 我试过这个,但我在“SelectJobExtended”方法中收到以下错误:附加信息:无法翻译表达式'Table(Job).Where(x => Not(x. archived))' 到 SQL 中,无法将其视为本地表达式。
  • 应该是 .Select(job => new JobExtended() ... { .. });
  • IList 应该是 IEnumerable。我已经更新了答案。
  • 我用这个方法让它工作了,我的问题是我在 SelectJobExtended 方法中使用了 String.IsNullOrEmpty。
【解决方案2】:

我以前见过这种问题。如果我记得,我们所做的是“堆叠”查询。

public IEnumerable<JobExtended> ConvertToJobExtended(IEnumerable<Job> jobs)
{
    return
        from job in jobs
        select new JobExtended()
        {
            MyString = job.MyInt.ToString(),
            ...
        };
}

那么你可以通过以下方式调用它:

var query = (from job in dc.Jobs
        where !job.archived
        select job;

var test2 = ConvertToJobExtended(query).ToList();

这里有很多选择......我希望这朝着你想要做的正确方向发展。

【讨论】:

  • 我试过了,但它似乎根本没有提高性能。
  • 您使用的是 IEnumerable 还是 IQueryable?另外,请注意 ToList() 只被调用一次(在最后)。
猜你喜欢
  • 1970-01-01
  • 2012-12-08
  • 2011-10-25
  • 1970-01-01
  • 2020-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多