A generic way to save an entity in Entity Framework

c# entity-framework repository-pattern

Question

I am trying to write a GenericEFRepository which will be used by other Repositories. I have a Save method as below.

public virtual void Save(T entity) // where T : class, IEntity, new() And IEntity enforces long Id { get; set; }
{
    var entry = _dbContext.Entry(entity);

    if (entry.State != EntityState.Detached)
        return; // context already knows about entity, don't do anything

    if (entity.Id < 1)
    {
        _dbSet.Add(entity);
        return;
    }

    var attachedEntity = _dbSet.Local.SingleOrDefault(e => e.Id == entity.Id);
    if (attachedEntity != null)
        _dbContext.Entry(attachedEntity).State = EntityState.Detached;
    entry.State = EntityState.Modified;
}

You can find the problem in comments of below code

 using (var uow = ObjectFactory.GetInstance<IUnitOfWork>()) // uow is implemented like EFUnitOfWork which gives the DbContext instance to repositories in GetRepository
 {
    var userRepo = uow.GetRepository<IUserRepository>();

    var user = userRepo.Get(1);
    user.Name += " Updated";

    userRepo.Save(user);
    uow.Save(); // OK only the Name of User is Updated 
 }

 using (var uow = ObjectFactory.GetInstance<IUnitOfWork>())
 {
    var userRepo = uow.GetRepository<IUserRepository>();

    var user = new User 
    {
        Id = 1,
        Name = "Brand New Name"
    };

    userRepo.Save(user);
    uow.Save();

    // NOT OK
    // All fields (Name, Surname, BirthDate etc.) in User are updated
    // which causes unassigned fields to be cleared on db
 }

The only solution I can think of is creating Entities via repository like userRepo.CreateEntity(id: 1) and repository will return an Entity which is attached to DbContext. But this seems error prone, still any developer may create an entity using new keyword.

What are your solution suggestions about this particular problem?

Note: I already know about cons and pros of using a GenericRepository and an IEntity interface. So, "Don't use a GenericRepository, don't use an IEntity, don't put a long Id in every Entity, don't do what you are trying to do" comments will not help.

1
2
1/20/2013 5:30:47 PM

Accepted Answer

Yes it is error prone but simply that is the problem with EF and repositories. You must either create entity and attach it before you set any data you want to update (Name in your case) or you must set modified state for each property you want to persist instead of whole entity (as you can imagine again developer can forget to do that).

The first solution leads to special method on your repository doing just this:

public T Create(long id) {
    T entity = _dbContext.Set<T>().Create();
    entity.Id = id;
    _dbContext.Set<T>().Attach(entity);
    return entity;
}

The second solution needs something like

public void Save(T entity, params Expression<Func<T, TProperty>>[] properties) {

    ...

    _dbContext.Set<T>().Attach(entity);
    if (properties.Length > 0) {
        foreach (var propertyAccessor in properties) {
            _dbContext.Entry(entity).Property(propertyAccessor).IsModified = true;
        }
    } else {
        _dbContext.Entry(entity).State = EntityState.Modified;
    }
}

and you will call it like:

userRepository(user, u => u.Name);
6
1/20/2013 11:23:46 PM

Popular Answer

While the other two answers provide good insight into how perhaps you can avoid this issue I think its worth pointing out a couple of things.

  • What you are trying to do (ie a proxy entity update) is extremely EF-centeric and IMO actually doesn't make sense outside of the EF context and hence it doesnt make sense that a generic repository would be expected to behave in this way.
  • You actually haven't even gotten the flow quite right for EF, if you attach an object with a few fields already set EF will conciser what you told it to be the current DB state unless you modify a value or set a modified flag. To do what you are attempting without a select you would normally attach an object without the name and then set the name after attaching the ID object
  • Your approach is normally used for performance reasons, I would suggest that by abstracting over the top of an existing framework you are almost always going to suffer some logical performance degradation. If this is a big deal maybe you shouldn't be using a repository? The more you add to your repository to cater to performance concerns the more complex and restrictive it becomes and the harder it gets to provide more than one implementation.

All that being said I do think you can handle this particular case in a generic situation.

This is one possible way you could do it

public void UpdateProperty(Expression<Func<T,bool>> selector, FunctionToSetAProperty setter/*not quite sure of the correct syntax off the top of my head*/)
{
   // look in local graph for T and see if you have an already attached version
   // if not attach it with your selector value set
   // set the property of the setter
}

Hope this makes some sense, I'm not by my dev box atm so I cant really do a working sample.

I think this is a better approach for a generic repository as it allows you to implement this same behavior in multiple different ways, the abovc may work for EF but there will be different methods if you have an in memory repository (for example). This approach allows you to implement different implementations that fulfill the intent rather than restrict your repository to only act like EF.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow