Лямбда-нить как 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>();