Страницы

Поиск по вопросам

пятница, 3 января 2020 г.

Практическая разница между Linq To Entites и Linq To Objects

#c_sharp #entity_framework #linq


Изучая различия между LINQ To Entites и LINQ To Objects  в EntityFramework - столкнулся
с интересной вещью : 

var phones = db.Phones.Where(p=> p.CompanyId == 1).ToList().Where(p=> p.Id<10);


Здесь используются два метода Where, но их реализация будет различной. В первом случае,
db.Phones.Where(p=> p.CompanyId == 1) транслируется в выражение SQL, которое было рассмотрено
выше. Далее метод ToList() по результатам запроса создает список в памяти компьютера.
После этого мы уже имеем дело со списком в памяти, а не с базой данных. И далее вызов
Where(p=> p.Id<10) будет обращаться к списку в памяти и будет представлять Linq to Object.

И мне стало интересно : А не проще ли просто получать нужное значение из БД путем
простого обращения через LINQ не используя при этом LINQ To Objects?

Ведь запрос в даном случае будет выглядеть так :

var phones = db.Phones
.Where(p => p.CompanyId==1 &&
            p.TelephoneId<10)


Хотелось бы узнать какой подход в данном случае будем эффективней и почему? 
    


Ответы

Ответ 1



Смотрите. Базы данных предназначены, кроме всего прочего, и для того, чтобы быстро и эффективно работать с большими объёмами данных. Например, базы данных легко справляются с выборкой из таблиц, больших по размеру, чем оперативная память. А вот получение целой такой таблицы в память с целью последующей фильтрации уже провалится по перерасходу памяти. Также и фильтрация в базе данных может быть очень быстрой, т. к. если у неё есть индекс на фильтруемое поле, она может совместить фильтрацию с выборкой. (А особо умные базы данных могут, исходя из запросов, такой индекс и выстроить сами.) Если индекса нет, то всё равно польза от фильтрации на стороне базы в том, что выброшенные фильтром данные не нужно перегонять из базы в программу, и не нужно создавать их в основной программе только для того, чтобы тут же выбросить. Поэтому обычно имеет смысл те части запроса, которые можно выполнить в базе данных (это та часть, при которой вы остаётесь в рамках IQueryable), выполнять на ней. (Я не уверен, относится ли эта рекомендация к вложенным подзапросам, пускай лучше специалисты по базам данных поправят меня.) К сожалению, скорость операций на базе данных имеет и обратную сторону: не все операции можно выполнить на базе данных, поэтому промежуточные результаты запроса приходится материализовать (то есть, получать из базы и загружать в память), используя ToList или AsEnumerable, и «дорабатывать напильником» в самой программе. «Умения» различных баз данных отличаются между собой. Обычно база данных должна уметь фильтровать не только по равенству поля значению, но и уметь сравнивать с константой. Судя по всему, либо вам попался код для менее продвинутой базы данных (да, абстракции протекают), либо автор кода ошибся. Либо это «костыль» под какой-нибудь баг. Если сравнение возможно выполнить на базе данных, его стоит именно там и выполнять.

Ответ 2



В общем случае, вы конечно правы, проще получить одним запросом к БД, чем фильтровать потом в оперативной памяти. Но тут есть несколько нюансов. Во-первых мы должны учитывать, что в базе данных таблица несколько более сложна чем мы можем видеть в нашем коде, с ней связаны еще такие сущности как и индексы, триггеры и тд... Поэтому добавление какого либо дополнительного условия, может порождать "плохой" план запроса с полным перебором довольно больших объемов данных (например если Id входит только в составной индекс, да, для поля с таким названием это было бы странно, но... ). В таком случае, иногда, проще выбрать только небольшую часть данных, материализовать их (ToList()), и отфильтровать их уже в памяти, вместо проведения рефакторинга всей БД. Во-вторых Phones может быть не таблицей, а представлением, и планы запросов станут в таком случае еще сложнее.

Комментариев нет:

Отправить комментарий