Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET EF remove discriminator column from not mapped class

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.

like image 404
Lion Avatar asked Aug 11 '15 22:08

Lion


3 Answers

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);
    .
    .
    .
}
like image 178
Mohsen Esmailpour Avatar answered Nov 10 '22 05:11

Mohsen Esmailpour


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.

like image 36
Gert Arnold Avatar answered Nov 10 '22 05:11

Gert Arnold


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));
like image 1
DDan Avatar answered Nov 10 '22 06:11

DDan