我有一个国际化的数据库,其中有一个Collection
表,一个i18n
表和一个translation
表。
collection
名称字段包含i18n
表中的GUID,而translation
表包含每个语言环境的翻译列表。
这是我在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字段的列表很有意义。
但是在“现实世界中”,每个translation
对象至少具有两个属性(文本和图片),每个collection
对象具有其他所需的属性。因此,仍然需要遍历collections
。哦,翻译将永远存在。
我想要实现的是,使用一个查询已加载的适当翻译来检索所有集合。
让我添加一个示例进行说明: 没有EF的“旧式” 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}");
}
答案 0 :(得分:2)
您正在枚举循环中的collections查询,然后为每个实例获取翻译。您可以在一个查询中完成相同的操作。
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}");
}
答案 1 :(得分:1)
尝试将集合设为IQueryable
,这样它将在服务器端执行查询,包括添加的过滤器。
即加上
Where(x => x.locale_id == "fr").FirstOrDefault();
答案 2 :(得分:0)
您需要在此处使用Query()
。
在明确加载相关实体时应用过滤器
Query方法提供对在加载相关实体时Entity Framework将使用的基础查询的访问。然后,您可以在调用LINQ扩展方法(例如ToList,Load等)执行该查询之前,使用LINQ将过滤器应用于查询。该Query方法可以与引用导航属性和集合导航属性一起使用,但对于其中的集合最有用它只能用于加载集合的一部分。例如:
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);
答案 3 :(得分:0)
基于其他答案,我找到了一种将原始对象保留在查询结果中的解决方案,以保持从数据库中获取新字段以在将来的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
指令。