我有一张表:
TABLE_XY
|ID |SOURCE|
|1 |value_aa|
|2 |other_aa|
|3 |eeeaa|
生成的查询应为:
select * from TABLE_XY where SOURCE like '%\_aa' ESCAPE '\'
我知道有两种选择可以满足我的需求
var result =
session.QueryOver<TableXy>()
.WhereRestrictionOn(x => x.Source)
.IsLike("%\_aa", MatchMode.Exact, '\\')
.List();
或
var result2 = session
.CreateCriteria<TableXy>()
.Add(LikeExpression("Source", "%\\_aa", MatchMode.Exact, '\\', false))
.List();
但我必须使用基于Linq的实现。我正在使用动态创建的表达式树,有时将使用Linq to Object Provider或Linq to Nhibernate执行。 但目前只支持这种方法:
var result = session
.Query<TableXy>()
.Where(x => NHibernate.Linq.SqlMethods.Like(x.Source, "%\\_aa"))
.ToList();
如何扩展Nhibernate Linq Provider以支持?
SqlMethods.IsLike(string source, string pattern, char? escape);
答案 0 :(得分:2)
好的,这是一个非常复杂的答案,很可能会遇到问题,但我能够让一个like
运营商使用escape
件。
这涉及几个步骤,但基本上我们正在做的是添加一种新的HqlTreeNode
类型,可以处理escape
运算符的like
部分。
创建一个您将在LINQ查询中使用的扩展方法。这种方法不需要实现 - 我们稍后会提供:
public static class LinqExtensions
{
public static bool IsLikeWithEscapeChar(
this string input,
string like,
char? escapeChar)
{
throw new NotImplementedException();
}
}
创建一个HqlEscape
树节点,我们将用它来表示escape
运算符的like
部分:
public class HqlEscape : HqlExpression
{
public HqlEscape(IASTFactory factory, params HqlTreeNode[] children)
: base(HqlSqlWalker.ESCAPE, "escape", factory, children)
{
}
}
创建HqlLikeWithEscape
树节点。默认的HqlLike
节点无法处理escape
部分,因此我们需要创建一个可以处理三个子节点的新节点:
public class HqlLikeWithEscape : HqlBooleanExpression
{
public HqlLikeWithEscape(IASTFactory factory, HqlExpression lhs, HqlExpression rhs, HqlEscape escape)
: base(HqlSqlWalker.LIKE, "like", factory, lhs, rhs, escape)
{
}
}
为我们之前定义的IsLikeWithEscapeChar
扩展方法创建一个生成器。这堂课&#39;责任是获取调用方法的信息并返回最终将转换为SQL的HQL树结构:
public class CustomLikeGenerator : BaseHqlGeneratorForMethod
{
public CustomLikeGenerator()
{
this.SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition(
() => LinqExtensions.IsLikeWithEscapeChar(null, null, null))
};
}
public override HqlTreeNode BuildHql(
MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Is there a better way to do this?
var factory = new ASTFactory(new ASTTreeAdaptor());
HqlTreeNode escapeCharNode = visitor.Visit(arguments[2]).AsExpression();
var escapeNode = new HqlEscape(factory, escapeCharNode);
HqlLikeWithEscape likeClauseNode =
new HqlLikeWithEscape(
factory,
visitor.Visit(arguments[0]).AsExpression(),
visitor.Visit(arguments[1]).AsExpression(),
escapeNode);
return likeClauseNode;
}
}
如您所见,我们已经使用了之前定义的新HQL树节点。这种方法的主要缺点是需要我手动创建ASTFactory
和ASTTreeAdaptor
。这些类的使用通常封装在HqlTreeBuilder
内,但HqlTreeBuilder
并不适合用于子类。如果有人提出一些建议,我将不胜感激。
创建一个新的LINQ to HQL生成器注册表。这个类只是将我们的扩展方法与我们在步骤4中提供的HQL实现相关联:
public class LinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public LinqToHqlGeneratorsRegistry() : base()
{
RegisterGenerator(
ReflectionHelper.GetMethodDefinition(() => LinqExtensions.IsLikeWithEscapeChar(null, null, null)),
new CustomLikeGenerator());
}
}
更新您的配置以使用新的LinqToHqlGeneratorsRegistry
:
cfg.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
(最后)在查询中使用新的扩展方法:
session.Query<Person>().Where(p => p.FirstName.IsLikeWithEscapeChar("%Foo", '\\'))
请注意,您需要指定通配符。这可以平滑,但这不会太难做。
这是我第一次以这种方式扩展HQL,这个解决方案可能会出现问题。我只能在SQL Server上进行测试,但我确信它应该可以工作,因为它创建了与HQL查询相同的树结构。
答案 1 :(得分:1)
这里的解决方案应该非常简单:
var result = session
.Query<TableXy>()
// instead of this
//.Where(x => NHibernate.Linq.SqlMethods.Like(x.Source, "%\\_aa"))
// This will add sign % at the beginning only
.Where(x => x.Source.EndsWith("[_]aa"));
// or wrap it on both sides with sign: %
.Where(x => x.Source.Contains("[_]aa"));
.ToList();
诀窍是使用 ruglar like expression 样式作为下划线 [_]