ASP.NET EF удаляет столбец дискриминатора из не сопоставленного класса

У меня есть модель моего контента:

class BaseModel {
    public virtual string Content{ get; set; }
    // ...
}

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

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

class EditableBaseModel : BaseModel {
    [UIHint("MyEditor"), AllowHtml]
    public override string Content{ get; set; }
}

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

myBbContextInstance.BaseModels.Add(editableBaseModelInstance as EditableBaseModel);

Таким образом, столбец-дискриминатор является пустой тратой пространства, и я хочу удалить его. Я узнал, что это можно сделать с помощью атрибута NotMapped. Но это приведет к следующему исключению, когда я попытаюсь сохранить модель:

Информация об отображении и метаданных не найдена для EntityType 'EditableBaseModel'.

Кажется, что атрибут NotMapped позволит EF знать, что существует другой класс, который наследуется от BaseModel, но EF не получит никакой информации об этом классе. Но это не то, что я хочу. Мне нужно сказать EF: EditableBaseModel - это ничего, о чем он должен заботиться, потому что он только подходит для моего представления и никогда не будет использоваться для базы данных.

Как я могу это сделать? Единственный способ, которым я узнал, - это преобразовать экземпляр EditableBaseModel вручную в объект BaseModel, например:

public ActionResult Save(EditableBaseModel editableBaseModel) {
    var baseModel = new BaseModel() {
        Content = editableBaseModel.Content
        // ...
    };
    myDbContextInstance.BaseModels.Add(baseModel);
}

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

Ответы

Ответ 1

Смешивание концепций EF с понятиями MVC в Model может не соответствовать для обоих. В этом случае создайте новый BaseModel и скопируйте содержимое EditableBaseModel в BaseModel, как и вы, это правильный путь. Вы можете использовать AutoMapper для отображения данных между двумя моделями.

class EditableBaseModel
{
    [UIHint("MyEditor"), AllowHtml]
    public string Content{ get; set; }
}

public ActionResult Save(EditableBaseModel editableBaseModel) {
    var baseModel = new BaseModel();
    Mapper.Map<EditableBaseModel, BaseModel>(editableBaseModel, baseModel);
    myDbContextInstance.BaseModels.Add(baseModel);
    .
    .
    .
}

Ответ 2

Суть в том, что, используя наследование в Entity Framework, , вы не можете представлять одну и ту же запись в базе данных двумя разными типами.

Иными словами, если вы используете наследование каким-либо образом, EF может материализовать любую строку в базе данных только для одного типа. Так что вы не хотите, с дискриминатором или без него.

Я думаю, что преобразование в и из EditableBaseModel является жизнеспособным вариантом. Или заверните BaseModel в EditableBaseModel, где последний имеет свойства делегирования, такие как

public string Content
{ 
    [UIHint("MyEditor"), AllowHtml]
    get { return _baseModel.Content; }
    set { _baseModel.Content = value; }
}

Это общий шаблон, называемый Decorator. Обратите внимание, что в этом случае (или с вашим преобразованием) вы не должны регистрировать EditableBaseModel как объект в модели EF.

Технически, возможен другой подход. Вы можете материализовать любой объект DbContext.Database.SqlQuery. Вы можете использовать BaseModel только для целей отображения и использовать EditableBaseModel как сопоставленный класс объектов. BaseModel, то, может быть материализовано

myBbContextInstance.Database.SqlQuery<BaseModel>("SELECT * FROM dbo.BaseModel");

Конечно, запрос может быть параметризован для фильтрации моделей. BaseModel не будет отслеживаться контекстом, но поскольку вы только хотите отображать их, это не обязательно. Это единственный способ, которым я представляю (сортировать) одну запись в базе данных другим типом.

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

Ответ 3

Рассматривали ли вы использование конструктора в BaseModel, который работает следующим образом:

public BaseModel(EditableBaseModel editableBaseModel) {
    this.Content = editableBaseModel.Content
}

и используйте его следующим образом:

myBbContextInstance.BaseModels.Add(new BaseModel(editableBaseModelInstance));