如何仅使用 include 预先加载和包含 ICollection 的子项?

How to eager load and include the children of an ICollection only using include?

提问人:sebastian whiffen 提问时间:6/9/2023 最后编辑:Palle Duesebastian whiffen 更新时间:6/23/2023 访问量:125

问:

我遇到的问题涉及在使用 include 时未被视为属性的集合的虚拟 ICollections,同时仍然能够在运行时进行访问。

例如:LoanClass

public class Loan : BaseEntity
{
    private ICollection<LoanStatus> _LoanStatuses;

    public virtual ICollection<LoanStatus> LoanStatuses
    {
        get { return _LoanStatuses ?? (_LoanStatuses = new Collection<LoanStatus>()); }
        set { _LoanStatuses = value; }
    }
}

public class LoanStatus : BaseEntity
{
    public int ID { get; set; }
    public int LoanID { get; set; }
    public int StatusID { get; set; }

    [ForeignKey("LoanID")]
    public virtual Loan Loan { get; set; }

    [ForeignKey("StatusID")]
    public virtual CodeType Status { get; set; }
}

public class CodeType : BaseEntity
{
    public int ID { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public string Category { get; set; }
    public int? Sequence { get; set; }

    public bool IsCodeType(CodeTypeCategory category, string code)
    {
        return Category.Equals(category.ToString()) && Code.Equals(code);
    }
}

service layer:

Loan loan = LoanUnitOfWork.LoanRepository.LoadByLoanNumberWithRequiredData(loanNumber,l => l.LoanStatuses);

Repository:

public Loan LoadByLoanNumberWithRequiredData(string loanNumber, params Expression<Func<Loan, object>>[] includes)
{
    return includes.Aggregate(Context.Loans.AsQueryable()
        ,(current, include) => current.Include(include)).FirstOrDefault(l => l.LoanNumber == loanNumber);
}

这里的问题是,我可以急切地加载 loan 类的其他属性,因为它们直接引用数据库中的 loan 表。

但是当我去急切时,像这样加载 LoanStatuses 表/对象上的属性:

Loan loan = LoanUnitOfWork.LoanRepository.LoadByLoanNumberWithRequiredData(loanNumber,l => l.LoanStatuses.*Status.Code*);

它不会允许我。

如果我将上面的斜体代码排除在包含查询之外,则属性不会预先加载,并且 EF 会发出许多调用来收集这些 Status.Codes:

Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (20ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='600']
SELECT [c].[Ct_ID], [c].[Active], [c].[Ct_Category], [c].[Ct_Code], [c].[Ct_CreatedBy], [c].[Ct_DateCreated], [c].[Ct_DateUpdated], [c].[Ct_Description], [c].[Ct_Sequence], [c].[Ct_UpdatedBy]
FROM [Code_Types] AS [c]
WHERE ([c].[Active] = CAST(1 AS bit)) AND ([c].[Ct_ID] = @__p_0)
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (11ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='600']
SELECT [c].[Ct_ID], [c].[Active], [c].[Ct_Category], [c].[Ct_Code], [c].[Ct_CreatedBy], [c].[Ct_DateCreated], [c].[Ct_DateUpdated], [c].[Ct_Description], [c].[Ct_Sequence], [c].[Ct_UpdatedBy]
FROM [Code_Types] AS [c]
WHERE ([c].[Active] = CAST(1 AS bit)) AND ([c].[Ct_ID] = @__p_0)
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (10ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='600']
SELECT [c].[Ct_ID], [c].[Active], [c].[Ct_Category], [c].[Ct_Code], [c].[Ct_CreatedBy], [c].[Ct_DateCreated], [c].[Ct_DateUpdated], [c].[Ct_Description], [c].[Ct_Sequence], [c].[Ct_UpdatedBy]
FROM [Code_Types] AS [c]
WHERE ([c].[Active] = CAST(1 AS bit)) AND ([c].[Ct_ID] = @__p_0)
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (312ms) [Parameters=[@__p_0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='600']
SELECT [c].[Ct_ID], [c].[Active], [c].[Ct_Category], [c].[Ct_Code], [c].[Ct_CreatedBy], [c].[Ct_DateCreated], [c].[Ct_DateUpdated], [c].[Ct_Description], [c].[Ct_Sequence], [c].[Ct_UpdatedBy]
FROM [Code_Types] AS [c]
WHERE ([c].[Active] = CAST(1 AS bit)) AND ([c].[Ct_ID] = @__p_0)

如何快速加载贷款状态的子属性?

C# 实体框架核心 急切加载

评论

0赞 JHBonarius 6/9/2023
你读过这个吗?
1赞 sebastian whiffen 6/9/2023
@JHBonarius很多次。它不包含有关虚拟集合的任何信息。
2赞 jmcilhinney 6/9/2023
郑重声明,EF 是一个工作单元,每个工作单元都是一个存储库,因此自己实现存储库模式是不必要的,并且可能会导致问题。创建自己的服务,但这些服务应直接使用 。改变这个项目可能为时已晚,但未来需要考虑一些事情。我们在我的办公室里有自己的UOW,当我们第一次搬到英孚时就用过它,当我们报废它时,生活变得更好了。DbContextDbSetDbContext
1赞 JHBonarius 6/9/2023
存储库并没有那么糟糕,有时是唯一的解决方案。这是实施不当的存储库,使整个存储库模式受到不良影响。如果只是复制 DbSet,则不会增加任何价值。如果您“尝试”在不使用 的情况下公开行为,就像在您显示的存储库中所做的那样,那么恕我直言,这只是糟糕的设计。设计这个存储库的人正在使他们的生活以及必须使用它的其他人的生活变得非常艰难。IQueryableIQueryable
1赞 JHBonarius 6/9/2023
目前,存储库应该有一个特定的方法,它应该急切地加载所需的值。如果您不被允许向存储库添加任何内容,请为我打架构师的头。GetByLoanNumberIncludingLoadStatus

答:

0赞 Timo Waibel 6/23/2023 #1

好吧,我认为你不能。在 EF6 中,可以使用 包含 ICollection 的子级,这在 EF Core 中仍然有效,但会收到 InvalidOperationException:ICollection.Select()

InvalidOperationException:表达式 'l.LoanStatuses.AsQueryable()。选择 (ls=> ls。Status)' 在 'Include' 操作中无效,因为它不表示属性访问: 't => t.MyProperty'。若要以在派生类型上声明的导航为目标,请使用强制转换 ('t => ((Derived)t)。MyProperty') 或 'as' 运算符 ('t => (t as Derived)。MyProperty“)。可以通过组合 Where、OrderBy(Descending)、ThenBy(Descending)、Skip 或 Take 操作来筛选集合导航访问。有关包含相关数据的详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkID=746393

我有同样的问题,但我找到了一个解决方案,这并不完全是你想要的,但可以完成工作。

而不是我使用看起来像这样的东西:params Expression<Func<Loan, object>>[] includesFunc<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include

public Loan LoadByLoanNumberWithRequiredData(string loanNumber, Func<IQueryable<Loan>, IIncludableQueryable<Loan, object>> include)
{
    return include(Context.Loans.AsQueryable())
        .FirstOrDefault(l => l.LoanNumber == loanNumber);
}
Loan loan = LoanUnitOfWork.LoanRepository.LoadByLoanNumberWithRequiredData(loanNumber, loan => loan
    .Include(l => l.LoanStatuses).ThenInclude(ls => ls.Status));

我知道这不是你要求的,但这是一个巧妙的解决方法。