Rafael Rocha
Rafael Rocha

Reputation: 183

Use DTO Entity that is derived from EF Entity to perform insertion

I have ASP.NET MVC website and a Class Library for Entity FrameWork

Now i need to had some data valitation using IValidatableObject.

So i have in Class Library entity framework this:

public class ClientA
{
    public Guid ID { get; set; }       
    public string FirstName { get; set; }      
    public string LastName { get; set; }
}

And in MVC project I have:

public class Client: ClientA, IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext){..}
}

But i got error make cast here:

Client client = new Client();
client.FirstName = "aaaa";
ClientA cl = (ClientA)client;

context.Clients.Add(cl); //error here: Type are different

Any ideas?

Upvotes: 3

Views: 1127

Answers (5)

elolos
elolos

Reputation: 4450

Mapping the objects might be preferable to casting, for example

ClientA c1 = new ClientA
{
    ID = client.ID,
    FirstName = client.FirstName,
    LastName = client.LastName
}

This of course could get very tedious, especially if you have big objects, but you could use a tool like Automapper, which can be a time saver when mapping objects with same named properties.

Also, as other answers have suggested, there is no need for your view model (that's what Client essentially is) to inherit from ClientA, even if they have the same properties. Validation is not a concern of class 'ClientA' and this would be more obvious with a more complex class, for example if you ever need to hide properties from the UI or address concerns like globalization/localization.

Upvotes: 2

Carlos Mu&#241;oz
Carlos Mu&#241;oz

Reputation: 17804

What you are trying to do is wrong for many reasons:

Problems

1. Type safety

context.Clients.Add(cl);

You are trying to add a ClientA variable to a Client collection. This is not possible since Client is a superType of ClientA. The C# compiler cannot upcast ClientA variable to its super type Client even when cl actually holds a Client object. Why are you casting to ClientA anyway? It should compile if you add client variable instead

2. Entity Framework

...Client: ClientA ...

This line by default will make EF treat the hierarchy as two different entities using TPH (Table per Hierarchy). This is definitely not what you are trying to achieve since you are only using Client as validation

3. Encapsulation

... Client: ClientA, IValidatableObject

As @Aron has stated validation logic is the resposability of the Client (or ClientA) class. Therefore there should be only one class

public class Client : IValidatableObject

This class is responsible for holding its properties, validation and logic. IValidatableObject interface its considered a core library so feel free to implement it on your Entity

Enough with the problems now the solutions...

Solutions

1. Only one Client class

public class Client : IValidatableObject

This solution is simple, well encapsulated, works great with EntityFramework and keeps logic where it should be.

This is how you must think when constructing a Domain layer

But unfortunately this layer doesn't live by itself. It should be consumed by your presentation layer and probably you were splitting this class in two because your Validate method contains presentation logic

So this give us the next solution...

2. ViewModels

//In your domain layer
public class Client
{
    ....
}

//In your presentation layer
public class ClientViewModel : IValidatableObject
{
    //...
    // Same properties as Client
    //...

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext){ ... }
}

Notice how ClientViewModel does not inherit from Client. It keeps the same properties as the entity but they aren't related in an inheritance hierarchy. So you have two ways of instantiating a ClientViewModel out of a Client entity.

  • Manually using a Constructor, BuilderMethod, or anything else that involves calling a new ClientViewModel() somewhere and manually coping the properties, one by one or with reflection (Please don't do reflection)

  • With a OOM (Object-Object Mapper): Being Automapper one of the most popular libraries. With this option you can register a map with Mapper.CreateMap<Client, ClientViewModel>(); and get a new ClientViewModel from a Client with var clientViewModel = Mapper.Map<ClientViewModel>(client);

Q. But wait, what about the logic in the Entity.

A. You can still make the entity an IValidatableObject too. that way it will handle pure domain validation, leaving the viewmodel with the UI validation.

Stay tuned for more solutions comming soon...

Upvotes: 1

Aron
Aron

Reputation: 15772

First answer I am going to give you.

Your instinct is wrong. The validation logic SHOULD live in your data objects in the Business Layer (not the persistence layer, perhaps your issue is that you didn't separate out BL and persistence).

This is why IValidatableObject lives in System.ComponentModel.DataAnnotations and not in System.Web.

This way when your application is a hit and you need to port it to windows phone (pfffhahahahaha), you don't need to rewrite your validation code.

It would also allow you to override SaveChanges() on your DbContext to1 run your validation just before you save.

1 You actually don't need to do that because EF already WILL do that for you...

Upvotes: 1

Aron
Aron

Reputation: 15772

You can use AOP instead of inheritance.

Look at projects like PostSharp to add your validation logic to your data class after you compile.

Upvotes: 0

Aron
Aron

Reputation: 15772

Use composition instead of inheritance. Most programmers after learning OOP think only in Inheritance/Polymorphism/Encapsulation.

You can instead put a thing inside another thing that validates it.

public abstract class Validator<T>
    : IValidatableObject 
{
    public T Value { get; }
    public abstract IEnumerable<ValidationResult> 
          Validate(ValidationContext validationContext);
}

Upvotes: 1

Related Questions