Используя 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, поэтому простите за неправильную терминологию.
Я добавил внешний ключ, но потом он накричал на меня об этом. Но я также верил, что EFCore подхватит его по соглашению. Это неправильно?
Чтобы иметь две (или более) связи между сущностью и другой, вам потребуются либо отдельные связывающие таблицы, либо отдельные 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, не беспокоясь о коллекциях, которые нельзя использовать в запросах)
Спасибо. Это очень полезно. Я думаю, что это приведет меня туда, куда я хочу пойти
Это может быть глупо, но разве выше нигде нет ссылки на внешний ключ, или это уловлено в системе?