Как присоединиться к списку и большим спискам/таблицам с помощью LINQ

Изначально у меня такой список:

List<Car> cars = db.Car.Where(x => x.ProductionYear == 2005).ToList();

Затем я пытаюсь объединить этот список с двумя большими таблицами, используя LINQ следующим образом:

var joinedList = (from car in cars
                  join driver in db.Driver.ToList() 
                    on car.Id equals driver.CarId
                  join building in db.Building.ToList() 
                    on driver.BuildingId equals building.Id
                  select new Building
                  {
                     Name = building.Name;
                     Id = building.Id;
                     City = building.City;
                  }).ToList();

Обе таблицы Driver и Building содержат около 1 миллиона строк. Когда я запускаю это соединение, я получаю исключение из памяти. Как я могу заставить это объединение работать? Должен ли я выполнить операцию соединения с базой данных? Если да, то как я могу перенести список cars в базу данных? Заранее спасибо.


person jason    schedule 30.06.2017    source источник
comment
db.Driver.ToList() Удалите из этого ToList(), это предотвратит выборку всей таблицы драйверов в памяти. Аналогично с db.Building.   -  person Rahul Singh    schedule 30.06.2017
comment
@RahulSingh Я сделал это, но все равно получаю исключение.   -  person jason    schedule 30.06.2017
comment
что вы планируете делать с этими коллекциями?   -  person Gilad Green    schedule 30.06.2017
comment
Да, это не поможет, если ваш join по-прежнему создает миллиарды записей. Вы должны фильтровать и извлекать данные партиями (если вы показываете эти данные в пользовательском интерфейсе или что-то в этом роде).   -  person Rahul Singh    schedule 30.06.2017
comment
@GiladGreen Я пытаюсь получить соответствующее здание для каждой машины.   -  person jason    schedule 30.06.2017
comment
да, но что ты собираешься с ним делать   -  person Gilad Green    schedule 30.06.2017
comment
@GiladGreen Я пытаюсь показать информацию о сборке на веб-сайте ASP.NET MVC.   -  person jason    schedule 30.06.2017
comment
@jason Сколько результатов вы получили? Замените последний .ToList() на .Count() и проверьте.   -  person Zein Makki    schedule 30.06.2017
comment
@user3185569 user3185569 бывает по-разному. Иногда несколько десятков, иногда несколько тысяч.   -  person jason    schedule 30.06.2017
comment
попробуйте это stackoverflow.com/a/11978832/5621827 или используйте фильтр или пейджинг при извлечении записей, я не думаю пользовательскому интерфейсу потребуется так много записей за раз   -  person jitender    schedule 30.06.2017
comment
@jason Сколько у вас записей в таблицах car, building и driver?   -  person Zein Makki    schedule 30.06.2017


Ответы (3)


даже если вы удалили .ToList(), замените .AsQueryable()

AsQueryable быстрее, чем ToList и AsEnumerable введите здесь описание изображения

введите здесь описание изображения

  • Если вы создаете IQueryable, то запрос может быть преобразован в sql и запущен на сервере базы данных.

  • Если вы создаете IEnumerable, все строки будут загружаться
    в память как объекты перед выполнением запроса.

  • В обоих случаях, если вы не вызываете ToList() или ToArray(), тогда query
    будет выполняться каждый раз, когда он используется, так что, скажем, у вас есть
    IQueryable и вы заполняете из него 4 списка. , то запрос будет выполняться к базе данных 4 раза.

поэтому после запроса на использование Linq

var joinedList = (from car in db.Car.Where(x => x.ProductionYear == 2005).AsQueryable()
              join driver in db.Driver.AsQueryable() 
                on car.Id equals driver.CarId
              join building in db.Building.AsQueryable() 
                on driver.BuildingId equals building.Id
              select new Building
              {
                 Name = building.Name,
                 Id = building.Id,
                 City = building.City,
              }).ToList();
