7

In my solution I have a Domain project and a DataAccess project. My Domain project contains domain objects and interfaces for various repositories and an interface for a UnitOfWork which will contain all my repositories. My DataAccess project contains the implementations of all these interfaces and the EF versions of my Domain objects.

So if I have a class called Person and I want to add a Person to the DB it would look like this:

unitOfWork.People.Add(person);
unitOfWork.Complete();

Inside unitOfWork.People.Add(person) I map my domain Person object to my EF Person object with AutoMapper. Then I add my EF Person object to the EF DbContext object.

public void Add(Person person) {
  DataAccess.Entities.Person dbPerson = Mapper.Map<DataAccess.Entities.Person>(person);
  dbContext.People.Add(dbPerson);
}

The Complete() function just wraps SaveChanges().

public int Complete() {
  dbContext.SaveChanges();
}

Normally EF updates an inserted object's DB generated ID after insertion. But in this case EF knows nothing about my Domain Person object.

Since my Domain Person object doesn't have it's ID, I can't update the DB entry or re-fetch it from the database.

The only alternative I can think of is generating a guid on the server and using that as an ID. But this creates redundancy in the form of two IDs. It also isn't very user friendly since users will reference this ID in search fields. Remembering ID 135 is much simpler than remembering a guid.

Is there some way to get dbContext to update the domain object's ID directly or alternatively have the ID bubble up through the layers?

Legion
  • 3,922
  • 8
  • 51
  • 95
  • What about using the _(serverside generated)_ `Guid` as a primary key? – Jeroen van Langen Apr 26 '16 at 14:48
  • Why does `Add()` return void? Why doesn't it call `Complete()` and return the inserted Person with its generated ID (and possible other calculated properties)? See also http://blog.ploeh.dk/2014/08/11/cqs-versus-server-generated-ids/ – CodeCaster Apr 26 '16 at 14:49
  • Why are you using separate domain classes? Why not let EF generate the domain classes, and persist those? – Maarten Apr 26 '16 at 14:54
  • @JeroenvanLangen It isn't user friendly when people have to talk about Person `f1936cfe-69ea-4c50-8dd1-e6bf31e53ff9`. It's much easier to work with a small integer. I could use both and only display the DB generated ID to the user, but now I have duplication and an extra trip to refetch the inserted object. – Legion Apr 26 '16 at 14:56
  • After committing the changes to the database you can get ID of saved Entity simply by checking `dbPerson.Id` if you have chosen your column to be as Identity (Auto Increment). Or you can Generate your ID prior to save the changes With GUID or any custom ID generator your like. – Mehrdad Kamelzadeh Apr 26 '16 at 14:56
  • @Maarten Why should my domain layer know anything about the persistence layer? – Legion Apr 26 '16 at 14:57
  • @Legion It shouldn't. To reverse the question: Why should your persistence layer use anything other than the domain objects to persist the information? What is the benefit of converting your domain objects into something else? It is only causing trouble since you now have to balance a database-generated-id and a domain-id. – Maarten Apr 26 '16 at 14:58
  • @Maarten I don't know of another way to keep them separate while using EF. – Legion Apr 26 '16 at 15:01
  • @Legion great question, stuck with this exact issue myself. Have gone with generating a guid for new entities in the past but obviously this wouldn't work with an existing auto inc integer keys. Did you ever find a good solution? – Christopher Thomas Jan 20 '17 at 21:03
  • @ChristopherThomas did u find something to solve that? – X.Otano Aug 02 '18 at 11:02
  • @Badulake I cheated a bit, in this exact case I think I added a method called SaveAndReturnId to an interface which inherited from IRepository and applied that to the repo class. I reasoned that it was a transaction in itself and could be used directly outside the context of unit of work. This way, the unit of work interface can still be used as intended without exposing the additional SaveAndReturnId method as it would be using the basic IRepository interface. Can't speak as to whether this is good practice, would welcome comments. – Christopher Thomas Aug 02 '18 at 14:20
  • Don't reinvent the wheel. Entity Framework is already a Repository. And DbContext is the unit of work. – Christian Gollhardt Aug 04 '18 at 12:29
  • @ChristopherThomas I updated the answer – X.Otano Oct 09 '18 at 09:28
  • @ChristianGollhardt maybe you are right, but we do not want to depend on an external framework ;) – X.Otano Oct 09 '18 at 09:29
  • Then create a thin Service Layer above it. For example a `PersonService`, which uses EF. If you change your ORM, you simple need to change the service. But setting a whole Repository with UoW above another Repository with UoW isn't very clever. @Badulake – Christian Gollhardt Oct 09 '18 at 09:58
  • I see your point. I have that layer you are saying, but it only knows about domain classes( not DB ones which is the main point I want to avoid) – X.Otano Oct 09 '18 at 10:00
  • @ChristianGollhardt I have also used UoW and repositorty pattern to test my database layer. I think its hard to test persistence layer if directly using entity framework & context related objects – Eldho Oct 10 '18 at 07:30

2 Answers2

2

You can try something like this.

Repository

public class GenericRepository<TEntity> where TEntity : class
{
    internal DbContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(DbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }
    public virtual TEntity Insert(TEntity entity)
    {
        dbSet.Add(entity);
        return entity;
    }
}

Use it like this

 var uow = new UnitOfWork();
 var added = uow.StudentReposiotry.Insert(new Student
 {
    Name = "Repository",
    RegistrationNo = "BSE-2018-004",
    Date = DateTime.Now,
    Department = "Philosphy",
    Email = "test@"
 });
 uow.Save(); //if its saved.
 long id = added.Id;

I added a separate layer called DataProvider which help to keep my entities live only in Repository everything came from DataProvider speaks about DTO.

public class StudentDataProvider : DataProviderBase
{
     public StudentDto Insert(Student dto)
     { 
         var entity = new Student{ Name = dto.Name,Deparment= dto.Deparment};

         var addedItem = unitofwork.StudentReposiotry.Insert(entity);
         unitofWork.Save();

         dto.Id = addedItem.Id;
         return dto;
     }

     public StudentDto AddAndUpdate(StudentDto dto, StudentDto updateDto)
     { 
         var entity = new Student{ Name = dto.Name,Deparment= dto.Deparment};
         var update= new Student{ Name = updateDto.Name,Deparment= updateDto.Deparment};

         var addedItem = unitofwork.StudentReposiotry.Insert(entity);
         var updateItem=  unitofwork.StudentReposiotry.Update(update)
         unitofWork.Save();

         dto.Id = addedItem.Id;
         return dto;
     }
}

 public class DataProvideBase
{
    protected IUnitOfWork UnitOfWork;

    public DataProvideBase()
    {
        UnitOfWork = new UnitOfWork();
    }
}
Eldho
  • 7,795
  • 5
  • 40
  • 77
-1

You can check my anser in another similar question, since UnitofWork is intimatelly related to Repository pattern.

Hope it helps:

How to get id from Add using UnitOfWork pattern?

X.Otano
  • 2,079
  • 1
  • 22
  • 40