EF Core 8 сначала код: объединение объекта с настраиваемым свойством дифференциации

Используя EF Core 8, сначала код, у меня есть две сущности: Student и Guardian. Я хочу иметь простой способ различать отношения, связанные с опекой и не связанные с опекой.

Это мой Student класс:

public class Student : Entity 
{
    public int Id { get; set; }
    // ... etc

    public virtual ICollection<Guardian> CustodialGuardians { get; set; }
    public virtual ICollection<Guardian> NonCustodialGuardians { get; set; }
}

А это класс Guardian:

public class Guardian : Entity 
{
    public int Id { get; set; }
    // ... etc

    public virtual ICollection<Student> CustodialGuardianOf { get; set; }
    public virtual ICollection<Student> NonCustodialGuardianOf { get; set; }
}

Затем я создал простую сущность соединения:

public class CustodialStudentGuardian
{
    public virtual Guardian ParentGuardian { get; set; } = default!;
    public virtual Student Student { get; set; } = default!;
    public bool Custodial { get; set; } = default!;
}

И соответствующая конфигурация:

modelBuilder.Entity<Student>(e =>
      e.HasMany(s => s.CustodialGuardians)
          .WithMany(s => s.CustodialGuardianOf)
          .UsingEntity<CustodialStudentGuardian>(c =>
             {
                 c.HasOne(c => c.Student).WithOne();
                 c.HasOne(c => c.ParentGuardian).WithOne();
                 c.HasDiscriminator(d => d.Custodial).HasValue(true);
            });
      });

Я повторяю приведенную выше конфигурацию для некастодиальных объектов, где для значения установлено значение false.

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

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

Я знаю, что могу сделать это с помощью методов расширения и/или запросов linq, но я надеялся, что смогу сделать это по соглашению, используя отношения.

Я все еще новичок в C# и EF Core, поэтому простите за неправильную терминологию.

Это может быть глупо, но разве выше нигде нет ссылки на внешний ключ, или это уловлено в системе?

Ross Bush 02.07.2024 01:33

Я добавил внешний ключ, но потом он накричал на меня об этом. Но я также верил, что EFCore подхватит его по соглашению. Это неправильно?

dgo 02.07.2024 02:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы иметь две (или более) связи между сущностью и другой, вам потребуются либо отдельные связывающие таблицы, либо отдельные FK в связующей таблице, либо реализация связи «многие ко многим» как единая связь, в которой дифференциация в отношении хранения выполняется постфактум. .

Несмотря на то, что вы по существу используете связи между тремя таблицами (Student, Guardian и единственной связывающей таблицей StudentGuardian), FK StudentId и GuardianId в таблице StudentGuardian могут представлять только ОДНУ связь. (Custodial или NonCustodial) Если у вас есть две отдельные таблицы для CustodialStudentGuardian и NonCustodialStudentGuardian, то эту связь можно обеспечить в базе данных. (В противном случае также может работать создание хранительных и некастодиальных FK в одной таблице)

Главный вопрос, однако, заключается в том, действительно ли вы получаете пользу от разделения отношений между опекуном и учеником по принципу опеки и не опеки? Если вы разделите данные, вам всегда нужно будет выполнять запросы как по опекунским, так и по неопекунским отношениям, чтобы построить представление об опекунах для учащегося или учащихся для опекуна. Вы не можете просто увидеть опекунов для учащегося или студентов для опекуна, это всегда будет включать в себя объединение результатов из двух коллекций. Лично это не кажется хорошей идеей, я бы подумал просто иметь простые отношения «многие ко многим» между студентами и опекунами, используя сущность StudentGuardian, чтобы отслеживать, какие отношения являются опекунскими, а какие нет.

Рассмотрим следующее (для ясности мы избавились от базового класса):

public class Student {
  public int Id {get;set;}
  public virtual ICollection<StudentGuardian> StudentGuardians {get; protected set;} = new [];
}

public class Guardian {
  public int Id {get;set;}
  public virtual ICollection<StudentGuardian> StudentGuardians {get; protected set;} = new [];
}

public class StudentGuardian
{
    public int StudentId { get; set; }
    public int GuardianId { get; set; }
    public bool Custodial {get; set;}

    [ForeignKey(nameof(StudentId))]
    public virtual Student Student { get; set; } = default!;
    [ForeignKey(nameof(GuardianId))]
    public virtual Guardian Guardian { get; set; } = default!;
}

Вам понадобится конфигурация StudentGuardian для составного PK, если вы используете StudentId + GuardianId.

Теперь это устанавливает базовое отношение «многие ко многим».

modelBuilder.Entity<Student>(e => 
    e.HasMany(s => s.StudentGuardians)
        .WithOne(s => s.Student)
  });
modelBuilder.Entity<Guardian>(e => 
    e.HasMany(s => s.StudentGuardians)
        .WithOne(s => s.Guardian)
  });

Теперь, чтобы перейти к желаемым подмножествам отношений, вернемся к объявлениям сущностей:

public class Student {
    public int Id {get;set;}
    public virtual ICollection<StudentGuardian> StudentGuardians {get; protected set;} = new [];

    [NotMapped]
    public IReadOnlyCollection<Guardian> CustodialGuardians => StudentGuardians.Where(x => x.Custodial)
        .Select(x => x.Student)
        .ToList()
        .AsReadOnly();
    [NotMapped]
    public IReadOnlyCollection<Guardian> NonCustodialGuardians => StudentGuardians.Where(x => !x.Custodial)
        .Select(x => x.Student)
        .ToList()
        .AsReadOnly();

}

public class Guardian {
    public int Id {get;set;}
    public virtual ICollection<StudentGuardian> StudentGuardians {get; protected set;} = new [];

    [NotMapped]
    public IReadOnlyCollection<Student> StudentsCustodial => StudentGuardians.Where(x => x.Custodial)
         .Select(x => x.Student)
        .ToList()
        .AsReadOnly();
    [NotMapped]
    public IReadOnlyCollection<Student> StudentsNonCustodial => StudentGuardians.Where(x => !x.Custodial)
         .Select(x => x.Student)
        .ToList()
        .AsReadOnly();
}

Я помечаю целевые коллекции как ReadOnly, чтобы избежать необходимости использовать их для таких действий, как добавление или удаление ассоциаций. Это должно быть сделано из необработанных StudentGuardians.

У этого подхода есть несколько предостережений. Во-первых, чтобы использовать методы доступа StudentGuardians, ваши запросы должны активно загружать (Include) StudentGuardians или иметь доступную отложенную загрузку. Вы также не можете использовать эти методы доступа в выражениях запроса. Например, следующее не будет работать:

var students = Context.Students
    .Where(s => s.CustodialGuardians.Any(g => g.Id == guardianId))
    .ToList();

... это незаконно, поскольку EF не предлагает «CustodialGuardians», поэтому мы должны пометить их как [NotMapped]. Любой такой запрос должен выполняться через отношение «многие ко многим» StudentGuardians:

var students = Context.Students
    .Where(s => s.StudentGuardians.Any(g => g.Custodial && g.GuardianId == guardianId))
    .ToList();

Помощники по сбору [NotMapped] совершенно необязательны, поскольку вы можете довольно легко запросить всех опекунов учащегося как на опеке, так и на неопеке через StudentGuardians. (Это безопасно для Linq-to-EF, не беспокоясь о коллекциях, которые нельзя использовать в запросах)

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

dgo 02.07.2024 14:04

Другие вопросы по теме