How to avoid N+1 in EF generated queries

故事扮演 提交于 2019-12-12 16:24:10

问题


I used to generate all the helper tables like UsergroupUsers for many-to-many relations or relational Ids in POCO myself but now I want EF to take care of them. Now I don't think it's such a good idea after all.

Problem

When I try to get all UsergroupDynamicField for particular user it generates N+1 query for every usergroup user is in.

Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.

Code

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
                };
            }
        }
    }
}

Database


回答1:


Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.

But the class that ends up implementing ICollection is EntityCollection<T>. This collection has a CreateSourceQuery() function that you can use:

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

Update: as pointed out in the comments, ICollection<T> will only be implemented using EntityCollection<T> when change tracking is possible and enabled (non-sealed classes, and all relevant properties virtual). You can create a query another way:

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

Note that this requires that you have access to db somehow.




回答2:


I try with something like

var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();

and it seems to be ok. I use EF5.

Of course... this is not a method in the User class. It requires to be able to invoke Include method on a DbSet object.

I hope this may help.

full solution

 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);
                } 
            }
        }
    }


来源:https://stackoverflow.com/questions/17858004/how-to-avoid-n1-in-ef-generated-queries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!