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

huangapple go评论85阅读模式
英文:

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

问题

你遇到的问题涉及虚拟的 ICollections 集合在使用 include 时不被视为属性,但在运行时仍然可以访问。例如,LoanClass 类中的 LoanStatuses 属性无法使用 include 进行预加载,但仍可以在运行时访问。你想知道如何预加载 Loan statuses 的子属性。

以下是你提供的代码的翻译部分:

// 问题出在这里,我无法通过 include 来预加载 LoanStatuses 表/对象的属性,例如:
Loan loan = LoanUnitOfWork.LoanRepository.LoadByLoanNumberWithRequiredData(loanNumber, l => l.LoanStatuses.*Status.Code*);
// 这将不允许我这样做。

// 如果我在 include 查询中省略上面斜体代码,那么属性将不会被预加载,EF 将多次调用以收集这些 Status.Codes。

希望这有助于解决你的问题。

英文:

The issue I am having involves virtual ICollections of collections not being considered as properties when using include, while still being able to be accessed as such at runtime.

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

The issue here is that I can eager load other properties of the loan class because they directly refer to the loan table in the db.

But when I go to eager, load properties on the LoanStatuses table/object like so:

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

It will not allow me.

And if I leave the italicised code above out of the include query, then the properties are not eager loaded and EF makes many calls out to collect these 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)

How can I eager load the child properties of Loan statuses?

答案1

得分: 0

EF6中,您可以使用ICollection.Select()来包含ICollection的子项,这在EF Core中仍然是有效的语法,但会引发InvalidOperationException:

> InvalidOperationException: 在“Include”操作中,表达式'l.LoanStatuses.AsQueryable().Select(ls=> ls.Status)'是无效的,因为它不代表属性访问:'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。

我曾经遇到过相同的问题,但我找到了一种解决方案,虽然不完全符合您的要求,但能够完成工作。

我使用了Func<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));

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

英文:

Well I don't think you can. In EF6 you could Include the children of an ICollection using ICollection.Select(), which is still valid Syntax in EF Core, but you get an InvalidOperationException:

> InvalidOperationException: The expression 'l.LoanStatuses.AsQueryable().Select(ls=> ls.Status)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.

I had the same Problem, but I found a Solution, that is not exactly what you wanted, but gets the Job done.

Instead of params Expression&lt;Func&lt;Loan, object&gt;&gt;[] includes I used Func&lt;IQueryable&lt;TEntity&gt;, IIncludableQueryable&lt;TEntity, object&gt;&gt; include which would look something like that:

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

I know it's not what you asked for, but it's a neat workaround.

huangapple
  • 本文由 发表于 2023年6月9日 13:42:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76437503.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定