person kari kalan    schedule 30.06.2017
comment
В чем разница между db.Driver и db.Driver.AsQueryable ? - person Zein Makki; 30.06.2017

Даже если вы удалите вызовы .ToList() внутри вашего соединения, ваш код все равно будет извлекать все данные и выполнять соединение в памяти, а не на сервере SQL. Это связано с тем, что вы используете локальный список cars в своем объединении. Нижеследующее должно решить вашу проблему:

var joinedList = (from car in db.Car.Where(x => x.ProductionYear == 2005)
                  join driver in db.Driver 
                    on car.Id equals driver.CarId
                  join building in db.Building 
                    on driver.BuildingId equals building.Id
                  select new Building
                  {
                     Name = building.Name;
                     Id = building.Id;
                     City = building.City;
                  }).ToList();

Вы можете удалить последние .ToList() и выполнить разбиение по страницам, если вы ожидаете получить слишком много записей в результатах.

person Zein Makki    schedule 30.06.2017
comment
Теперь он использует меньше памяти, но по-прежнему потребляет более 1 ГБ памяти. Нет ли способа уменьшить это? И есть ли способ освободить память после того, как я закончу соединение? Спасибо за ответ. - person jason; 30.06.2017
comment
@jason joinedList должно быть всем, о чем вам нужно беспокоиться после выполнения приведенного выше кода. Все, что больше не используется, помечается для сборки мусора (и будет собрано в какой-то момент в будущем при запуске GC). Однако, если в joinedList слишком много элементов, проверьте последнюю строку в моем ответе и внедрите пейджинг. - person Zein Makki; 30.06.2017
comment
В одном случае у меня возникло исключение нехватки памяти в этом объединении. Я сделал именно то, что вы сделали. - person jason; 30.06.2017
comment
@jason Приведенный выше ответ решает проблему, когда db.Driver, db.Building и db.Card имеют слишком много записей, и вы загружаете их в память, потому что делаете join неправильно. Однако, если возвращаемый результат представляет собой огромный список, который не подходит в память, тогда подкачка является решением для этого. Помните, что select new Building создает объекты в памяти, если у вас слишком много (более 2 ГБ, я думаю, вы получите исключение нехватки памяти). Пейджинг решает это, создавая части объектов по запросу. - person Zein Makki; 30.06.2017
comment
Я не знаю почему, но когда я добавляю AsQueryable() после таблиц, это работает очень эффективно. - person jason; 30.06.2017
comment
@jason Для меня это не имеет смысла, db.Driver уже является производной IQueryable, и этот метод просто выполняет приведение типов, если это применимо. Должен быть еще один фактор, влияющий на тестирование выполнения. - person Zein Makki; 30.06.2017

Во-первых, никогда не пытайтесь использовать ToList() при использовании LINQ (вы можете), но убедитесь, что вы используете ToList() как можно меньше в очень редких случаях. Каждый раз вы будете получать OutOfMemoryException, когда таблица содержит много строк. Итак, вот код вашего вопроса:

var joinedList = (from car in db.Car.GetQueryable().Where(x => x.ProductionYear == 2005)
              join driver in db.Driver.GetQueryable() on car.Id equals driver.CarId
              join building in db.Building.GetQueryable() on driver.BuildingId equals building.Id
              select new Building
              {
                 Name = building.Name;
                 Id = building.Id;
                 City = building.City;
              }).ToList();
person Starlord Live    schedule 30.06.2017
comment
в очень редких случаях .. Это неправильно. Вы можете выполнить несколько перечислений, если будете следовать этому совету, который влияет на производительность и делает круговые обходы. Хороший совет - понять отложенное выполнение, тогда вы сможете знать, когда использовать .ToList(), а когда нет. - person Zein Makki; 30.06.2017
comment
Да, я согласен с вами, если вы хороший кодер, вы будете знать, когда использовать и где использовать в соответствии с вашими потребностями после понимания выполнения. Иногда это полезно на основе бизнес-логики. - person Starlord Live; 30.06.2017