Linq guru - фильтрация связанных сущностей

Моя таблица имеет следующую структуру:

Person 1-M PesonAddress
Person 1-M PesonPhone
Person 1-M PesonEmail
Person 1-M Contract 
Contract M-M Program
Contract M-1 Organization

В конце этого запроса мне нужен заполненный граф объектов, в котором у каждого человека есть свои:

  • ПесонАдреса
  • PesonPhone's
  • PesonEmail's
  • PesonPhone's
  • Contract's - and this has its respective
    • Program's

Теперь у меня был следующий запрос, и я подумал, что он отлично работает, но у него есть пара проблем:

from people in ctx.People.Include("PersonAddress")
                        .Include("PersonLandline")
                        .Include("PersonMobile")
                        .Include("PersonEmail")
                        .Include("Contract")
                        .Include("Contract.Program")
where people.Contract.Any(
    contract => (param.OrganizationId == contract.OrganizationId)
        && contract.Program.Any(
            contractProgram => (param.ProgramId == contractProgram.ProgramId)))
select people;

Проблема в том, что он фильтрует человека по критериям, но не по контрактам или программам контракта. Он возвращает все Контракты, которые есть у каждого человека, а не только те, которые имеют OrganizationId x, и то же самое касается каждой из этих Контрактных Программ соответственно.

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

Я вроде понимаю, почему он не работает, но я не знаю, как это изменить, чтобы он работал ...

Это моя попытка:

from people in ctx.People.Include("PersonAddress")
                        .Include("PersonLandline")
                        .Include("PersonMobile")
                        .Include("PersonEmail")
                        .Include("Contract")
                        .Include("Contract.Program")
let currentContracts = from contract in people.Contract
                where (param.OrganizationId == contract.OrganizationId)
                select contract 
let currentContractPrograms = from contractProgram in currentContracts 
                    let temp = from x in contractProgram.Program
                        where (param.ProgramId == contractProgram.ProgramId)
                        select x
                    where temp.Any()
                    select temp
where currentContracts.Any() && currentContractPrograms.Any()
select new Person { PersonId = people.PersonId, FirstName = people.FirstName, ..., ...., 
                    MiddleName = people.MiddleName, Surname = people.Surname, ..., ...., 
                    Gender = people.Gender, DateOfBirth = people.DateOfBirth, ..., ...., 
                    Contract = currentContracts, ... };  //This doesn't work

Но здесь есть несколько проблем (если тип Person является объектом EF):

  • Мне остаётся делать мэппинг самому, а в данном случае есть довольно много маппинга.
  • Когда я пытаюсь сопоставить список со свойством (например, Scholarship = currentScholarships), он говорит, что я не могу, потому что IEnumerable пытается преобразовать в EntityCollection
  • Включить не работает

Следовательно, как мне заставить это работать. Имея в виду, что я пытаюсь сделать это как скомпилированный запрос, я думаю, что это означает, что анонимные типы отсутствуют.


person vdh_ant    schedule 27.04.2010    source источник
comment
Вы уверены, что контрактная программа - это M-M? Значит, для этого есть таблица перекрестных ссылок?   -  person Fedor    schedule 03.05.2010
comment
Да ... В базе данных есть таблица между Контрактом и Программой, которая упрощает M-M, но структура Entity абстрагировала эту `` сущность '' ...   -  person vdh_ant    schedule 03.05.2010


Ответы (3)


Только не используйте Include, фильтруйте вручную. Сначала вы можете отфильтровать контракты, связанные с требуемыми ProgramId и OrganizationId. После этого вы можете выбрать лиц, связанных с выбранными контрактами. Я приложил образец кода. Вам нужно будет изменить его, чтобы правильно использовать отношение M-M. Но в любом случае логика должна быть верной.

public class PersonDetails
{
    public Person person;
    public List<Contract> contracts;
}

var selected_program = (from pr in ctx.Programs where pr.Id == param.ProgramId select pr).Single();

//select contracts by OrganizationId and ProgramId
var selected_contracts = from c in ctx.Contracts
                where c.OrganizationId == param.OrganizationId
                from p in ctx.Programs
                where p.Id == param.ProgramId
                where p.ContractId == c.Id
                select c;

//select persons and contracts
var people =
    from p in ctx.People
    select new PersonDetails()
    {
        person = p,
        contracts = (from c in selected_contracts
                     where c.PersonId == p.Id
                     select c).ToList()
    };

//select people associated with selected contracts
var selected_people = from p in people where p.contracts.Count > 0 select p;
person Fedor    schedule 03.05.2010
comment
@Fedor: Я попытался реализовать этот подход в своей ситуации и получаю следующее исключение ... LINQ to Entities не распознает метод 'System.Collections.Generic.List1[Contract](System.Collections.Generic.IEnumerable1 [Contract])' метода, и этот метод не может быть переведено в магазинное выражение. - person vdh_ant; 04.05.2010
comment
Так что это нужно исправить. К сожалению, я не могу собрать ваш код, чтобы понять, в чем проблема. Вы можете использовать IEnumerable вместо List в PersonDetails. Или даже используйте анонимный класс вместо PersonDetails. - person Fedor; 04.05.2010
comment
Хотя это не точный ответ, я действительно привел меня к его поиску ... Спасибо за вашу помощь. - person vdh_ant; 06.05.2010

Включить в Entity Framework всегда будет возвращать все, что есть во взаимосвязи, нет способа выполнить частичное включение или эквивалент AssociateWith, который есть в Linq to SQL.

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

После выполнения обоих запросов ваши объекты Person будут содержать только контракты, возвращенные запросом Contracts в их коллекции Contracts.

person Mant101    schedule 30.04.2010
comment
Извините, но я не нашел все это настолько полезным ... не могли бы вы представить образец того, о чем вы говорите ... - person vdh_ant; 01.05.2010
comment
Вот ссылка на статью с примерами, которая на самом деле была написана в ответ на вопрос о переполнении стека: blogs.msdn.com/alexj/archive/2009/10/13/ - person Mant101; 04.05.2010

Как говорит Mant101, вы не можете фильтровать часть .Include в Linq по сущностям. Посмотрите на объект Person как на представление всей информации, хранящейся в базе данных об этом человеке, включая всю contracts. Вся установка должна производиться отдельно.

Эти вопросы, кажется, здесь часто возникают. По крайней мере, я думаю, что видел некоторые, но не могу найти многих. Вот еще один вопрос по этой теме: условное включение в linq для объектов?.

Там также есть работоспособный ансер: просто верните свой (весь) объект person и любую дополнительную (отфильтрованную) информацию о нем в новом анонимном типе.

person Jens    schedule 03.05.2010
comment
Проблема с этим подходом заключается в том, что, как я уже упоминал выше, я остаюсь делать все сопоставление самому ... что в этом случае есть тонна сопоставления, которую мне нужно было бы сделать в операторе select. Также некоторые из этого сопоставления невозможно выполнить с использованием анонимных типов из-за скомпилированного характера запроса ... Наконец, в запросе, который был принят в качестве ответа в вашей ссылке, он эффективно выполняет только левое соединение, а не внутреннее соединение. .. Мне нужно, чтобы это были только те дилерские центры, у которых есть детали, соответствующие критериям, и только те детали, которые также соответствуют критериям ... - person vdh_ant; 03.05.2010
comment
Напишите один запрос, который возвращает только те детали, которые вам нужны, и извлекает результаты в память (ToList). Напишите один запрос, который возвращает нужные вам дилерские центры, и помещает результаты в память. Напишите запрос, который возвращает нужный объект Person. Когда вы выполняете итерацию через это, каждому человеку будут возвращены только дилерские центры во втором запросе, а у каждого дилерского центра будут только детали, возвращенные по первому запросу. Нет необходимости в каких-либо анонимных типах или отображении, фреймворк автоматически подключает отношения. Вы также можете предварительно скомпилировать все три запроса. - person Mant101; 05.05.2010