A generic way to save an entity in Entity Framework
c#, entity-framework, repository-pattern
Solution
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);
Problem
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.