EF Entities vs. Domain Model Entities - Hard dependencies on Data Access/Persistence in core domain classes

Apr 23, 2014 at 11:50 PM
I have a sort of 'philosophical' question....

If I've created my entities as true domain model entities (not EF, or anemic DTO-ish models) following the DDD pattern, and have them in their own project, devoid of any data persistence knowledge or frameworks....but now I want to use this UoW/Repo framework of yours, that means I'll have to add references to my domain project for all this data persistence plumbing to my "pristine" domain project.

In addition, I'll have to "taint" (is that too strong of a word) my pure domain model classes with an inheritance to your Entity type, so that it can weave all the EF state-management stuff around my domain classes.

Here's another scenario....what if I don't control the domain model project. I just use it and now I have to persist it? How can use your framework without making changes to those domain model classes/entities?

Is there a better way to leverage this framework with existing domain entities without adding all the data/persistence dependencies into my domain project?
Apr 24, 2014 at 12:12 AM
Edited Apr 24, 2014 at 12:13 AM
Many teams including my own have separated the Entities project to only contain POCO's e.g. Northwind.Entities or what you refer to as Domain Model Entities. All things EF (DbContext, Mappings, etc.), live another project e.g. Northwind.Data which would reference Northwind.Entites. This pattern should leave you with a isolated project that only contains nothing but your entities.

No, the integration for this framework would be that your entities implement IObjectState, and if we take a look at what kind of overhead this actually brings to your entity project, it's actually only one line of code.
 public abstract class Entity : IObjectState
 {
    [NotMapped]
    public ObjectState ObjectState { get; set; }
 }
Marked as answer by lelong37 on 4/25/2014 at 8:59 AM
Apr 24, 2014 at 12:23 AM
Edited Apr 24, 2014 at 12:27 AM
Okay...let's speak in concrete terms:

Say I have my Customer entitity in my domain model:
public class Customer
{
    internal Customer(){}
    public Customer(string name)
    {
        //do some validations here...

        this.Name = name;
    }

    public int CustomerId { get; private set; }
    public string Name { get; private set; }

    //some behavior methods here....
}
What you are saying (pls correct me if I am wrong) is that I need to change my Customer type to inherit from your abstract Entity type, in order to implement IObjectState.
public class Customer : Entity
{
    //....
}
However, by doing that I'd be adding a reference to the Repo.Pattern.Ef6 (and also the Repo.Pattern) project, which in turn also adds a dependency for EF library. Am I missing something here?

This also doesn't address the scenario where I am handed a DLL which I don't own, with all the domain model entities, in which case I cannot modify myself, in order to make them inherit from your abstract Entity type.
Apr 24, 2014 at 4:07 PM
Edited Apr 24, 2014 at 4:34 PM
Hi Tfas7,

You can use a mapping tool to map Data Access Entity to your Domain model using your Web APi

See sample below
1-consuming an Odata webapi and mapping (Consuming Odata from other Host and mapping)
2- you can do the mapping on your web api or odata (web APi mapping)
3-Create IObjectState in your project instead of reference from Repo.Pattern.Ef6 (and also the Repo.Pattern)


1__Consuming Odata from other Host__
public async Task<ActionResult> Data_Read(int id = 1)
        {
            
            string url = string.Format("{0}/Entity?$inlinecount=allpages&$filter=ID%20eq%20{1}",_oDataBaseUrl, id);

            var baseRequest = new BaseRequest(url, string.Empty);
            var result = await baseRequest.GetAsync<RootObject<DataFromJson>>(url);

            var dataResult = ConvertooDataToDataSourceResult.Execute(result);
         
        *****You can do the mapping to your domain here
            return Json(dataResult);
        }
2__Web APi mapping__
 public List<YourDomain> GetDomainObject(ODataQueryOptions<EntityPoco> options)
        {
            var queryable = _yourRepository.ODataQueryable(options);

        Map from result to your domain here
            
            return result;
        }
3 -Creade the following classes inside your domain project
 public interface IObjectState
    {
        [NotMapped]
        ObjectState ObjectState { get; set; }
    }

public enum ObjectState
    {
        Unchanged,
        Added,
        Modified,
        Deleted
    }


 public abstract class Entity : IObjectState
 {
    [NotMapped]
    public ObjectState ObjectState { get; set; }
 }
Apr 24, 2014 at 9:23 PM
tafs7,

Not sure if this would create a circular dependency or not, but if you didn't want to reference Repository.Pattern and Repository.Pattern.EF6, could you pull Entity.cs into it's own project and have your Northwind.Entites and Repository.Pattern.EF6 reference the new project?
Apr 25, 2014 at 11:00 AM
Hi,

