fv28@xs4all.nl (6) [Avatar] Offline
#1
Dear Mark,

Thanx for the excellent book. I've read it with much interest and pleasure..

I'm now practicing the learnings in a new project using EntityFramework with DBContext and CodeFirst approach. I use separate layers for data access and datamodel (business logic) avoiding dependency between the layers (the datamodel must be completely ignorant of EntitityFramework). I was succeeding doing this (using an abstract Repository and constructor injection and using the Fluent API) until Validation came around. By the way using the Fluent API for defining constraints instead of
fv28@xs4all.nl (6) [Avatar] Offline
#2
Re: DI and Entity Framework using DBContext Code First - Validation
Dear Mark,

Thanks for the excellent book. I've read it with much interest and pleasure..

I'm now practicing the learned in a new project using EntityFramework with DBContext and CodeFirst approach. I use separate layers for data access and datamodel (POCO's and business logic) avoiding dependency between the layers (the datamodel must be completely ignorant of EntitityFramework). I was succeeding doing this (using an abstract Repository and constructor injection in the datamodel and using the Fluent API) until Validation came around. By the way using the Fluent API for defining database constraints instead of DataAnnotations in the POCO classes (the last requires EntityFramework to be referred in the datamodel).

For Validations EF DBContest has the IValidatableObject interface. The common approach is to apply this interface to the POCO classes, p.e.

namespace Design.DataModel.Entities
{
public class ModelbaseModel : Logger, IObjectWithState, IValidatableObject
{
public virtual int ModelbaseModelId { get; set; }
public virtual int? ModelGroupId { get; set; }
public virtual string ModelCode { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string ModelType { get; set; }
public virtual string Status { get; set; }
public virtual string Version { get; set; }
public virtual DateTime ModifiedDate { get; set; }
public virtual Nullable<DateTime> ReleaseDate { get; set; }
public virtual byte[] RowVersion { get; set; }

public virtual ICollection<InputAgenda> InputAgendas { get; set; }
public virtual ICollection<ModelRevisionAgenda> ModelRevisionAgendas { get; set; }
public virtual ICollection<DesignerNote> DesignerNotes { get; set; }
public virtual ICollection<DesignEvent> DesignEvents { get; set; }
public virtual ICollection<ModelbaseModel> DerivedModels { get; set; }
public virtual ICollection<ModelCategory> ModelCategories { get; set; }
public virtual ModelGroup ModelGroup { get; set; }
public virtual ModelbaseModel DerivedFromModel { get; set; }

#region IObjectWithState Members

public State State { get; set; }
public Dictionary<string, object> OriginalValues { get; set; }

#endregion

//TODO: How to realize loose coupling?
#region IValidatableObject Members

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Status == "Production" && ModelRevisionAgendas.Count == 0)
{
yield return new ValidationResult(
"Model with status <Production> must have a Revision Agenda.");
}

if (Status != "Production" && ModelRevisionAgendas.Count > 0)
{
yield return new ValidationResult(
"Model other than model with status <Production> cannot have a Revision Agenda.");
}

if (Status == "Production" && Version == string.Empty)
{
yield return new ValidationResult(
"Model with status <Production> must have version number.");
}

if (Status == "Production" && ReleaseDate == null)
{
yield return new ValidationResult(
"Model with status <Production> must have a release date.");
}

if (Status != "Production" && ReleaseDate != null)
{
yield return new ValidationResult(
"Model with status other than <Production> cannot have a release date.");
}

if (ReleaseDate != null && ((DateTime) ReleaseDate).Date < DateTime.Now)
{
yield return new ValidationResult(
"Model cannot have release date earlier than now");
}

if (Status != "Production" && DesignEvents.Count == 0)
{
yield return new ValidationResult(
"Concept model must have at least one design event (on creation).");
}

if (Status == "Production" && DesignEvents.Count > 0)
{
yield return new ValidationResult(
"Model with status <Production> cannot have Design Events");
}
}

#endregion
}
}

This interface has the advantage that business rules are checked against the DBContext at SaveChanges and it is a must because validation cannot happen without the context to validate. But it has one problem it makes the DataModel EF- dependent (when using it the DataModel has to refer to EntityFramework).

My question is: How to avoid this dependency?

By the way I've read Julia Lerman's books on Entity Framework (also very good books) too. But she circumvents the issue of loose coupling. Quite understandable I think, because she is the EF expert.

Thanks for the answer.

Kind regards,
Frans Verhoeven
mark.seemann (383) [Avatar] Offline
#3
Re: DI and Entity Framework using DBContext Code First - Validation
Dear Frans

Thank you for writing. It's good to hear that you like the book.

Before we look at any specific interfaces, I think it's necessary to take a step back in order to answer the questions: When do we need to implement validation? Why do we need to validate?

While I don't expect you to derive a full answer yet, this blog post provides a hint - particularly if you read the discussion, too: http://blog.ploeh.dk/2011/05/27/DesignSmellRedundantRequiredAttribute.aspx

