C# 在实体框架中保存实体的通用方法
我正在尝试编写一个GenericEFRepository,它将被其他存储库使用。我有一个保存方法如下C# 在实体框架中保存实体的通用方法,c#,entity-framework,repository-pattern,C#,Entity Framework,Repository Pattern,我正在尝试编写一个GenericEFRepository,它将被其他存储库使用。我有一个保存方法如下 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.Detach
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;
}
public virtual void Save(T entity)//其中T:class、IEntity、new()和IEntity强制执行长Id{get;set;}
{
var entry=_dbContext.entry(实体);
if(entry.State!=EntityState.Detached)
return;//上下文已经知道实体,不做任何操作
如果(实体Id<1)
{
_添加(实体);
返回;
}
var attachedEntity=_dbSet.Local.SingleOrDefault(e=>e.Id==entity.Id);
如果(附件身份!=null)
_Entry(AttacheIdentity).State=EntityState.Distached;
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
}
using(var-uow=ObjectFactory.GetInstance())//uow的实现类似于EFUnitOfWork,它将DbContext实例提供给GetRepository中的存储库
{
var userRepo=uow.GetRepository();
var user=userRepo.Get(1);
user.Name+=“已更新”;
userRepo.Save(用户);
uow.Save();//确定仅更新用户名
}
使用(var uow=ObjectFactory.GetInstance())
{
var userRepo=uow.GetRepository();
var user=新用户
{
Id=1,
Name=“全新名称”
};
userRepo.Save(用户);
uow.Save();
//不好
//更新用户中的所有字段(姓名、姓氏、出生日期等)
//这会导致在db上清除未分配的字段
}
我能想到的唯一解决方案是通过存储库创建实体,如userRepo.CreateEntity(id:1)
,存储库将返回一个附加到DbContext的实体。但这似乎容易出错,但任何开发人员都可以使用new
关键字创建实体
你对这个问题有什么解决建议
注意:我已经知道使用GenericRepository和IEntity接口的利弊。所以,“不要使用GenericRepository,不要使用IEntity,不要在每个实体中都放一个长Id,不要做你想做的事情”的评论不会有帮助。这是这种方法的一个基本问题,因为你希望存储库神奇地知道你更改了哪些字段,哪些字段没有更改。如果
null
是有效值,则使用null
作为“未更改”的信号不起作用
您需要告诉存储库您想要编写哪些字段,例如发送带有字段名的字符串[]
。或者每个字段一个布尔值。我认为这不是一个好的解决办法
也许您可以像这样反转控制流:
var entity = repo.Get(1);
entity.Name += "x";
repo.SaveChanges();
这将允许更改跟踪工作。它更接近EF希望使用的方式
备选方案:
var entity = repo.Get(1);
entity.Name += "x";
repo.Save(entity);
是的,它很容易出错,但这就是EF和存储库的问题所在。在设置任何要更新的数据之前,您必须创建实体并附加它(在您的情况下,必须为每个要持久化的属性设置修改状态,而不是整个实体(您可以想象,开发人员可能会忘记这样做) 第一个解决方案在您的存储库中引入了一种特殊的方法,它可以执行以下操作:
public T Create(long id) {
T entity = _dbContext.Set<T>().Create();
entity.Id = id;
_dbContext.Set<T>().Attach(entity);
return entity;
}
虽然其他两个答案很好地说明了您可以如何避免这个问题,但我认为值得指出几点
- 您试图做的事情(即代理实体更新)是极端的EF centeric,IMO实际上在EF上下文之外没有意义,因此一般存储库以这种方式运行是没有意义的
- 实际上,对于EF,您甚至还没有获得完全正确的流,如果您附加了一个具有几个已设置字段的对象,EF将简化您告诉它的当前DB状态,除非您修改值或设置修改的标志。要在没有选择的情况下执行所尝试的操作,您通常会附加一个没有名称的对象,然后在附加ID对象后设置名称
- 您的方法通常是出于性能原因而使用的,我建议,通过在现有框架之上进行抽象,您几乎总是会遇到一些逻辑性能下降的问题。如果这是一个大问题,也许你不应该使用存储库?为满足性能问题而向存储库中添加的内容越多,存储库就越复杂,限制也越大,提供多个实现就越困难
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
}
public void UpdateProperty(表达式选择器,functiontosetproperty setter/*我不太清楚正确的语法*/)
{
//在本地图中查找T,看看是否有已附加的版本
//如果没有,请将其与选择器值集一起附加
//设置setter的属性
}
希望这有点道理,我不是在我的开发盒atm机旁,所以我不能真正做一个工作样本
我认为这是一种更好的通用存储库方法,因为它允许您以多种不同的方式实现相同的行为。abovc可能适用于EF,但如果您有内存存储库(例如),则会有不同的方法。此方法允许您实现不同的实现,以满足目的,而不是将存储库限制为仅像EF一样工作。第一种方法存在一个小问题,将值类型属性设置为其默认值不会作为更改。例如,如果保留登录尝试失败的计数器,并且希望将其重置为0,则该计数器将不起作用。可以在附加之前将其设置为0以外的值(例如1),然后在附加之后将其更改为零。
userRepository(user, u => u.Name);
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
}