EntityFramework 发送的查询中不包含 where 子句

EntityFramework doesn't include where clause in sent query

我有一个国际化数据库,假设有一个 Collection table、一个 i18n table 和一个 translation table。

collection 名称字段包含来自 i18n table 的 GUID,translation table 包含每个语言环境的翻译列表。

这是我在 C# 中使用的代码:

var ctx = new CollectEntities();
var colls = ctx.collections.Include(x => x.i18n);

foreach(var c in colls)
{
    var t = c.i18n.translations.Where(x => x.locale_id == "fr").FirstOrDefault();
    MessageBox.Show(t.trans_text);
}

这里是生成的 SQL 查询:

SELECT 
    1 AS [C1], 
    [Extent1].[coll_id] AS [coll_id], 
    [Extent1].[coll_name] AS [coll_name], 
    [Extent2].[i18n_id] AS [i18n_id], 
    [Extent2].[i18n_default] AS [i18n_default]
    FROM  [dbo].[collection] AS [Extent1]
    INNER JOIN [dbo].[i18n] AS [Extent2] ON [Extent1].[coll_name] = [Extent2].[i18n_id]
-- Executing at 20/11/2019 11:39:12 +01:00
-- Completed in 17 ms with result: SqlDataReader

SELECT 
    [Extent1].[trans_id] AS [trans_id], 
    [Extent1].[i18n_id] AS [i18n_id], 
    [Extent1].[locale_id] AS [locale_id], 
    [Extent1].[trans_text] AS [trans_text]
    FROM [dbo].[translation] AS [Extent1]
    WHERE [Extent1].[i18n_id] = @EntityKeyValue1
-- EntityKeyValue1: '929ba17e-c6c0-43ff-a8bc-6efa950fa03d' (Type = Guid, IsNullable = false)

如果我有 50 个可用的翻译,这就是浪费时间和流量。为什么不生成:

    WHERE [Extent1].[i18n_id] = @EntityKeyValue1 AND [Extent1].[locale_id] = @the_locale_I_want

我错过了什么?

编辑:为了这个问题,我简化了代码。我知道如前所述,直接获取 trans_text 字段的列表是有意义的。

但是"in real world",每个translation对象至少有两个属性(文本和图片),每个collection对象还有其他需要的属性。所以它仍然需要遍历 collections。哦,翻译永远存在。

我想要实现的是检索所有已通过一个查询加载了适当翻译的集合。

我再举个例子来说明: 没有 EF 的 'old style' SQL 查询类似于:

SELECT collection.*, i18n.*, translation.*
FROM collection
    INNER JOIN i18n ON i18n.i18n_id=collection.coll_name
    LEFT OUTER JOIN translation ON translation.i18n_id=i18n.i18n_id
        AND translation.locale_id = 'fr'

要使用的代码是:

var ctx = new CollectEntities();
var colls = ctx.collections.Include(x => x.i18n)[.something to catch translation];

foreach(var c in colls)
{
    var t = c.i18n.translations.Where(x => x.locale_id == "fr").FirstOrDefault();
    MessageBox.Show($"{c.coll_id}, price={c.coll_price}, name is {t?.trans_text ?? c.i18n.i18n_defaulttext}, picture file is {t?.trans_picturefilename}");
}

尝试将集合设为 IQueryable,这样它将在您的服务器端执行查询,包括您添加的过滤器。

即包含

Where(x => x.locale_id == "fr").FirstOrDefault();

包含方式

你需要在这里使用Query()

Loading Related Entities:

Applying filters when explicitly loading related entities

The Query method provides access to the underlying query that Entity Framework will use when loading related entities. You can then use LINQ to apply filters to the query before executing it with a call to a LINQ extension method such as ToList, Load, etc. The Query method can be used with both reference and collection navigation properties but is most useful for collections where it can be used to load only part of the collection. For example:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Load the posts with the 'entity-framework' tag related to a given blog.
    context.Entry(blog)
           .Collection(b => b.Posts)
           .Query()
           .Where(p => p.Tags.Contains("entity-framework"))
           .Load();

无包含方式

您似乎没有过滤任何特定的集合,为什么不直接加载翻译呢?

forach( t in ctx.translations.Where(x => x.locale_id == "fr"))
         MessageBox.Show(t.trans_text);

如果您想添加过滤器,您可以

var t = ctx.translations.Where(x => x.locale_id == "fr" && i18n_id = ...))
MessageBox.Show(t.trans_text);

您在循环中枚举集合查询,然后为每个实例获取翻译。您可以在一个查询中完成相同的操作。

var ctx = new CollectooEntities();
var dto = ctx.collections.Select(x => new {
        coll_id = x.coll_id,
        coll_price = x.coll_price,
        i18n_defaulttext = x.i18n.i18n_defaulttext,
        trans = x.i18n.translations
            .Where(t => t.locale_id == "fr")
            .Select(t => new { trans_text, trans_picturefilename })
            .FirstOrDefault()
    });

foreach(var c in dto)
{
    MessageBox.Show($"{c.coll_id}, price={c.coll_price}, name is {c.trans?.trans_text ?? c.i18n_defaulttext}, picture file is {c.trans?.trans_picturefilename}");
}

根据其他答案,我找到了一个将原始对象保留在查询结果中的解决方案,以便在未来的 EF 模型从数据库同步时保持获取可用新字段的优势:

var ctx = new CollectEntities();

var colls = ctx.collections
    .Select(x => new { Obj = x, x.i18n, trans = x.i18n.translations.Where(t => t.locale_id == "fr").FirstOrDefault() });

foreach (var c in colls2)
{
    MessageBox.Show($"{c.Obj.coll_id}: {c.trans?.trans_text ?? c.Obj.i18n.i18n_default}");
}

这样,Obj 属性 包含原始 collection 对象,trans 包含 translation 对象(如果适用)。我必须将 x.i18n 添加到生成的对象中以强制 EF 加载 i18n 导航 属性,因为返回新对象类型的 Select 方法取消了 Include 指令。