Лямбда-нить как VARCHAR

Один из моих ключей-селекторов Join выглядит следующим образом:

x => x.A + "-" + x.B

NHibernate делает "-" дополнительный параметр. Этот параметр получает тип SQL nvarchar и поэтому весь оператор конвертируется на SQL Server из varchar в nvarchar,

Проблема в том, что SQL Server имеет огромную проблему, если запрашиваемый столбец имеет тип varchar вместо nvarchar, Это связано с тем, что столбец имеет тип, отличный от параметра, и поэтому индекс нельзя использовать.

Я не могу изменить тип столбца, поэтому мне нужно как-то определить, что NHibernate должен использовать varchar для строковых литералов при преобразовании лямбд.

Есть ли способ сделать это?

ОБНОВИТЬ

С помощью Оскара Берггрена я установил следующие классы:

public static class VarcharFix
    {
        /// This method returns its argument and is a no-op in C#.
        /// It's presence in a Linq expression sends a message to the NHibernate Linq Provider.
        public static string AsVarchar(string s)
        {
            return s;
        }
    }

public class MyHqlIdent : HqlExpression
    {
        internal MyHqlIdent(IASTFactory factory, string ident)
            : base(HqlSqlWalker.IDENT, ident, factory)
        {
        }

        internal MyHqlIdent(IASTFactory factory, System.Type type)
            : base(HqlSqlWalker.IDENT, "", factory)
        {
            if (IsNullableType(type))
            {
                type = ExtractUnderlyingTypeFromNullable(type);
            }

            switch (System.Type.GetTypeCode(type))
            {
                case TypeCode.Boolean:
                    SetText("bool");
                    break;
                case TypeCode.Int16:
                    SetText("short");
                    break;
                case TypeCode.Int32:
                    SetText("integer");
                    break;
                case TypeCode.Int64:
                    SetText("long");
                    break;
                case TypeCode.Decimal:
                    SetText("decimal");
                    break;
                case TypeCode.Single:
                    SetText("single");
                    break;
                case TypeCode.DateTime:
                    SetText("datetime");
                    break;
                case TypeCode.String:
                    SetText("string");
                    break;
                case TypeCode.Double:
                    SetText("double");
                    break;
                default:
                    if (type == typeof(Guid))
                    {
                        SetText("guid");
                        break;
                    }
                    if (type == typeof(DateTimeOffset))
                    {
                        SetText("datetimeoffset");
                        break;
                    }
                    throw new NotSupportedException(string.Format("Don't currently support idents of type {0}", type.Name));
            }
        }

        private static System.Type ExtractUnderlyingTypeFromNullable(System.Type type)
        {
            return type.GetGenericArguments()[0];
        }

        // TODO - code duplicated in LinqExtensionMethods
        private static bool IsNullableType(System.Type type)
        {
            return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
        }
    }

    public class MyHqlCast : HqlExpression
    {
        public MyHqlCast(IASTFactory factory, IEnumerable<HqlTreeNode> children)
            : base(HqlSqlWalker.METHOD_CALL, "method", factory, children)
        {

        }

        public static MyHqlCast Create(IASTFactory factory, HqlExpression expression, string targetType)
        {
            return new MyHqlCast(factory,
                                new HqlTreeNode[]
                                {
                                    new MyHqlIdent(factory, "cast"),
                                    new HqlExpressionList(factory, expression,
                                    new MyHqlIdent(factory, targetType))
                                });
        }
    }

    public class MyBaseHqlGeneratorForMethod : BaseHqlGeneratorForMethod
    {
        public MyBaseHqlGeneratorForMethod()
            : base()
        {
            SupportedMethods = new MethodInfo[] { typeof(VarcharFix).GetMethod("AsVarchar") };
        }

        public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, HqlTreeBuilder treeBuilder, global::NHibernate.Linq.Visitors.IHqlExpressionVisitor visitor)
        {
            return MyHqlCast.Create(new ASTFactory(new ASTTreeAdaptor()),
                        visitor.Visit(targetObject).AsExpression(),
                        "varchar");
        }
    }

public class ExtendedLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
    {
        public ExtendedLinqtoHqlGeneratorsRegistry()
        {
            this.Merge(new MyBaseHqlGeneratorForMethod());
        }
    }

Пока все еще не работает, но я вижу свет;)

ОБНОВЛЕНИЕ 2: Запрос

var query = aQueryable
            .Join(bQueryable,
        x => x.AB, x => x.A + VarcharFix.AsVarchar("-") + x.B,
                                (head, middle) => new ...)

ОБНОВЛЕНИЕ 3:

Как "-".AsVarchar() оптимизируется до "-" нам нужен фиктивный параметр, который нельзя оптимизировать, как "-".AsVarchar(x.A) - так начинается расширение Linq!

var query = aQueryable
            .Join(bQueryable,
        x => x.AB, x => x.A + "-".AsVarchar(x.A) + x.B,
                                (head, middle) => new ...)

1 ответ

Решение

Для этого может быть несколько способов, но вот один из них:

Придумайте свой собственный метод, такой как:

/// This method returns its argument and is a no-op in C#.
/// It's presence in a Linq expression sends a message to the NHibernate Linq Provider.
public static string AsVarchar(string s)
{
    return s;
}

Также создайте класс для представления фрагмента выражения HQL:

public class MyHqlCast : HqlExpression
{
    private MyHqlCast(IASTFactory factory, IEnumerable<HqlTreeNode> children)
        : base(HqlSqlWalker.METHOD_CALL, "method", factory, children)
    {
    }

    public static MyHqlCast Create(IASTFactory factory, HqlExpression expression,
                                   string targetType)
    {
        return new MyHqlCast(factory,
                             new [] {
                                 new HqlIdent(factory, "cast")),
                                 new HqlExpressionList(factory, expression,
                                         new HqlIdent(factory, targetType)),
                             });
    }
}

Затем выведите класс из BaseHqlGeneratorForMethod. В его конструкторе установите свойство SupportedMethods для метода AsVarchar(). Переопределите метод BuildHql(). Он должен вывести HQL-конструкции, эквивалентные приведению (@param как varchar). Обычно вы используете метод Cast() для параметра treeBuilder, но, к сожалению, он принимает только System.Type, что недостаточно для этого случая. Вместо этого создайте и верните экземпляр вашего MyHqlCast:

return MyHqlCast.Create(new ASTFactory(new ASTTreeAdaptor()),
                        visitor.Visit(arguments[0]).AsExpression(),
                        "varchar");

Ваша реализация BaseHqlGeneratorForMethod должна быть зарегистрирована путем наследования от DefaultLinqToHqlGeneratorsRegistry. Вызовите это.Merge(новый MyGenerator()); в конструкторе. Затем зарегистрируйте свой тип реестра с помощью

nhibernateConfiguration.LinqToHqlGeneratorsRegistry<MyRegistry>();
Другие вопросы по тегам