Linq to Entities与Linq对象性能问题

时间:2018-05-02 18:11:25

标签: c# asp.net-mvc entity-framework linq

在我的一个应用程序中,我正在尝试在视图中添加约会“状态”信息。我可以毫不费力地做到这一点,它不会减慢整体查询的速度 - 除非我尝试计算其中一个特定的状态。

这是一些额外的背景:必须通过检查一些字段来评估约会的状态。在大多数情况下,这并不算太糟糕,除非我需要计算患者是否为他们的预约“没有出现”。

为了确定没有出现,我需要查看appt_date字段,看它是否在今天的日期之前。但是,appt_date以VARCHAR格式存储为yyyyMMdd。为了将其与今天的日期进行比较,我需要将appt_date值转换为Datetime值。

然而,为了做到这一点,似乎我需要通过在查询上调用AsEnumerable()(并进行一些其他微小的更改)从“Linq到Entites”切换到“Linq to Objects”。当然,问题是查询变得慢得令人无法接受。我的理解是,“Linq to Objects”方法减慢了速度,因为它将更多数据加载到内存中,因此Entity Framework可以生成正确的SQL查询。

有没有人对我如何克服这个问题并使用查询保持良好的性能有任何建议?我非常感谢您提供的任何建议。

供参考,以下是使用Linq to Entites的查询(并且没有“未显示”计算):

var referrals = 
               (from r in _context.Referrals
                join cu in _context.ClinicUsers on r.ClinicId equals cu.ClinicId
                from ppa in _context.ReferralPPAs
                            .Where(p => p.ref_id == r.seq_no.ToString())
                            .DefaultIfEmpty()
                from ap in _context.Appointments
                .Where(a => a.appt_id.ToString() == ppa.appt_id)
                .DefaultIfEmpty()
                join ec in _context.EnrolledClinics on r.ClinicId equals ec.ClinicId
                join pm in _context.ProviderMasters on ec.ClinicId equals pm.ClinicId
                join ml in _context.MasterLists on pm.HealthSystemGuid equals ml.Id
                join au in _context.Users on r.ApplicationUserId equals au.Id
                where cu.UserId == userId
                select new ReferralListViewModel()
                {
                    ClinicName = pm.Description,
                    ClinicId = r.ClinicId,
                    ReferralId = r.seq_no,
                    EnteredBy = (au.FirstName ?? string.Empty) + " " + (au.LastName ?? string.Empty),
                    PatientName = (r.LastName ?? string.Empty) + ", " + (r.FirstName ?? string.Empty),
                    DateEntered = r.create_timestamp,
                    AppointmentDate = ap != null ? ap.appt_date : string.Empty,
                    AppointmentTime = ap != null ? ap.begintime : string.Empty,
                    Status = ppa != null ? ppa.Status : string.Empty,
                    AppointmentStatus = (ap != null & ap.cancel_ind == "N" & ap.confirm_ind == "N" & ap.resched_ind == "N" & ap.appt_kept_ind == "N") ? "Scheduled" :
                                        (ap != null & ap.cancel_ind == "Y") ? "Cancelled" :
                                        (ap != null & ap.confirm_ind == "Y") ? "Confirmed" :
                                        (ap != null & ap.resched_ind == "Y") ? "Rescheduled" :
                                        (ap != null & ap.appt_kept_ind == "Y") ? "Kept" : string.Empty
                }).Distinct();

使用Linq to Objects工作,但速度慢得令人无法接受:

var referrals = 
               (from r in _context.Referrals
                join cu in _context.ClinicUsers on r.ClinicId equals cu.ClinicId
                from ppa in _context.ReferralPPAs
                            .Where(p => p.ref_id == r.seq_no.ToString())
                            .DefaultIfEmpty()
                from ap in _context.Appointments
                .Where(a => a.appt_id.ToString() == ppa.appt_id)
                .DefaultIfEmpty()
                join ec in _context.EnrolledClinics on r.ClinicId equals ec.ClinicId
                join pm in _context.ProviderMasters on ec.ClinicId equals pm.ClinicId
                join ml in _context.MasterLists on pm.HealthSystemGuid equals ml.Id
                join au in _context.Users on r.ApplicationUserId equals au.Id
                where cu.UserId == userId
                select new { pm.Description, r.ClinicId, r.seq_no, au.FirstName, au.LastName, PatientLastName = r.LastName, PatientFirstName = r.FirstName, r.create_timestamp, ppa.Status, ap.cancel_ind, ap.confirm_ind, ap.resched_ind, ap.appt_kept_ind, ap.appt_date, ap.begintime })
                //Calling .AsEnumerable() converts it to Linq to Objects, which allows me to do the date conversion
                .AsEnumerable()
                    .Select(r => new ReferralListViewModel()
                    {
                        ClinicName = r.Description,
                        ClinicId = r.ClinicId,
                        ReferralId = r.seq_no,
                        EnteredBy = (r.FirstName ?? string.Empty) + " " + (r.LastName ?? string.Empty),
                        PatientName = (r.PatientLastName ?? string.Empty) + ", " + (r.PatientFirstName ?? string.Empty),
                        DateEntered = r.create_timestamp,
                        Status = r.Status != null ? r.Status : string.Empty,
                        AppointmentStatus = (r.cancel_ind != null & r.cancel_ind == "N" & r.confirm_ind == "N" & r.resched_ind == "N" & r.appt_kept_ind == "N") ? "Scheduled" :
                                            (r.cancel_ind != null & r.cancel_ind == "Y") ? "Cancelled" :
                                            (r.cancel_ind != null & r.confirm_ind == "Y") ? "Confirmed" :
                                            (r.cancel_ind != null & r.resched_ind == "Y") ? "Rescheduled" :
                                            //Here is the line used to calculate a "no-show" appointment
                                            (r.cancel_ind != null & r.appt_kept_ind == "N" & DateTime.ParseExact(r.appt_date, "yyyyMMdd", CultureInfo.InvariantCulture) < today) ? "No-show" :
                                            (r.cancel_ind != null & r.appt_kept_ind == "Y") ? "Kept" : string.Empty
                    }).Distinct();

2 个答案:

答案 0 :(得分:3)

  

为了确定没有出现,我需要查看appt_date字段以查看它是否在今天的日期之前。但是,appt_date以yyyyMMdd格式存储为VARCHAR。为了将其与今天的日期进行比较,我需要将appt_date值转换为Datetime值。

     

有没有人对我如何克服这个问题并使用查询保持良好的性能有任何建议?我非常感谢您提供的任何建议。

转到另一个方向,将今天的日期转换为varchar并在比较中使用它。这样您就可以使用表中的现有索引。

var today = DateTime.Today.ToString("yyyyMMdd");

// in your query down below
string.Compare(r.appt_date, today) < 0 ? "No-show" : ....

另请参阅Canonical Functions,了解EF可以将哪些函数转换为商店表达式。正如您所注意到的DateTime.ParseExact不是其中之一,这就是为什么您需要将数据存入内存然后根据您的条件再次在内存数据中过滤的原因。缓慢来自于必须从数据库中提取超出必要数据的数据。

更好的解决方案,但需要更多工作的解决方案是更改架构并实际将DateTime保留为Date或DateTime类型。这是我的建议,但如果您无法控制架构,则无法实现。

答案 1 :(得分:0)

理想情况下,在使用Entity Framework时,所有操作都应该执行Querable以允许在sql server级别进行解析树评估。 您的查询运行缓慢不是由于某些基础数据库原因,而是由于使用Enumerable将整个表提取到内存中,然后对这些执行操作。

如果您使用Querable,您的整个表达式将转换为在db上运行的sql查询并仅返回匹配的记录,一旦您需要数据,就可以随意应用Enumerable以适合您的特定操作。 / p>

尝试替换并放置探查器并查看生成的查询及其运行方式,您将轻松了解差异

下面的伪代码

Var query = from obj in db.AsQueryable()

选择obj.Name,........ obj.Id == myId

Query.AsEnumerable()。其中​​........