奇怪的实体框架行为-当IEnumerable不是List时添加double
本文关键字:不是 IEnumerable List double 添加 实体 框架 | 更新日期: 2023-09-27 18:09:31
我有一个Repository基类,它有以下方法:
public void AddRange(IEnumerable<TEntity> entities)
{
Context.Set<TEntity>().AddRange(entities);
foreach (var entity in entities)
{
if (Context.Entry(entity).State == EntityState.Detached)
{
Context.Entry(entity).State = EntityState.Added;
}
}
}
我这样称呼它:
uow.UserPermissions.AddRange(permissions);
其中UserPermissions是继承自base的存储库。
当权限不是列表时,我看到一些奇怪的行为,我无法解释。例如,当我尝试这样做时:
var permissions = permissionDtos.Select(dto => new UserPermission()
{
...
});
uow.UserPermissions.AddRange(permissions);
实体框架添加到数据库的权限是permissionDtos的两倍。但是,如果在select语句的末尾添加一个ToList(),那么奇怪的行为就会消失。我还注意到,当我注释掉Repository.AddRange()方法中的forEach循环(修改上下文条目状态)时,奇怪的行为也消失了(即使没有添加ToList())。
你做错了。这就是为什么会有这种行为。当您执行此Context.Set<TEntity>().AddRange(entities);
时,它将给定的实体集合添加到该集合的上下文中,每个实体都处于已添加状态,以便在调用SaveChanges
时将其插入到数据库中。您可以在MSDN上看到它。所以你不需要在foreach
循环中再做一次。当您删除其中任何一个时,您的方法应该是正确的。
您可以尝试如下所示。
public void AddRange(IEnumerable<TEntity> entities)
{
Context.Set<TEntity>().AddRange(entities);
}
详细说明为什么您获得两倍的条目,这是因为IQueryable<T>
的工作方式。
每次枚举查询时,将重新执行该查询。实际上,查询在到达Context.Set<TEntity>().AddRange(entities);
在内部,adrange枚举查询结果,在您的示例中,它将选择permissionDtos后面表中的所有行,并将这些行投影到新的UserPermission对象中。
然后,在foreach循环中,通过再次枚举来第二次执行查询,这再次选择所有行并将它们投影为新的UserPermission对象。由于您正在投影这些结果,因此框架无法重用它在第一次执行查询期间创建的对象。如果您直接枚举Set<TEntity>
两次,它仍然会执行两次查询,但它将能够重用已经物化的对象。
在使用ToList()
的情况下,您正在强制查询和投影立即发生,并且您不再保存查询本身- permissions
在这种情况下不再是查询,而是您已经投影的项目的列表。
Sampath已经回答了你的foreach循环是多余的——将实体添加到上下文中会将它们标记为已添加,而将状态设置为已添加会将它们添加到上下文中(两者之间的唯一区别在于如何通过导航属性访问相关实体)。即使在List的例子中,你也在做一个不必要的循环。