When you've done that, you probably have follow-up questions. That's quite OK - you're welcome to continue the discussion here smilie
fv28@xs4all.nl (6) [Avatar] Offline
#4
Re: DI and Entity Framework using DBContext Code First - Validation
Dear Mark,

I've read the discussion. I agree with your sample. Using the DataAnnotation API is just a bad idea from an architectural (SOA) point of view.
On the other hand EF in combination with DBContext and CodeFirst approach is a very comfortable way to prototype data. You just have to define your POCO's and set a few configurations and EF will generate the database for you. Changes in your POCO's during the development process can be automatically on the fly be reflected in the database.

A few remarks on the discussion:
1. You don't have to use the DataAnnotation API. In my example I don't use attributes as you can see. I prefer the alternative to define database requirements, the Fluent API. Via the Fluent API you can define the database constraints separate from the POCO's. An example:

namespace Design.DataAccess.Configuration
{
public class ModelbaseModelConfiguration : EntityTypeConfiguration<ModelbaseModel>
{
public ModelbaseModelConfiguration()
{
Property(m => m.Name)
.IsRequired()
.HasMaxLength(50);

Property(m => m.Description)
.IsRequired();

Property(m => m.ModelCode)
.IsRequired();

Property(m => m.ModifiedDate)
.IsRequired();

Property(m => m.RowVersion)
.IsRowVersion();

HasOptional(m => m.DerivedFromModel)
.WithMany(m => m.DerivedModels);


ToTable("ModelbaseModel", "dfsDesign");
}
}
}

This results in cleaner POCO's that can be encapsulated.

2. Although I agree with your underlying message, using "Id" in your example of DataAnnotation is somewhat an unhappy choise. The attribute "Required" is never used in combination with the primary key. The compiler requires a primary key. A missing or empty primary key will result in a compiler error. The Id (with certain naming conventions) must be defined in the POCO, otherwise EF does not generate the database. A better example would be setting the attributes p.e. "[Required]" or ["MaxLength"] on another property of the POCO.

3. Using external interfaces to boundary objects is bad practice. In my example (see previous posts) I used IValidatableObject as an external interface. This makes the POCO's unusable as boundary objects. IValidableObject is just like DataAnnotations bad practice.

My first conclusions on my problem:
1. Don't use IValidatableObject. Keep the POCO's clean.
2. Don't define business validations in the POCO's. The POCO's just are boundary objects of the DataAccess-Layer.
3. Make a separate business layer where to put the business type validations.Use the general .NET validation API not the EF validation API.

Do you agree?

Thanks for the answer.

Kind regards,
Frans Verhoeven.
mark.seemann (383) [Avatar] Offline
#5
Re: DI and Entity Framework using DBContext Code First - Validation
Well, I certainly agree that an ORM like EF can make a lot of things easier - just not separations of concerns. I've yet to see an ORM that doesn't impose some sort of constraints over the classes you'd have to create. These constraints are often at odds with object-orientation.

Let's step back a bit and attempt to answer this question: why do you need validation code?
fv28@xs4all.nl (6) [Avatar] Offline
#6
Re: DI and Entity Framework using DBContext Code First - Validation
Two reasons I think:
1. To ensure that no constraint violations reach the database and result in abends.
2. To ensure business integrity by enforcing business rules.
mark.seemann (383) [Avatar] Offline
#7
Re: DI and Entity Framework using DBContext Code First - Validation
That first point - is it a business concern or a technical concern?

The second point: why even allow business objects to be able to created in invalid states?
fv28@xs4all.nl (6) [Avatar] Offline
#8
Re: DI and Entity Framework using DBContext Code First - Validation
Good Questions. The first point regards technical concerns (field length, and so on). They are part of the configuration settings.
The second, I must admit are not such good design. Most invalid states in my example can be prevented by business logic on creating and changing the objects.
mark.seemann (383) [Avatar] Offline
#9
Re: DI and Entity Framework using DBContext Code First - Validation
What I'm driving at here is that ideally, in a pure OO code base, validation shouldn't really be related to the database.

Validation is still relevant, but it should happen at the boundary of the application. You can easily receive invalid data from a user or an external partner, but the proper thing is to validate input and then construct proper domain objects from valid data.

From there, the persistence engine should accept whatever the Domain Model allows. If not, then the responsibility is blurred: Who decides what valid is? Is it the domain model? Is it the database?

That's a recipe for getting into a big mess very quickly.

Now, keep in mind that this applies for code bases where the Domain Model is the ultimate definition of correctness and the database is only an implementation detail.

I do realize that there are lots of applications where this is not so. Here, the database is the central authority and the rest of the code must just fit in as well as it can. There can be good business reason for such a design, so I'm not pointing fingers or anything. What I'm trying to say is just that it pays to be aware of this, because it basically means that most decoupling efforts are going to be more or less futile. When that happens, you might as well just bite the bullet and take a hard dependency on your ORM of choice, and then not try to hide it.
fv28@xs4all.nl (6) [Avatar] Offline
#10
Re: DI and Entity Framework using DBContext Code First - Validation
Thanks for your answer. I will keep it in mind.