奇怪的实体框架行为-当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())。

奇怪的实体框架行为-当IEnumerable不是List时添加double

你做错了。这就是为什么会有这种行为。当您执行此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的例子中,你也在做一个不必要的循环。

相关文章: