EntityFramework查询操作、数据库提供程序封装、数据库表达式树

14

我正在尝试为Entity Framework实现数据本地化逻辑。例如,如果查询选择Title属性,则在幕后应引用当前用户文化设置为Title_enGBTitle_deCH的列。

为了实现这一点,我想重新编写来自Entity Framework的DbExpression CommandTrees。我以为这些树结构是一种新的通用.NET方法,用于构建跨数据库的插入/更新/选择查询... 但是,现在命名空间System.Data.MetadataSystem.Data.Common.CommandTrees中所有相关的构造函数/工厂在System.Data.Entity.dll中都是内部的!(在MSDN文档中记录为public,例如:DbExpressionBuilder)。

是否有人知道如何使用或不使用查询树重写实现此查询操作?

我的期望代码:(public class DbProviderServicesWrapper : DbProviderServices

/// <summary>
/// Creates a command definition object for the specified provider manifest and command tree.
/// </summary>
/// <param name="providerManifest">Provider manifest previously retrieved from the store provider.</param>
/// <param name="commandTree">Command tree for the statement.</param>
/// <returns>
/// An exectable command definition object.
/// </returns>
protected override DbCommandDefinition CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree)
{
    var originalCommandTree = commandTree as DbQueryCommandTree;
    if (originalCommandTree != null)
    {
        var expression = new MyCustomQueryRewriter(originalTree.MetadataWorkspace).Visit(originalCommandTree.Query);
        commandTree = DbQueryCommandTree.FromValidExpression(originalCommandTree.MetadataWorkspace, originalCommandTree.DataSpace, expression);
    }

    // TODO: UpdateCommand/InsertCommand

    var inner = this.Inner.CreateCommandDefinition(providerManifest, commandTree);
    var def = new DbCommandDefinitionWrapper(inner, (c, cd) => new DbCommandWrapper(c));

    return def;
}



更新

在一张表中拥有两个标题列并不太好,但在第一步中更容易实现。稍后我会使用另一个表连接本地化字段,这样主表将只包含不变的数据。

多语言


1
你似乎忽略了本地化中两个与文化相关的问题。(1) 价格以美元表示。(2) 数量以公制系统表示。 - smartcaveman
3个回答

5
我同意Shiraz的答案,如果您仍然可以更改设计,则不应该是您想要的,但我假设这是一个现有的应用程序,您正在将其转换为Entity Framework。
如果是这样,那么EDMX文件/POCO中是否映射了Title_enGB等列就很重要。如果是这样,我认为这是可能的。在这里,您可以使用一个表达式访问器来访问MemberExpressions,检查它们是否访问名为“Title”的属性(您可以创建一个需要像这样处理的属性的白名单),然后返回一个新的MemberExpression,如果已登录的用户设置了该语言,则访问Title_enGB。
以下是一个快速示例:
public class MemberVisitor : ExpressionVisitor
{
  protected override Expression VisitMember(MemberExpression node)
  {
    if(node.Member.Name == "Title")
    {
        return Expression.Property(node.Expression, "Title_" + User.LanguageCode)
    }

    return base.VisitMember(node);
  }
}

在执行查询之前:

var visitor = new MemberVisitor();
visitor.Visit(query);

如果你已经无法控制数据库,那么这只是一个好主意。

根据你的具体情况,这个解决方案可能或可能不可行,但使用表达式重写查询绝对是可能的。

这是一个比修改Entity Framework生成实际SQL查询方式更高级的解决方案。这通常是被隐藏的,很可能有充分的理由。相反,你只需要修改描述查询的表达式树,让Entity Framework负责将其转换为SQL。


这两行代码应该放在哪里?var visitor = new MemberVisitor(); visitor.Visit(query); - Abou-Emish

5
在.NET中,您可以使用resx文件来处理本地化。请参阅:资源(.resx)文件的好处是什么? 您的方法存在几个问题:
  • 添加新语言需要更改数据库
  • 从数据库传输的数据比所需的要多
虽然我知道这不是直接回答您的问题,但我认为您应该考虑使用resx文件。
如果您必须将其存储在数据库中,您可以重新设计数据库如下:
  • 表1: id, 文本
  • 表2: id, Table1_id, 语言代码, 文本
这样,添加新语言就不需要更改数据库了,并且EF代码变得更简单。

您的建议很简单。但我想编写一个实体框架扩展,可以自动处理这个问题,这样开发人员就不需要在每个查询上手动执行这些语言连接,因为我的大多数实体都具有多语言字符串和二进制属性。在我们的 .Net 3.5 ORM Genome(如上图所示)中,这也是自动管理的。 - benwasd
1
你可以构建一个视图并将连接放置在视图中。 - Shiraz Bhaiji
但这对于 Code First 不太友好,是吗? - sharp johnny
创建视图更多地是一种数据库优先的方法,而使用代码优先则需要访问每个表。 - Shiraz Bhaiji

1

相反,我将提出另一种设计...

Products
   ProductID 
   ProductName
   Price
   Description
   ParentID (Nullable, FK on ProductID)
   LangCode

在这种情况下,您现在有:
1, Milk, $1 , EnglishDesc  , NULL, en-us 
2. M*^*, ^*&, OtherLangDesc, 1   , @$#$$

您的记录2实际上是另一种语言的产品描述,使用语言代码进行识别。
这样,您只需要管理一个表格,并且编写一些基于泛型或反射的查询解决方案将会更加容易。
// Get Active Products
q = context.Products.Where( x=> x.ParentID == null);

// Get Product's Language Code Description
IQueryable<Product> GetProductDesc(int productID, string langCode){
    return context.Products.Where( x=>x.ParentID == productID &&
              x.LangCode == langCode);
}

你可以按照以下方式创建一个接口:
interface IMultiLangObject{
    int? ParentID {get;set;}
    string LangCode {get;set;}
}

你可以基于此编写一个通用的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接