如何避免EF生成的查询中的N + 1

时间:2013-07-25 12:26:44

标签: c# entity-framework entity-framework-5

我曾经在POCO中为多对多关系或关系ID生成所有帮助表,如UsergroupUsers,但现在我希望EF能够处理它们。现在我认为这毕竟不是一个好主意。

问题

当我尝试为特定用户获取所有UsergroupDynamicField时,它会为每个用户组用户生成N+1查询。

Here我通过简单地声明Usergroups将是IQUeriable而不是IEnumerable来解决这个问题。现在我不能这样做,因为EF不会映射它,它必须是ICollection

代码

public class User
{
    ...
    public virtual ICollection<Usergroup> Usergroups { get; set; }
    public IEnumerable<UserField> Fields
    {
        get
        {
            var fields = this.Usergroups.SelectMany(x => x.UsergroupDynamicFields); // N + 1 for every Usergroup

            foreach (var field in fields)
            {
                yield return new UserField
                {
                    Name = field.Name
                };
            }
        }
    }
}

数据库

DB DB2

2 个答案:

答案 0 :(得分:1)

  

这里我通过简单地声明Usergroups将是IQUeriable而不是IEnumerable来克服这个问题。现在我不能这样做,因为EF不会映射它,它必须是ICollection。

但最终实施ICollection的课程是EntityCollection<T>。此集合有CreateSourceQuery() function您可以使用:

var usergroupsQuery = ((EntityCollection<UserGroup>)this.Usergroups).CreateSourceQuery();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);

更新:正如评论中所指出的,只有在可以进行更改跟踪并启用更改跟踪时,才会使用ICollection<T>实施EntityCollection<T>(非密封类,以及所有相关内容)属性virtual)。您可以用另一种方式创建查询:

var usergroupsQuery = db.Entry(this).Collection(u => u.Usergroups).Query();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);

请注意,这需要您以某种方式访问​​db

答案 1 :(得分:0)

我尝试使用像

这样的东西
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();

似乎没问题。我用的是EF5。

当然......这不是User类中的方法。它需要能够在DbSet对象上调用Include方法。

我希望这可能有所帮助。

完整的解决方案

 public class User {
        public Int32 Id { get; set; }
        public String Name { get; set; }

        public virtual ICollection<UserGroup> Groups { get; set; }
    }

    public class UserGroup {
        public Int32 Id { get; set; }
        public String Name { get; set; }

        public virtual ICollection<User> Users { get; set; }

        public virtual ICollection<UserGroupDynamicField> DynFields { get; set; }
    }

    public class UserGroupDynamicField {
        public Int32 Id { get; set; }
        public String Name { get; set; }
        public virtual UserGroup Group { get; set; }
    }


    public class UserGroupDynFEFCFConfiguration : EntityTypeConfiguration<UserGroupDynamicField > {
        public UserGroupDynFEFCFConfiguration()
            : base() {
                HasRequired(x => x.Group);
        }
    }

    public class UserGroupEFCFConfiguration : EntityTypeConfiguration<UserGroup> {
        public UserGroupEFCFConfiguration()
            : base() {
                HasMany(x => x.Users).WithMany(y => y.Groups);
        }
    }       

    public class TestEFContext : DbContext {
        public IDbSet<User> Users { get; set; }
        public IDbSet<UserGroup> Groups { get; set; }

        public TestEFContext(String cs)
            : base(cs) {
            Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder) {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Configurations.Add(new UserGroupDynFEFCFConfiguration());
            modelBuilder.Configurations.Add(new UserGroupEFCFConfiguration());
        }
    }

    class Program {
        static void Main(String[] args) {
            String cs = @"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";

            using (TestEFContext c = new TestEFContext(cs)) {
                UserGroup g1 = new UserGroup {
                    Name = "G1",
                    DynFields = new List<UserGroupDynamicField> { 
                        new UserGroupDynamicField { Name = "DF11"},
                        new UserGroupDynamicField { Name = "DF12"}
                    }
                };
                c.Groups.Add(g1);

                UserGroup g2 = new UserGroup {
                    Name = "G2",
                    DynFields = new List<UserGroupDynamicField> { 
                        new UserGroupDynamicField { Name = "DF21"},
                        new UserGroupDynamicField { Name = "DF22"}
                    }
                };
                c.Groups.Add(g2);

                c.Users.Add(new User {
                    Name = "U1",
                    Groups = new List<UserGroup> { g1, g2 }
                });

                c.SaveChanges();
            }      

            using (TestEFContext c = new TestEFContext(cs)) {
                var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
                foreach (var v in res) {
                    Console.WriteLine(v.Name);
                } 
            }
        }
    }