Почему этот запрос LINQ не работает правильно? - PullRequest
1 голос
/ 26 февраля 2020

У меня возникают проблемы с корректной работой следующего запроса

var result = await db.Set<Employee>()               
   .Include(ca => ca.Person)          
   .Include(ca=> ca.Person).ThenInclude(x=> x.GenderType)
   .Include(ca=> ca.Position).ThenInclude(x=> x.Department)
   .AsNoTracking()
   .ToListAsync();

Когда он выполняется, сущность person становится пустой, но только в новых реестрах. Я имею в виду, что в БД уже есть сотрудники которые были введены непосредственно с помощью SQL, и с этими реестрами он работает нормально.

Я подумал, что проблема может быть в том, как сохраняются реестры, поскольку два разных объекта сохраняются одновременно, а PersonId является ключ, чтобы соотнести сотрудника с человеком, вот как это делается:

 var person = await db.Set<Person>().AddAsync(obj.Person);
              await db.SaveChangesAsync();

obj.PersonId = person.PersonId;
db.Entry(obj.Person).State = EntityState.Detached;

await db.Set<Employee>().AddAsync(obj);
await db.SaveChangesAsync();

Я использую EntityState.Detached, поскольку Employee.Person уже сохранен. Это отлично работает для сохранения, но когда я пытаюсь получить все сущности от Employee, запрос вернет ноль, даже если Employee.PersonId правильный.

Если я сделаю более "прямой" запрос, он будет работать:

var query = from e in a
    join person in db.Set<Person>().AsNoTracking()
    on new { e.PersonId, e.SubscriptionId}
    equals new { person.PersonId, person.SubscriptionId}
    select person;

Так что я уверен, что реестр есть, поэтому я не могу найти проблему.

PS: Извините за неоднозначный вопрос

Ответы [ 2 ]

1 голос
/ 27 февраля 2020

Я полагаю, что вы, возможно, слишком усложняете свою работу с отсоединением + добавлением и пытаетесь вручную убедиться, что ссылочные объекты будут сохранены первыми. В большинстве обычных сценариев ios, когда EF разрешено нормально отслеживать объекты, он может справиться с этой задачей самостоятельно. Я также настоятельно рекомендую вам определять свои объекты для использования полей навигации или FK, а не обоих. Т.е. свойства навигации + свойства тени для FK или просто поля FK, если вам не нужны какие-либо из связанных свойств сущности или они являются чем-то вроде кэшированных поисков. Если вы используете оба, полагайтесь на отслеживаемые свойства навигации и не устанавливайте отношения по FK.

Используя db.Entry(obj.Person).State = EntityState.Detached;, вы в основном сказали DbContext забыть об этом объекте. Я согласен с тем, что если вы позже скажете DbContext загрузить Employee и .Include(x => x.Person), то для этой сущности Person будет странным быть #null. Но, возможно, вы можете избежать этой «ошибки» / поведения:

Этот код здесь пахнет:

var person = await db.Set<Person>().AddAsync(obj.Person);
          await db.SaveChangesAsync();

obj.PersonId = person.PersonId;

EF управляет назначениями FK автоматически на 100%. Когда я вижу такой код, он намекает на то, что разработчик SQL / ADO не доверяет EF управлять ассоциациями.

Принимая следующий упрощенный пример кода:

var person = new Person { Name = "Steve" };
var employee = new Employee { Title = "Developer", Person = person };

В SQL land У Employee есть личный идентификатор, поэтому нам обычно нужно сначала сохранить запись Person, получить ее и связать с нашим столбцом Employee.PersonId. Следовательно, код выглядит следующим образом:

context.Persons.Add(person);
context.SaveChanges(); // Generate PersonId.
employee.PersonId = person.PersonId;
context.Employees.Add(employee);
context.SaveChanges();

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

context.Employees.Add(employee);
context.SaveChanges();

При добавлении сотрудника EF просматривает все связанные объекты. Он находит Человека, о котором он не знает, поэтому он будет рассматривать его как добавленную сущность. Из-за сопоставления отношений он будет знать, что сначала нужно вставить Person, и PersonId Employee будет обновлен в результате до того, как вставлен Employee.

Где люди обычно запутываются, когда имеют дело с отношениями. неотслеживаемые экземпляры. Допустим, запись «Персона» уже существует. Мы создаем сотрудника, которого хотим связать с Person ID # 14. Наиболее распространенные примеры, которые я вижу, это когда Person был загружен из DbContext, отправлен клиенту, затем передан обратно на сервер, и разработчики предполагают, что это все еще «сущность», а не десериализованный POCO, о котором DbContext ничего не знает. Например:

public void CreateEmployeeForPerson(Person person)
{
   var employee = new Employee( Title = "Developer", Person = person );
   Context.Employees.Add(employee);
   Context.SaveChanges();
}

Это приводит к сбивающей с толку ошибке, что строка уже существует. Это связано с тем, что ссылка person рассматривается как новая сущность, она не отслеживается. EF хотел сгенерировать оператор INSERT как для сотрудника, так и для сотрудника. Работая с подключенным состоянием и AsNoTracking () со ссылками на сущности, которые вы можете использовать для обновлений, вы также можете столкнуться с такими проблемами. Эту проблему можно решить с помощью Attach, чтобы связать ее с контекстом, хотя это может быть рискованно, как если бы что-либо устанавливало измененное состояние для него, и данные были подделаны клиентом, что могло бы привести к непреднамеренным изменениям. Вместо этого мы должны всегда иметь дело с отслеживаемыми экземплярами:

public void CreateEmployeeForPerson(int personId)
{
   var person = Context.Persons.Single(x => x.PersonId == personId);
   var employee = new Employee( Title = "Developer", Person = person );
   Context.Employees.Add(employee);
   Context.SaveChanges();
}

Ссылка person известна / отслеживается DbContext, и мы утверждали, что PersonId действительно существует в БД через Single вызов. Теперь, когда сотрудник добавлен, ссылка .Person указывает на известный экземпляр DbContext, поэтому EF просто создает соответствующий оператор INSERT только для сотрудника.

Хотя это может не указывать, почему .Include не включал только что созданные вами экземпляры Person, надеюсь, это поможет в целом упростить ваш код персистентности и избежать странного поведения вокруг отдельных объектов.

1 голос
/ 27 февраля 2020

ОБНОВЛЕНИЕ:

Я понял, почему это происходило, корреляция один к одному в FK имела ошибку, так как я не делал эту часть кода, я не осознавал это раньше.

это была проблема:

modelBuilder.Entity<Employee>(entity =>
{
                entity.HasKey(e => new { e.EmployeeId, e.SubscriptionId });

                entity.HasOne(d => d.Person)
                    .WithOne(p => p.Employee)
                    .HasForeignKey<Person>(d => new { d.PersonId, d.SubscriptionId })
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_Employee_PersonId_SubscriptionId");

Когда это должно было быть так

modelBuilder.Entity<Employee>(entity =>
{
                entity.HasKey(e => new { e.EmployeeId, e.SubscriptionId });

                entity.HasOne(d => d.Person)
                    .WithOne(p => p.Employee)
                    .HasForeignKey<Employee>(d => new { d.PersonId, d.SubscriptionId })
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_Employee_PersonId_SubscriptionId");

Как видите. HasForeignKey<Employee>(d => new { d.PersonId,d.SubscriptionId }) было HasForeignKey<Person>... Я надеюсь, что это может помочь человеку, столкнувшемуся с той же проблемой.

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