#c# #entity-framework #inheritance #architecture #abstract-class
#c# #entity-framework #наследование #архитектура #абстрактный класс
Вопрос:
Для начала я работаю с EF, поскольку я создаю приложение MVC на C #. Я хочу, чтобы разные типы экзаменов имели разные типы вопросов. Вот мои абстрактные классы:
public abstract class Exam
{
public int Id { get; set; }
public string Description { set; get; }
public abstract ICollection<Question> GetQuestions();
public abstract void SetQuestions(ICollection<Question> questions);
}
public abstract class Question
{
public int Id { get; set; }
public string Description { set; get; }
public abstract Exam getExam();
public abstract void setExam(Exam exam);
}
Обратите внимание, что вместо типичного public virtual ICollection<Question>
в объявлении класса Exam я создал абстрактный сеттер и геттер. То же самое относится и к свойству Exam в классе вопросов.
Вот мои конкретные экзаменационные классы:
[Table("SingleExam")]
public class SingleExam : Exam
{
public virtual ICollection<SingleQuestion> Questions { get; set; }
public override ICollection<Question> GetQuestions() { return Questions as ICollection<Question>; }
public override void SetQuestions(ICollection<Question> questions)
{
if (!(questions is ICollection<SingleQuestion>))
throw new ArgumentException("You must set single questions.");
Questions = questions as ICollection<SingleQuestion>;
}
}
[Table("MultipleExam")]
public class MultipleExam : Exam
{
public virtual ICollection<MultipleQuestion> Questions { get; set; }
public override ICollection<Question> GetQuestions() { return Questions as ICollection<Question>; }
public override void SetQuestions(ICollection<Question> questions)
{
if (!(questions is ICollection<MultipleQuestion>))
throw new ArgumentException("You must set multiple questions.");
Questions = questions as ICollection<MultipleQuestion>;
}
}
… И мои конкретные классы вопросов:
[Table("SingleQuestion")]
public class SingleQuestion : Question
{
public int ExamId { get; set; }
public virtual SingleExam Exam { get; set; }
public override Exam getExam() { return Exam; }
public override void setExam(Exam exam)
{
if (!(exam is SingleExam))
throw new ArgumentException("You must set a SingleExam");
Exam = exam as SingleExam;
}
}
[Table("MultipleQuestion")]
public class MultipleQuestion : Question
{
public int ExamId { get; set; }
public virtual MultipleExam Exam { get; set; }
public override Exam getExam() { return Exam; }
public override void setExam(Exam exam)
{
if (!(exam is MultipleExam))
throw new ArgumentException("You must set a MultipleExam");
Exam = exam as MultipleExam;
}
}
Я сделал все это, потому что MultipleExam должен иметь только MultipleQuestions, а SingleExam должен иметь только SingleQuestions, точно так же, как MultipleQuestion должен иметь MultipleExam, а Single question должен иметь SingleExam.
Есть ли лучший способ гарантировать, что подкласс класса ‘A’ содержит или имеет определенный подкласс класса ‘B’ (как в случае с моими экзаменами и вопросами) и имеет доступ к нему через абстрактный класс без абстрактных геттеров и сеттеров?
Комментарии:
1. Почему вопрос должен знать об экзамене, на котором он находится?
2. Это всего лишь свойства навигации, но на самом деле они не важны (хотя examID необходим для правильного отображения базы данных). Действительно важно убедиться, что разные типы экзаменов содержат определенные подклассы вопросов
3. Если вы не упустили некоторые другие подробности о назначении отдельных (множественных / одиночных) экзаменационных и (множественных / одиночных) типов вопросов, этот подход кажется чрезмерно сложным. Не проще ли иметь отдельные классы экзаменов и вопросов? Если вам нужно добавить различие между одним и несколькими экзаменами, вы можете добавить свойство bool Single, которое задается в конструкторе и контролирует, может ли экзамен иметь только один или более одного вопроса.
4. Функциональность будет сильно отличаться. Я не стал вдаваться в подробности, но SingleQuestion и MultipleQuestion будут иметь разные поля и функции
5. Возможно, рассмотрение того, что говорит @Richard, имело бы смысл. У вас может быть две новые сущности
ExamType
,QuestionType
и, установив отношения между ними, вы могли бы управлять тем, какие типы вопросов возможны для каких типов экзаменов. У васExam
есть одинExamType
, и у васQuestion
есть одинQuestionType
. Кроме того, у вас может быть «базовый» экзаменационный класс, а «специализированные» экзамены могут наследовать от него (используя TPH, TPT или TPC для сопоставления базы данных)
Ответ №1:
Как упоминали другие, я думаю, вы чрезмерно усложняете свою проблему. Однако; ваш вопрос касается гарантий типа, и я постараюсь ответить на это.
Сначала код:
public interface IExam<out T> where T:IQuestion {
int Id { get; set; }
string Description { set; get; }
IEnumerable<T> GetQuestions();
}
public interface IQuestion{
int Id { get; set; }
string Description { set; get; }
IExam<IQuestion> Exam { get; }
}
public class SingleQuestion:IQuestion {
public string Description { get; set; }
public int Id { get; set; }
IExam<IQuestion> IQuestion.Exam {
get { return Exam; }
}
public SingleExam Exam { get; set; }
}
public class SingleExam:IExam<SingleQuestion> {
public int Id { get; set; }
public string Description { get; set; }
private IEnumerable<SingleQuestion> _questions;
public IEnumerable<SingleQuestion> GetQuestions() {
return _questions;
}
public void SetQuestions(IEnumerable<SingleQuestion> questions) {
_questions = questions;
}
}
Прежде всего, мы заменили абстрактные классы интерфейсами.
Это требуется, потому что мы хотим сделать IExam ковариантным по IQuestion, а ковариация может быть определена только в интерфейсе. По этой же причине мы переходим на IEnumerable для коллекции.
Обратите внимание, что мы не определяем метод SetQuestions в IExam, короче говоря, это потому, что мы не можем. В long это связано с тем, что это сделало бы T контравариантным, а также контравариантным, что, в свою очередь, привело бы к обстоятельствам, при которых гарантии типа не могли быть сделаны.
IQuestions довольно прост, здесь нет реальных изменений. Я полагаю, вы могли бы оставить его как абстрактный тип.
Теперь реализации: в SingleQuestion мы должны явно реализовать Exam, который ожидает IExam, а затем затенить его свойством, которое возвращает SingleExam. Это позволяет нам возвращать наиболее точный возможный тип экзамена.
SingleQuestion sq = new SingleQuestion();
IQuestion q = sq; //Upcast
sq.Exam; //returns a SingleExam
q.Exam; //returns a IExam<IQuestion>
В SingleExam теперь вы можете задавать вопросы и ограничивать их, чтобы можно было добавлять только отдельные вопросы.
Кроме того, теперь легче понять, почему SetQuestions не могут быть определены в IExam. Рассмотрим следующее:
SingleExam se = new SingleExam();
IExam<IQuestion> singleUpcast = se;
//What type of question can we set on singleUpcast?
Все, что мы знаем, это то, что singleUpcast содержит IQuestions, но мы не можем просто добавить IQuestions, потому что singleUpcast в конечном счете является экземпляром SingleExam, который обещал, что только SingleQuestions могут быть установлены таким образом. Короче говоря, невозможно узнать, какие типы могут быть добавлены в IExam, без потенциального нарушения гарантий типов