I had the same problem with dependencies.

A first solution could be to move Entity class from Repository.Pattern.Ef6 to Repository.Pattern assembly

A second one, could be to replace the clause

where TEntity : Entity

by

where TEntity : class, IObjectState

in FakeDbContext.cs, FakeDbSet.cs, QueryFluent.cs and Repository.cs
It's what I have done

Best regards
Apr 25, 2014 at 4:22 PM
@dguncet: hmm....I've thought of using AutoMapper to map between the EF entities to Domain Entities....but IMHO (although that is what DDD recommends) it takes away from the goodness of code-first EF where I can take my POCO domain objects and fluently map them to the DbSets for EF. I believe the EF fluent mapping API is that boundary mapper between the database and my domain.

By having another set of entities just for data access, which will probably look exactly like my domain model, it is just adding another layer of indirection for no reason.

With regards to option 3, I don't think it is as simple as that...because the I'd have to make the Repo.Pattern.Ef project depend on my domain project, when it really should just stand alone, as a true framework agnostic of my domain. As it stands, the Repo.Pattern.Ef.Entity type depends on Repo.Pattern.Infrastructure.IObjectState interface.

I don't mean to sound critical, but I just want to make sure I understand how leveraging this framework will impact my design. I think this framework does a great job and I've used very similar patterns in the past and have evolved my data access framework to something similar, though not as flexible (e.g. no QueryObject support), so I am appreciative of what it provides.

Am I missing something in the design, or is there another way to "weave" this framework into my domain models without having to mark my model entities as the Repo.Pattern.Ef.Entity type?
Apr 26, 2014 at 2:20 AM
I will go for option 2 Mapping from DAL to your Domain using Automapper. You'll need an extra 3 lines of code to do the Mapping.

You'll need also to map from your Domain Model to dtos and from dtos to the view model because none of your object need to have the same shape.

If you would like to use your option then you could try Le long’s approach "pull Entity.cs into it's own project and have your Northwind.Entites and Repository.Pattern.EF6 reference the new project?"

You can create all the following classes on its own project and not have the Repo.Pattern.Ef project depend on my domain project. Give it a try.
public interface IObjectState
    {
        [NotMapped]
        ObjectState ObjectState { get; set; }
    }

public enum ObjectState
    {
        Unchanged,
        Added,
        Modified,
        Deleted
    }


 public abstract class Entity : IObjectState
 {
    [NotMapped]
    public ObjectState ObjectState { get; set; }
 }
I strongly suggest to try out auto mapper http://automapper.org/
You can refactor at any time. 
Please let me know your final decision
May 1, 2014 at 10:50 PM
In response to yla, I believe you also have to change IFakeDbContext.cs in order for your solution to work. That's what I did but still debugging to make sure that all is well.

Thank you,
Frank
May 1, 2014 at 10:53 PM
fgalarraga,

If you be so kind to reply and post code snippets, if your implementation works, that would greatly be appreciated.
May 2, 2014 at 6:59 PM
lelong37

Here are the changes I made to the Repository.Pattern.Ef6 project in the Northwind sample project.
IFakeDbContext:
        void AddFakeDbSet<TEntity, TFakeDbSet>()
            where TEntity : class , IObjectState, new()
            where TFakeDbSet : FakeDbSet<TEntity>, IDbSet<TEntity>, new();


FakeDbSet:
public abstract class FakeDbSet<TEntity> : DbSet<TEntity>, IDbSet<TEntity> where TEntity : class, IObjectState, new()


QueryFluent
public sealed class QueryFluent<TEntity> : IQueryFluent<TEntity> where TEntity : class , IObjectState

Repository
public class Repository<TEntity> : IRepositoryAsync<TEntity> where TEntity : class , IObjectState

FakeDbContext
        public void AddFakeDbSet<TEntity, TFakeDbSet>()
            where TEntity : class , IObjectState, new()
            where TFakeDbSet : FakeDbSet<TEntity>, IDbSet<TEntity>, new()
        {
            var fakeDbSet = Activator.CreateInstance<TFakeDbSet>();
            _fakeDbSets.Add(typeof(TEntity), fakeDbSet);
        }
Let me know if you see anything strange.

Thank you,
F.
May 2, 2014 at 7:14 PM
Hi fgalarraga,

Did you add the following code to a new project? Thanks a lot for sharing.
public interface IObjectState
    {
        [NotMapped]
        ObjectState ObjectState { get; set; }
    }

