15

I have a model of my content:

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

To display the data only the model above is fine. But I want to add the functionality to edit the content. So I need to add an attribute to the member content - But this should only happen when the autor press an edit button, not in the regular view of the content.

So I created a second model which inherits from the BaseModel so that I can override the member with my attribute:

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

This works fine, but because of the inheritance EF create an additional column discriminator. It contains the type of the class as string. In my case its always BaseModel because I always convert EditableBaseModel to BaseModel before It gets saved to the database like this:

myBbContextInstance.BaseModels.Add(editableBaseModelInstance as EditableBaseModel);

Thus, the discriminator-column is a waste of space and I want to remove it. I found out that this can be done using the NotMapped-attribute. But this will result in the following exception when I try to save the model:

Mapping and metadata information could not be found for EntityType 'EditableBaseModel'.

It seems that the NotMapped-attribute will let EF know that another class exists that inherits from BaseModel, but EF won't get any information about this class. But thats not what I want. I need to tell EF: EditableBaseModel is nothing it should care about because its only to fit my view, and would be never used for the database.

How can I do that? The only way I found out is to convert the EditableBaseModel instance manually to a BaseModel object like this:

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

But this seems not a good way to do that because I have multiplice attributes. And it's also not very flexible because when I add something to the BaseModel, I have to add it also here - Which can result in strange errors.

Community
  • 1
  • 1
Lion
  • 16,606
  • 23
  • 86
  • 148
  • Why don't you just add a non mapped boolean field to the BaseModel which will indicate a user desire to change the content? – Maxim Balaganskiy Aug 11 '15 at 23:38
  • Because I'm using the **UIHint** attribute to insert partial views like the one from my editor. It seems that there is no way to set this attribute at runtime. – Lion Aug 11 '15 at 23:54
  • Attributes are per class, not per instance. In my opinion, you could use a condition in a partial view which, based on a flag field, would display or hide editing controls. – Maxim Balaganskiy Aug 12 '15 at 00:04
  • There are other ways to do this: https://msdn.microsoft.com/en-us/library/ee712708.aspx – Gert Arnold Aug 14 '15 at 22:19
  • You can't just add `[NotMapped]` attribute to `EditableBaseModel`? – Ray Suelzer Aug 14 '15 at 22:28

3 Answers3

8

Mixing EF concepts with MVC concepts into a Model may not fits for both. In this case creating new BaseModel and copy the content of EditableBaseModel into BaseModel as you did, is the right way. You can use AutoMapper for mapping data between two models.

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);
    .
    .
    .
}
Mohsen Esmailpour
  • 11,224
  • 3
  • 45
  • 66
4

The bottom line is that, using inheritance in Entity Framework, you can't represent the same record in the database by two different types.

Stated differently, if you use inheritance in any way, EF can materialize any row in the database to one type only. So what you want is never possible, with or without discriminator.

I think the conversion to and from EditableBaseModel is a viable option. Or wrap a BaseModel in a EditableBaseModel, where the latter has delegate properties like

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

This is a common pattern, called Decorator. Note that in that case (or with your conversion) you should not register EditableBaseModel as an entity in the EF model.

Technically, another approach would be possible. You can materialize any object by DbContext.Database.SqlQuery. You could use BaseModel for display purposes only and use EditableBaseModel as the mapped entity class. BaseModels then, could be materialized by

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

Of course the query can be parametrized to filter the models. The BaseModels will not be tracked by the context, but as you only want to display them, that's not necessary. This is the only way I see to represent (sort of) one record in the database by another type.

While I mention the technical possibility, that doesn't mean I recommend it. But then, even for the editable option I'd prefer using view models. I don't like this tight coupling between data layer and UI.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
1

Have you considered using a constructor in BaseModel which works in the following way:

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

and use it like this:

myBbContextInstance.BaseModels.Add(new BaseModel(editableBaseModelInstance));
DDan
  • 8,068
  • 5
  • 33
  • 52
  • Yes I tried a similar solution where I placed a method called **ToBaseModel** in the EditableBaseModel which return a BaseModel instance. It works but I don't like this solution because when I change the model I also have to add the attribute to the converter-method. So I would prefer an automated solution like type casting, but unfortunately this isn't working. – Lion Aug 17 '15 at 14:13
  • I see. I was thinking of a constructor because whenever someone changes the model will know that the constructor has to be changed as well, but yeah, not much automation there... – DDan Aug 18 '15 at 00:47