简体   繁体   English

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

[英]Entity Framework Core Nested List Performance

I struggle with the entity framework core performance while I try to add an additional item to a nested list.当我尝试向嵌套列表中添加其他项目时,我在实体框架核心性能方面遇到了困难。

Let's say as an example: I have multiple projects, the project contains multiple houses, the house has multiple facades and the facade has multiple windows.举个例子:我有多个项目,项目包含多个房屋,房屋有多个立面,立面有多个windows。 If I now want to add an additional window to a specific project, house, facade I do it like that:如果我现在想向特定项目、房屋、立面添加额外的 window,我会这样做:

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

This works fine regarding the functionality.这在功能方面效果很好。 However the performance gets slower and slower when the database is increasing.但是,当数据库增加时,性能会越来越慢。 Is there a more performant way to add an item to a nested list?是否有更高效的方式将项目添加到嵌套列表?

Update 1更新 1

I created a simple test database with this futuristic objects with 50 projects, each project has 10 houses, each house has 10 facades and each facade has 10 windows.我用这个包含 50 个项目的未来派对象创建了一个简单的测试数据库,每个项目有 10 个房子,每个房子有 10 个立面,每个立面有 10 个 windows。 this results in a database size of about 10Mb.这导致数据库大小约为 10Mb。

In the test I add 1000 Windows after each other (no bulk):在测试中,我一个接一个地添加了 1000 Windows(没有散装):

The solution mention above requires a total time of 145s.上面提到的解决方案需要145s的总时间。

The solution mentioned by @David Browne - Microsoft takes about 54s @David Browne 提到的解决方案——微软大约需要 54s

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

facadeEntity.Windows.Add(windowEntity);

await context.SaveChangesAsync();

Update 2更新 2

As recommended by @David Browne I added a ForeignKey to the window:正如@David Browne 推荐的那样,我在 window 中添加了一个 ForeignKey:

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

The save is executed like that:保存是这样执行的:

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

This issue is the same the more windows I have the longer the add takes.这个问题是一样的 windows 我有更长的添加时间。 The durartion for 1000 Windows is around 53s. 1000 个 Windows 的持续时间约为 53 秒。

I currently have only a "DbSet ProjectSet" you would add an additional DbSet to the context?我目前只有一个“DbSet ProjectSet”,你会在上下文中添加一个额外的 DbSet 吗?

If you don't have declared DbSet<T> for an entity, access it through DbContext.Set<T>() , something like:如果您没有为实体声明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();
    }
}

This translates to:这转化为:

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

and then:接着:

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

Assuming Facade has a single-column primary key.假设Facade有一个单列主键。 If it has a compound key of (ProjectId,HouseId,FacadeId), then add those to the Where .如果它具有 (ProjectId,HouseId,FacadeId) 的复合键,则将它们添加到Where

The best way to do this, however is to set the Foreign Key property of Window.FacadeId and not load the Facade at all.但是,最好的方法是设置 Window.FacadeId 的外键属性,并且根本不加载外观。 In EF Core you can do this with Shadow Properties if you don't have a Foreign Key Property.在 EF Core 中,如果您没有外键属性,则可以使用影子属性执行此操作。 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();
    }
}

If you're trying to add a Window to a specific project/house/facade, can you just set the FacadeId on the WindowEntity directly and save that?如果您尝试Window 添加到特定项目/房屋/立面,您可以直接在 WindowEntity 上设置 FacadeId 并保存吗? Presumably, the Window has a FacadeId property, just as Facade has a HouseId, and House has a ProjectId.据推测,Window 有一个 FacadeId 属性,就像 Facade 有一个 HouseId,而 House 有一个 ProjectId。 If Window has the Ids of all its parents (unnecessary), then just set the House and Project Ids as well.如果 Window 有其所有父母的 Id(不必要),那么也只需设置 House 和 Project 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();
    }
}

Update: If you don't want to set the facadeId on the entity directly, then you can load just the facade, and set its Facade property rather than the Id.更新:如果您不想直接在实体上设置 facadeId,那么您可以加载外观,并设置其 Facade 属性而不是 Id。 You can optionally add the windowEntity to the Facade's Windows collection as well if you are going to continue working with that instance of the collection.如果您要继续使用该集合的实例,您也可以选择将 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();
    }
}

You should firstly select from your Windows DbSet then join Facades then join houses your query should looks like that您应该首先从 Windows DbSet 中的 select 然后加入 Facades 然后加入房屋您的查询应该看起来像这样

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM