[英]A generic way to save an entity in Entity Framework
我正在尝试编写一个GenericEFRepository,它将被其他存储库使用。 我有一个Save方法如下。
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;
}
您可以在以下代码的评论中找到问题
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
}
我能想到的唯一解决方案是通过像userRepo.CreateEntity(id: 1)
这样的存储库创建实体,而存储库将返回一个附加到DbContext的实体。 但这看起来容易出错,仍然任何开发人员都可以使用new
关键字创建实体。
您对此特定问题的解决方案建议是什么?
注意:我已经知道使用GenericRepository和IEntity接口的缺点和优点。 因此,“不要使用GenericRepository,不要使用IEntity,不要在每个实体中放置长ID,不要做你想做的事情”评论也无济于事。
是的,它容易出错,但这只是EF和存储库的问题。 您必须先创建实体并在设置要更新的任何数据之前附加它(在您的情况下为Name
),或者您必须为要保留的每个属性而不是整个实体设置修改状态(如您所能想象的那样,开发人员可以忘记去做)。
第一个解决方案导致您的存储库执行以下特殊方法:
public T Create(long id) {
T entity = _dbContext.Set<T>().Create();
entity.Id = id;
_dbContext.Set<T>().Attach(entity);
return entity;
}
第二种解决方案需要像
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;
}
}
你会称之为:
userRepository(user, u => u.Name);
这是这种方法的一个基本问题,因为您希望存储库能够神奇地知道您更改了哪些字段以及哪些字段没有更改。 如果null
是有效值,则使用null
作为“未更改”的信号不起作用。
您需要告诉存储库您要写入哪些字段,例如发送带字段名称的string[]
。 或每个领域一个布尔。 我不认为这是一个很好的解决方案。
也许你可以像这样反转控制流程:
var entity = repo.Get(1);
entity.Name += "x";
repo.SaveChanges();
这将允许更改跟踪工作。 它更接近于EF 希望如何被使用。
替代方案:
var entity = repo.Get(1);
entity.Name += "x";
repo.Save(entity);
虽然其他两个答案提供了很好的洞察力,你可以如何避免这个问题,但我认为它值得指出一些事情。
所有这一切,我认为你可以在一般情况下处理这种特殊情况。
这是一种可行的方法
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
}
希望这是有道理的,我不是我的开发箱atm所以我不能真正做一个工作样本。
我认为这对于通用存储库来说是一种更好的方法,因为它允许您以多种不同的方式实现相同的行为,abovc可能适用于EF,但如果您有内存存储库(例如),则会有不同的方法。 此方法允许您实现满足意图的不同实现,而不是将您的存储库限制为仅像EF一样。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.