Linq to Entities против производительности Linq to Objects - PullRequest
0 голосов
/ 02 мая 2018

В одном из моих приложений я пытаюсь добавить информацию о статусе встречи на мой взгляд. Я могу сделать это без особых проблем, и это не сильно замедляет общий запрос - кроме случаев, когда я пытаюсь вычислить один из определенных статусов.

Вот еще один дополнительный фон: статус встречи должен оцениваться путем изучения нескольких полей. По большей части, это не так уж плохо, за исключением тех случаев, когда мне нужно подсчитать, был ли пациент «не явился» на прием.

Чтобы определить неявку, мне нужно взглянуть на поле appt_date, чтобы увидеть, предшествует ли оно сегодняшней дате. Тем не менее, appt_date сохраняется как VARCHAR в формате yyyyMMdd. Чтобы сравнить его с сегодняшней датой, мне нужно преобразовать значение appt_date в значение Datetime.

Однако, для этого, мне кажется, мне нужно переключиться с «Linq to Entites» на «Linq to Objects», вызвав AsEnumerable() в запросе (и внеся некоторые другие незначительные изменения). Конечно, тогда проблема в том, что запрос становится неприемлемо медленным. Насколько я понимаю, подход "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 голосов
/ 02 мая 2018

В идеале при использовании Entity Framework все ваши операции должны выполнять Querable, чтобы разрешить оценку дерева разбора на уровне сервера sql. Ваш запрос выполняется медленно не из-за каких-то причин, связанных с базой данных, а из-за того, что использование Enumerable извлекает таблицу whole в память, а затем выполняет операции с ними.

Если вы сделаете Querable, все ваше выражение будет преобразовано в sql-запрос, который выполняется на БД и возвращает только совпадающие записи, и как только вы получите необходимые данные, вы можете применять Enumerable в соответствии с вашими конкретными операциями.

Попробуйте заменить и разместить профилировщик, посмотреть сгенерированный запрос и узнать, как он выполняется, вы легко узнаете разницу

Псевдокод ниже

Var query = from obj in db.AsQueryable()

Выберите obj.Name, ........ Где obj.Id == myId

Query.AsEnumerable (). Где ........

0 голосов
/ 02 мая 2018

Чтобы определить неявку, мне нужно взглянуть на поле appt_date, чтобы увидеть, предшествует ли оно сегодняшней дате. Однако appt_date сохраняется как VARCHAR в формате yyyyMMdd. Чтобы сравнить его с сегодняшней датой, мне нужно преобразовать значение appt_date в значение Datetime.

Есть ли у кого-нибудь предложения о том, как мне решить эту проблему и поддерживать хорошую производительность с помощью запроса? Буду очень признателен за любые советы, которые вы можете дать.

Идите в другом направлении, конвертируйте сегодняшнюю дату в varchar и используйте ее в своем сравнении. Это позволит вам также использовать ваши существующие индексы в таблице.

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

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

См. Также Канонические функции , чтобы узнать, какие функции могут быть преобразованы в выражения магазина EF. Как вы заметили, DateTime.ParseExact не является одним из них, поэтому вам потребуется загрузить данные в память, а затем снова отфильтровать их в данных памяти в зависимости от вашего состояния. Замедление происходит из-за необходимости извлекать из базы данных гораздо больше данных, чем необходимо.


Лучшее решение, но требующее дополнительной работы, состоит в том, чтобы изменить схему и фактически сохранить DateTime в качестве типа Date или DateTime. Это моя рекомендация, но если у вас нет контроля над схемой, это невозможно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...