无法为具有集合属性的类编写正确的 Linq 查询 - 出现 EfCore5 翻译错误
Posted
技术标签:
【中文标题】无法为具有集合属性的类编写正确的 Linq 查询 - 出现 EfCore5 翻译错误【英文标题】:Cannot compose correct Linq query for a class having collection properties - getting EfCore5 translation errors 【发布时间】:2021-11-17 03:05:01 【问题描述】:我通过查询 DB 获得了以下数据 (IQueryable<Article>
) 的平面集合:
ArticleId | LanguageName | ArticleText | ExtraData1 | ExtraData2 |
---|---|---|---|---|
1 | English | EngText1 | Something1 | Something2 |
1 | English | EngText2 | Another1 | Another2 |
1 | French | FraText1 | Blabla1 | |
2 | English | EngText2 | Ololo1 | Blabla2 |
2 | German | GerText1 | Naturlisch2 |
现在我需要填写IQueryable<AgregatedArticle>
:这个想法是按ArticleId
分组并将重复数据放入嵌套列表中:
public class AgregatedArticle
public int ArticleId get; set;
public List<Data> ArticleTexts get; set;
public class Data
public string LanguageName get; set;
public string ArticleText get; set;
很遗憾,我无法做到:我收到各种 EfCore5 翻译错误并且不知道:是我还是 EfCore5 错误或限制。我浪费了 3 天时间尝试不同的方法。请帮助 - 我无法在 Internet 上找到合适的示例。当我尝试填写 ArticleTexts
属性时出现问题。
这是一个简化的例子:
private async Task<IQueryable<LawArticleAggregated>> GetLawArticlesGroupedById(DbSet<LawArticleDetail> dbSet, string userContentLangRestriction = null)
var dbContext = await GetDbContextAsync();
var articlesQuery =
(from articleIds in dbSet.Select(x => x.ArticleId).Distinct()
from articlesPerId in dbSet
.Where(x => x.ArticleId == articleIds.ArticleId)
join askedL in dbContext.Langs
.Where(l => l.LanguageCode == userContentLangRestriction)
on
articlesPerId.LanguageCode
equals
askedL.StringValue
into askedLanguages
from askedLawLanguage in askedLanguages.DefaultIfEmpty()
join fallbackL in dbContext.Langs
.Where(l => l.LanguageCode == CoreConstants.LanguageCodes.English)
on
articlesPerId.LanguageCode
equals
fallbackL.StringValue
into fallbackLanguages
from fallbackLanguage in fallbackLanguages.DefaultIfEmpty()
select new
ArticleId = articleIds.ArticleId,
ArticleText = articlesPerId.ArticleText,
LanguageName = askedLawLanguage.ShortName ?? fallbackLanguage.ShortName
)
.OrderBy(x => x.ArticleId).ThenBy(x => x.LanguageName).ThenBy(x => x.ArticleText);
await articlesQuery.LoadAsync();
var aggregatedArticleData = articlesQuery.Select(x => new
ArticleId = x.ArticleId,
ArticleText = x.ArticleText,
LanguageName = x.LanguageName
);
var aggregatedArticles = articlesQuery.Select(x => x.ArticleId).Distinct().Select(x => new ArticleAggregated
ArticleId = x.ArticleId,
ArticleTexts = aggregatedArticleData.Where(a => a.ArticleId == x.ArticleId)
.Select(x => new LawArticleAggregated.Data
ArticleText = x.ArticleText,
LanguageName = x.LanguageName
).ToList()
);
return aggregatedArticles;
对于此特定代码,异常如下:
自父项以来无法在投影中翻译集合子查询 查询不会投影所有表的关键列 需要在客户端生成结果。这可能发生在 尝试关联无钥匙实体或使用“不同”或 'GroupBy' 操作而不投影所有关键列。
【问题讨论】:
发布您的代码。IQueryable<T>
代表一个查询。它不包含任何内容,因此无法“填充”。该查询由 EF Core 转换为 SQL。如果某些内容无法转换为 SQL,则会引发异常。你想要做的是不是 GROUP BY
虽然
@panagiotis-kanavos 谢谢 - 我已经添加了代码。是的,我知道它在后台是如何工作的——抱歉不准确。我的意思是 EfCore 无法翻译我的查询并引发各种翻译异常,因此,当稍后使用ToList
时,我无法用数据库数据填充我的代码集合。
您已经使用LoadAsync()
将所有内容加载到内存中,然后丢弃了结果。改用ToListAsync()
并实际使用加载的数据。如果您再次尝试使用articlesQuery
,您将执行一个new 查询。您一遍又一遍地加载相同的数据
首先,不要创建平面查询结果集,因为 EF Core GroupBy limitations 无法将其分组以生成您想要的结果。产生这种查询形状的唯一方法是不基于平面结果。很难给你好的建议,因为所有这些连接都很难遵循 - 你没有正常的关系/导航属性吗?
@panagiotis-kanavos 当我实现整个平面结果时 - 它确实有效。当然,我需要将返回结果从IQueryable
更改为IEnumerable
。如果我坚持这种方法,我将需要进行其他更改,因为同时在塑造实体列表数据之后,我们的代码中应用了传统的过滤器和分页,这些代码被转换为 SQL 代码。所以我需要将这部分移动到当前方法中,以获得只需要的页面块......可能有不同的方法来保留 IQueryable......会很好。
【参考方案1】:
我想我已经对您的查询进行了逆向工程。很大的不同是我们不能从这个函数返回IQueryable
,但准备好了IEnumerable
。所以如果你以后有分页,最好将页面信息传递给函数参数。
private async Task<IEnumerable<LawArticleAggregated>> GetLawArticlesGroupedById(DbSet<LawArticleDetail> dbSet, string userContentLangRestriction = null)
var dbContext = await GetDbContextAsync();
var articlesQuery =
from article in dbSet
from askedLawLanguage in dbContext.Langs
.Where(askedLawLanguage => askedLawLanguage.LanguageCode == userContentLangRestriction && article.LanguageCode == askedLawLanguage.StringValue)
.DefaultIfEmpty()
from fallbackLanguage in dbContext.Langs
.Where(fallbackLanguage => fallbackLanguage.LanguageCode == CoreConstants.LanguageCodes.English && article.LanguageCode == fallbackLanguage.StringValue)
.DefaultIfEmpty()
select new
ArticleId = article.ArticleId,
ArticleText = article.ArticleText,
LanguageName = askedLawLanguage.ShortName ?? fallbackLanguage.ShortName
;
articlesQuery = articlesQuery
.OrderBy(x => x.ArticleId)
.ThenBy(x => x.LanguageName)
.ThenBy(x => x.ArticleText);
var loaded = await articlesQuery.ToListAsync();
// group on the client side
var aggregatedArticles = loaded.GroupBy(x => x.ArticleId)
.Select(g => new ArticleAggregated
ArticleId = g.Key,
ArticleTexts = g.Select(x => new LawArticleAggregated.Data
ArticleText = x.ArticleText,
LanguageName = x.LanguageName
).ToList()
);
return aggregatedArticles;
【讨论】:
我认为分组需要在服务器上进行,因为需要在它之后应用分页以保持正确的 UI 网格分页:1ArticleAggregated
entry ~ UI 中的 1 个网格行
只有 EF Core 6 才能在服务器上进行这种分组。但如果您将分页信息作为函数的参数传递,我们可以模拟这种行为。【参考方案2】:
我最终得到了以下实现(我“按原样”显示它,没有从第一条消息中简化来演示该方法,对初始变体进行了轻微修改以使用正确的分页):
private async Task<IEnumerable<LawArticleAggregated>> GetLawArticlesGroupedByIdListAsync(
DbSet<LawArticleDetail> dbSet,
Expression<Func<IQueryable<LawArticleDetail>, IQueryable<LawArticleDetail>>> filterFunc,
int skipCount,
int maxResultCount,
string userContentLangRestriction = null,
CancellationToken cancellationToken = default
)
var dbContext = await GetDbContextAsync();
var articlesQuery =
(from articleIds in filterFunc.Compile().Invoke(dbSet).Select(x => new x.TenantId, x.LawArticleId )
.Distinct().OrderBy(x => x.TenantId).OrderByDescending(x => x.LawArticleId).Skip(skipCount).Take(maxResultCount)
from articlesPerId in dbSet
.Where(x => x.TenantId == articleIds.TenantId && x.LawArticleId == articleIds.LawArticleId)
join askedL in dbContext.FixCodeValues
.Where(l =>
l.DomainId == CoreConstants.Domains.CENTRAL_TOOLS
&& l.CodeName == CoreConstants.FieldTypes.LANGUAGE
&& l.LanguageCode == userContentLangRestriction)
on
articlesPerId.LanguageCode
equals
askedL.StringValue
into askedLanguages
from askedLawLanguage in askedLanguages.DefaultIfEmpty()
join fallbackL in dbContext.FixCodeValues
.Where(l =>
l.DomainId == CoreConstants.Domains.CENTRAL_TOOLS
&& l.CodeName == CoreConstants.FieldTypes.LANGUAGE
&& l.LanguageCode == CoreConstants.LanguageCodes.English)
on
articlesPerId.LanguageCode
equals
fallbackL.StringValue
into fallbackLanguages
from fallbackLanguage in fallbackLanguages.DefaultIfEmpty()
select new
TenantId = articleIds.TenantId,
LawArticleId = articleIds.LawArticleId,
Shortcut = articlesPerId.Shortcut,
ArticleText = articlesPerId.ArticleText,
LanguageName = askedLawLanguage.ShortName ?? fallbackLanguage.ShortName
)
.OrderBy(x => x.TenantId).ThenByDescending(x => x.LawArticleId).ThenBy(x => x.Shortcut).ThenBy(x => x.LanguageName).ThenBy(x => x.ArticleText);
var articleList = await articlesQuery.ToListAsync(cancellationToken);
var aggregatedArticles = articleList.GroupBy(x => new x.TenantId, x.LawArticleId )
.Select(g => new LawArticleAggregated
TenantId = g.Key.TenantId,
LawArticleId = g.Key.LawArticleId,
ArticleTexts = g.Select(x => new LawArticleAggregated.Data
Shortcut = x.Shortcut,
ArticleText = x.ArticleText,
LanguageName = x.LanguageName
).ToList()
);
return aggregatedArticles;
private async Task<long> GetLawArticlesGroupedByIdCountAsync(
DbSet<LawArticleDetail> dbSet,
Expression<Func<IQueryable<LawArticleDetail>, IQueryable<LawArticleDetail>>> filterFunc,
CancellationToken cancellationToken = default
)
return await filterFunc.Compile().Invoke(dbSet).GroupBy(x => new x.TenantId, x.LawArticleId ).LongCountAsync(cancellationToken);
【讨论】:
以上是关于无法为具有集合属性的类编写正确的 Linq 查询 - 出现 EfCore5 翻译错误的主要内容,如果未能解决你的问题,请参考以下文章