Расширение LINQ до поставщика Nhibernate в сочетании с проблемой динамического LINQ

#c# #.net #linq #nhibernate #linq-to-nhibernate

#c# #.net #linq #nhibernate #linq-to-nhibernate

Вопрос:

Я использую NHibernate 3.1.0 и пытаюсь расширить поставщика LINQ, используя BaseHqlGeneratorForMethod и расширяя DefaultLinqToHqlGeneratorsRegistry , как описано в сообщении Fabio.

Например, для поддержки ToString() я создал ToStringGenerator , как показано ниже.

 internal class ToStringGenerator : BaseHqlGeneratorForMethod
{
    public ToStringGenerator()
    {
        SupportedMethods = new[]
            {
                ReflectionHelper.GetMethodDefinition<object>(x => x.ToString())
            };
    }

    public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        return treeBuilder.Cast(visitor.Visit(targetObject).AsExpression(), typeof(string));
    }
}
  

и я зарегистрировался с помощью

 internal class CustomLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public CustomLinqToHqlGeneratorsRegistry()
    {
        this.Merge(new ToStringGenerator());
    }
}
  

и т.д. Пока это работает для «статических» запросов, я могу использовать это следующим образом:

 var results = mSession.Query<Project>();
string pId = "1";
results = results.Where(p => p.Id.ToString().Contains(pId));
  

Это правильно переводится на его аналог SQL (с использованием SQL Server 2008)

 where cast(project0_.Id as NVARCHAR(255)) like (''%'' @p0 ''%'')
  

Проблема возникает, когда я пытаюсь использовать его в сочетании с библиотекой Microsoft Dynamic LINQ (обсуждаемой в этом сообщении Скотта Гатри) следующим образом:

 var results = mSession.Query<Project>();
string pId = "1";
results = results.Where("Id.ToString().Contains(@0)", pId);
  

Это приводит к исключению NotSupportedException с сообщением «System.String toString()» (это были точно такие же сообщения, которые я получал со статическими запросами перед внедрением упомянутых выше классов). Это исключение генерируется с источником «NHibernate» и с отслеживанием стека в «в NHibernate.Linq.Visitors.HqlGeneratorExpressionTreeVisitor.visitmethod CallExpression(выражение MethodCallExpression)«.

Итак, чего мне здесь не хватает? Что я сделал не так или что нужно сделать для поддержки этого сценария?

Ответ №1:

У меня была такая же проблема, и я ее исправил.
Сначала я хочу поблагодарить murki за предоставленную информацию, которая помогла мне в этом!

Ответ частично содержится в сообщении Fabio. Чтобы решить эту проблему, вы должны использовать RegisterGenerator вместо Merge метода в CustomLinqToHqlGeneratorsRegistry конструкторе. Моя реализация CustomLinqToHqlGeneratorsRegistry класса заключается в следующем:

 public class CustomLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public CustomLinqToHqlGeneratorsRegistry()
        : base()
    {
        MethodInfo toStringMethod = ReflectionHelper.GetMethodDefinition<int>(x => x.ToString());
        RegisterGenerator(toStringMethod, new ToStringGenerator());
    }
}
  

Комментарии:

1. Тогда это звучит как ошибка в реализации слияния.

Ответ №2:

Здесь есть два четко определенных отдельных этапа:

  1. Преобразование динамического (строкового) запроса в статическое выражение (выполняется динамической библиотекой Linq)
  2. Разбор этого в HqlTree, затем выполнение (выполняется NHibernate)

Поскольку вы определили, что статическое выражение работает хорошо, проблема заключается в 1.

Что произойдет, если вы сделаете следующее?

 var results = Enumerable.Empty<Project>().AsQueryable();
string pId = "1";
results = results.Where("Id.ToString().Contains(@0)", pId);
  

Если это не удается, вы подтвердите, что проблема связана только с Dynamic Linq (т. Е. он не поддерживает выражение, которое вы ему передаете), поэтому вам придется разобраться в этом и исправить.

Частично связанный: ToStringGenerator выглядит полезным; не могли бы вы отправить исправление для NHibernate? http://jira.nhforge.org

Комментарии:

1. Спасибо за ваш ответ. Я сделал то, что вы предложили, и запрос работает нормально (я имею в виду, что он не дает результатов, но выражение внутри запроса сгенерировано правильно). Что-то, что я заметил сейчас, сравнивая два запроса, заключается в том, что выражение для статического использует выражение. Константа со значением переменной (pId), тогда как динамический использует строковое значение напрямую. Как вы думаете, это может иметь отношение к проблеме?

2. @murki: да, это может быть связано. Высокоимпедансные деревья выражений чрезвычайно хрупки в плане поддержки небольших вариаций. Теперь, учитывая это, у вас есть два пути: исправление динамического Linq для генерации выражения. Константа или исправление NHibernate для поддержки использования, создаваемого Dynamic Linq.

Ответ №3:

Предположим, что свойство Id класса Project является Int32 попробуйте зарегистрировать соответствующий Int32.ToString() метод в вашем ToStringGenerator классе.

 ...
public ToStringGenerator()
{
    SupportedMethods = new[]
        {
            ReflectionHelper.GetMethodDefinition<object>(x => x.ToString()),
            ReflectionHelper.GetMethodDefinition<int>(x => x.ToString()),
        };
}
...
  

Комментарии:

1. Да, это Int32, но я действительно пробовал это раньше — и это, вероятно, тема для другого вопроса, но: 1) если я добавлю только object и int, в моей ошибке ничего не изменится, 2) если я начну вводить больше типов данных (например, DateTime), это вызовет исключение NhibernateException с ошибкой «Элемент с тем же ключом уже добавлен» при создании SessionFactory.

2. Хорошо, спасибо. После некоторого исследования NHibernate 3.2, все еще находящийся в бета-версии, будет иметь встроенную поддержку toString в Linq.