public enum ObjectState
    {
        Unchanged,
        Added,
        Modified,
        Deleted
    }


 public abstract class Entity : IObjectState
 {
    [NotMapped]
    public ObjectState ObjectState { get; set; }
 }
May 2, 2014 at 7:19 PM
Edited May 2, 2014 at 7:31 PM
Hey dguncet,

That code was already there in v3.3 that is the Northwind Sample I made the other changes to the Repository.Pattern.Ef6.

In another project where I am implementing the pattern. I split out the Entity.cs class into another project Repository.Pattern.Core that only references Repository.Pattern. In that way my entities do not need to reference EF at all. In addition, I am also working on moving the Fluent Mapping files to the Repository instance. In the case of the sample I am testing moving them to Northwind.Repository, but like I said this is a work in progress.

Thanks,
F.
May 2, 2014 at 7:31 PM
Le,
Are you making any change to the code base?
Thanks,
DG
May 2, 2014 at 10:04 PM
Le & DG,

I have taken the latest Northwind Sample and refactored it to have clean POCO Entities with no reference to the Entity Framework. With the help of Resharper I was able to do the refactoring very quickly. I have done some initial testing and the functionality remains the same with the refactored code. I added a few projects to the framework and to the sample. Repository.Pattern.Core which hold the Entity.cs and I use an interface for my entities called IEntity.cs to make things more extensible and the few lines of code to implement the IObjectState interface to my entities I don't think clutters them up. I also added Northwind.Context to the sample project to store the context classes and mappings. I thought about putting it all into the Northwind.Repository but I thought it would be better to separate out the context incase someone wanted to use multiple contexts with one repository.

I placed the code on my OneDrive (http://1drv.ms/R8SDWT). Let me know what you think. I would love to contribute more to the project. As you will see now that I have clean entities I will work on using them to store them in Azure Table Storage.

Thank you,
F.
May 2, 2014 at 10:15 PM
fgalarraga,

We'll pull down the code from OneDrive and review the team, we appreciate you sharing this the team.

dguncet,

As mentioned, we'll review before pushing any changes, I'll update this thread once we've reviewed.
May 2, 2014 at 10:36 PM
Great Job!!!
Le,
It will be a great help if you add the sample Api controller accessing the "not exposed Rest Api odata" sample to the new release, since it is only a few lines and will be a great help.

Thank you guys.
May 4, 2014 at 11:53 PM
fgalarraga,

First and foremost thank you for your contribution.

The team has reviewed you changes, a few quick updates before we merge this into the master branch:

Note: Could you be so kind to issue a "Fork" (branch) directly on CodePlex and implement all your changes there, you could name which ever makes sense, please let me know what the name is so I can merge the correct "fork" into the master branch. This is the typical process for getting contributions into master and the branch is where we can review, and if we make any changes we can check them in, so that you can get latest to review any changes or recommendations as well.,
  • Rename Northwind.Context to Northwind.Data
  • Rename Repository.Core to Repository.Common, if anything we'll rename Repository.Pattern to Repository.Pattern.Core in the future, since this name makes more sense.
Question 1: Could your refresh our memory, on the objective for the changes in FakeDbContext?
Question 2. Are there any other changes we need to review?

Once this is complete, we'll go ahead and start the merge back into master.
Marked as answer by lelong37 on 5/4/2014 at 3:59 PM
May 4, 2014 at 11:59 PM
dguncet wrote:
Great Job!!!
Le,
It will be a great help if you add the sample Api controller accessing the "not exposed Rest Api odata" sample to the new release, since it is only a few lines and will be a great help.

Thank you guys.
Team is reviewing this request, as well as the best approach and best way to implements this (HttpClient), we are leaning towards creating an MVC Area to placed this in.
May 5, 2014 at 12:40 AM
Thanks for the heads up. Let me know if you need any help or any other sample.
May 5, 2014 at 7:57 PM
Le,

I created a fork of the master and made the changes to the code. Before I create the pull request I had some issues with the Web project. I basically had to revert some of the files that has conflicts in the prior pull and I had some files that where missing.

Let me know if you want me to go ahead and issue the pull request or if you want me to wait and get the latest Web project when those conflicts are completed.

Thank you,
F.
May 5, 2014 at 8:01 PM
When you do the Fork, this will Fork what's the latest from the master branch into whatever you've named your Fork. Once you your Fork, you would implement your new changes into this Fork. The Fork should also have the latest Northwind.Web project which is fully integrated with AngularJS and Angular Kendo. Please let me know if you need additional assistance.