实体框架核心嵌套列表性能

时间:2019-11-19 12:58:45

标签: c# .net database entity-framework

在尝试向嵌套列表中添加其他项目时,我为实体框架的核心性能而苦恼。

让我们举个例子: 我有多个项目,该项目包含多个房屋,房屋具有多个立面,并且立面具有多个窗口。 如果现在我想向特定项目,房屋,外墙添加一个额外的窗口,则可以这样:

    public async Task SaveWindowAsync(Guid projectId, Guid houseId, Guid facadeId, WindowEntity windowEntity)
    {
        using (ProjectsDbContext context = new ProjectsDbContext())
        {
            var windowList = context.ProjectSet
                .Include(p => p.Houses)
                .ThenInclude(h => h.Facades)
                .ThenInclude(f => f.Windows)
                .First(p => p.Id == projectId).Houses
                .First(h => h.Id == houseId).Facades
                .First(f => f.Id == facadeId).Windows;

            windowList.Add(windowEntity);

            await context.SaveChangesAsync();
        }
    }

关于功能,这很好用。但是,当数据库增加时,性能会越来越慢。有没有一种将项目添加到嵌套列表的更高效的方法?

更新1

我用这个具有50个项目的未来派对象创建了一个简单的测试数据库,每个项目有10个房屋,每个房屋有10个立面,每个立面有10个窗口。这样数据库的大小约为10Mb。

在测试中,我彼此之后添加了1000个Windows(无批量):

上面提到的解决方案总共需要145秒的时间。

@David Browne提到的解决方案-微软大约需要54秒

var facadeEntity = context.Set<FacadeEntity>()
    .Include(f => f.Windows)
    .Single(f => f.Id == facadeId);

facadeEntity.Windows.Add(windowEntity);

await context.SaveChangesAsync();

更新2

按照@David Browne的建议,我在窗口中添加了一个ForeignKey:

modelBuilder.Entity<FacadeEntity>()
.HasMany(f => f.Windows).WithOne()
.HasForeignKey(f => f.FacadeId)
.OnDelete(DeleteBehavior.Cascade);

保存是这样执行的:

context.Entry(windowEntity).Property(nameof(WindowEntity.FacadeId)).CurrentValue = facadeId;
context.Set<WindowEntity>().Add(windowEntity);
await context.SaveChangesAsync();

这个问题是相同的,我拥有的窗口越多,添加所需的时间就越长。 1000 Windows的持续时间约为53s。

3 个答案:

答案 0 :(得分:2)

  

我目前只有一个“ DbSet ProjectSet”,您会在上下文中添加一个额外的DbSet吗?

如果您尚未为实体声明DbSet<T>,请通过DbContext.Set<T>()进行访问,例如:

public static async Task SaveWindowAsync(Guid projectId, Guid houseId, Guid facadeId, Window windowEntity)
{
    using (ProjectsDbContext context = new ProjectsDbContext())
    {
        var facade = context.Set<Facade>()
                            .Where(f => f.FacadeId == facadeId)
                            .Single();

        facade.Windows.Add(windowEntity);

        await context.SaveChangesAsync();
    }
}

这意味着:

SELECT TOP(2) [f].[FacadeId], [f].[HouseId]
FROM [Facade] AS [f]
WHERE [f].[FacadeId] = @__facadeId_0

然后:

INSERT INTO [Window] ([WindowId], [FacadeId])
VALUES (@p0, @p1)

假定Facade具有单列主键。如果复合键为(ProjectId,HouseId,FacadeId),则将其添加到Where

但是,最好的方法是设置Window.FacadeId的Foreign Key属性,而不完全加载Facade。如果没有外键属性,则可以在EF Core中使用Shadow Properties进行此操作。 EG:

public static async Task SaveWindowAsync(Guid projectId, Guid houseId, Guid facadeId, Window windowEntity)
{
    using (ProjectsDbContext context = new ProjectsDbContext())
    {
        context.Entry(windowEntity).Property("FacadeId").CurrentValue = facadeId;
        context.Set<Window>().Add(windowEntity);

        await context.SaveChangesAsync();
    }
}

答案 1 :(得分:0)

如果您要向特定项目/房屋/立面添加一个窗口,是否可以直接在WindowEntity上设置FacadeId并将其保存?大概,Window具有FacadeId属性,就像Facade具有HouseId,而House具有ProjectId一样。如果Window拥有所有其父母的ID(不必要),则只需设置房屋和项目ID。

public async Task SaveWindowAsync(Guid projectId, Guid houseId, Guid facadeId, WindowEntity windowEntity)
{
    using (ProjectsDbContext context = new ProjectsDbContext())
    {            
        windowEntity.FacadeId = facadeId;
        context.WindowSet.Add(windowEntity);
        await context.SaveChangesAsync();
    }
}

更新: 如果您不想直接在实体上设置FacadeId,则可以加载 just Facade,并设置其Facade属性而不是Id。如果要继续使用该集合实例,可以选择将windowEntity添加到Facade的Windows集合中。

public async Task SaveWindowAsync(Guid facadeId, WindowEntity windowEntity)
{
    using (ProjectsDbContext context = new ProjectsDbContext())
    {
        var facade = Facades.Single(x => x.Id == facadeId);
        windowEntity.Facade = facade;
        facade.Windows.Add(windowEntity);
        await context.SaveChangesAsync();
    }
}

答案 2 :(得分:0)

您应该首先从Windows DbSet中选择,然后加入Facades,然后加入房屋  您的查询应该像这样

 var windowList = context.DbSet<Windows>().Where(w => w.Facade.Id== facadeId && w.Facade.House.Id == houseId && w.Facade.House.Project.Id == projectId)