Манипулирование запросами EntityFramework, перенос базы данных, деревья выражений базы данных - PullRequest
14 голосов
/ 16 июня 2011

Я пытаюсь реализовать логику локализации данных для Entity Framework.Таким образом, если, например, запрос выбирает свойство Title, за кулисами он должен ссылаться на столбец Title_enGB или Title_deCH в зависимости от текущей культуры пользователя.

Чтобы достичь этого, я бы хотел переписать дерево команд DbExpression из Entity Framework.Я думал, что эти деревья - это новый общий способ .NET для создания кросс-запросов на вставку / обновление / выборку базы данных. Но теперь все соответствующие конструкторы / фабрики в пространствах имен System.Data.Metadata и System.Data.Common.CommandTrees в System.Data.Entity.dllявляются внутренними!(В msdn, задокументированном как общедоступный, например: DbExpressionBuilder).

У кого-нибудь есть идея достичь этой манипуляции с запросом с переписыванием дерева запросов или без него?

myжелаемый код: (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;
}

Обновление

Наличие двух столбцов заголовков в одной таблице - это не круто, но это проще реализовать на первом этапе.Позже я присоединюсь к другой таблице с локализованными полями, поэтому основная таблица будет содержать только инвариантные данные.

Multilanguage

Ответы [ 3 ]

5 голосов
/ 21 июня 2011

Я согласен с ответом Шираз, что это не должно быть тем, что вы хотите, если вы все еще способны изменить дизайн, но я буду предполагать, что это существующее приложение, которое вы конвертируете в Entity Framework.

Если это так, имеет значение, отображаются ли столбцы Title_enGB / etc в файле EDMX / POCO. Если они есть, я полагаю, это возможно. Что вы можете сделать здесь, это использовать посетитель Expression, который посещает 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.

5 голосов
/ 17 июня 2011

В .net у вас есть файлы resx для обработки локализации. См .: В чем преимущества файлов ресурсов (.resx)?

Есть пара проблем с вашим подходом:

  • Добавление дополнительного языка требует изменения базы данных
  • Трафик данных из базы данных больше, чем требуется

Я знаю, что это не прямой ответ на ваш вопрос, но я думаю, что вы должны посмотреть на файлы resx.

Если вам необходимо сохранить его в базе данных, вы можете изменить структуру базы данных:

  • Таблица 1: идентификатор, текст
  • Таблица 2: id, Table1_id, language_code, text

Таким образом, новый язык не требует изменения базы данных, и код EF становится намного проще.

1 голос
/ 24 июня 2011

Вместо этого я предложу еще один дизайн ...

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

Теперь в этом случае у вас есть,

1, Milk, $1 , EnglishDesc  , NULL, en-us 
2. M*^*, ^*&, OtherLangDesc, 1   , @$#$$

Ваша запись 2 на самом деле является другим языковым описанием всего продукта на другом языке, указанном LanguageCode.

Таким образом, вы можете управлять только одной таблицей, и написание некоторых основанных на отражениях или основанных на отражениях решений будет намного проще.

// 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;}
}

И вы можете написать общее решение, основанное на этом.

...