Обработка запросов EntityFramework, обертка поставщика db, деревья выражений db

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

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



Update

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

Multilanguage

Ответ 1

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

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

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

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

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

  • Таблица 1: id, Текст
  • Таблица 2: id, Table1_id, language_code, текст

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

// Get Active Products
q = context.Products.Where( x=> x.ParentID == null);

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

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