Пользовательский поставщик linq для поиска в поле XML атрибута xml с определенным значением
Некоторые из моих таблиц базы данных, с которыми я взаимодействую через NHibernate, содержат поле XML со следующей структурой:
<L xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<I>
<C>
<N>Attribute1</N>
<V>a_value</V>
</C>
<C>
<N>Attribute2</N>
<V>123</V>
</C>
</I>
</L>
По сути, каждый тег "C" содержит атрибут, где его имя содержится в теге "N", а его значение - в теге "V".
То, чего я хочу достичь, - это написать синтаксис LINQ в моих запросах:
..
.Where(m=>m.XMLField(attribute_name, attribute_value))
..
так что я могу получить объекты конкретной таблицы, в поле XML которой содержится атрибут с именем "attribute_name" со строковым значением, указанным "attribute_value".
Это так просто, структура XML всегда такая, и мне нужно только запросить один атрибут с определенным значением.
Выполняя мои поиски, я обнаружил, что есть конкретный метод для реализации собственного поставщика LINQ:
- http://www.primordialcode.com/blog/post/nhibernate-3-extending-linq-provider-fix-notsupportedexception
- http://fabiomaulo.blogspot.it/2010/07/nhibernate-linq-provider-extension.html
- Как бы я изменил SQL, который Linq-to-Nhibernate генерирует для определенных столбцов?
К сожалению, я не смог найти некоторую структурированную документацию о том, как использовать построитель дерева, поэтому на данный момент у меня есть вот что:
Я выяснил правильный HQL для выполнения такой задачи:
where [some other statements] and XML_COLUMN_NAME.exist('/L/I/C[N=\"{0}\" and V=\"{1}\"]') = 1","attribute_name", "attribute_value");
метод, который я собираюсь вызвать внутри запроса LINQ:
public static bool AttributeExists(this string xmlColumnName, string attributeName, string attributeValue)
{
throw new NotSupportedException();
}
часть интеграции с HQL:
public class XMLAttributeGenerator : BaseHqlGeneratorForMethod
{
public XMLAttributeGenerator()
{
SupportedMethods = new[] { ReflectionHelper.GetMethodDefinition(() => TestClass.AttributeExists(null, null, null)) };
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject,
ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
return treeBuilder.Exists(???);
}
}
Как вы можете видеть, я до сих пор не понял, как правильно использовать построитель дерева с объектом посетителя для репликации синтаксиса HQL, описанного выше. Может кто-нибудь помочь мне с этим или, по крайней мере, указать мне некоторую основную документацию по использованию построителя дерева? Спасибо
1 ответ
Вот как я добился желаемого результата:
MOCK МЕТОД
public static class MockLINQMethods
{
public static bool XMLContains(this MyCustomNHType input, string element, string value)
{
throw new NotImplementedException();
}
}
ТАМОЖЕННЫЙ ГЕНЕРАТОР
public class CustomLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public CustomLinqToHqlGeneratorsRegistry()
: base()
{
RegisterGenerator(ReflectionHelper.GetMethod(() => MockLINQMethods.XMLContains((MyCustomNHType) null, null, null)),
new LINQtoHQLGenerators.MyCustomNHTypeXMLContainsGenerator());
}
}
public class MyCustomNHTypeXMLContainsGenerator : BaseHqlGeneratorForMethod
{
public MyCustomNHTypeXMLContainsGenerator()
{
SupportedMethods = new[] { ReflectionHelper.GetMethod(() => MockLINQMethods.XMLContains((MyCustomNHType) null, null, null)) };
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject,
ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
var column_name = visitor.Visit(arguments[0]).AsExpression();
var element_name = visitor.Visit(arguments[1]).AsExpression();
var value = visitor.Visit(arguments[2]).AsExpression();
return treeBuilder.BooleanMethodCall("_ExistInMyCustomNHType", new [] { column_name, element_name, value});
}
}
ТАМОЖЕННАЯ ФУНКЦИЯ
public class CustomLinqToHqlMsSql2008Dialect : MsSql2008Dialect
{
public CustomLinqToHqlMsSql2008Dialect()
{
RegisterFunction("_ExistInMyCustomNHType",
new SQLFunctionTemplate(NHibernateUtil.Boolean,
"?1.exist('/L/I/C[N=sql:variable(\"?2\") and V=sql:variable(\"?3\")]') = 1"));
}
}
И, наконец, СВЯЗЬ С ТАМОЖЕННЫМ ГЕНЕРАТОРОМ И ТАМОЖЕННОЙ ФУНКЦИЕЙ В ПОМОЩИ СЕССИИ
factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString)
.Dialect<CustomLinqToHqlMsSql2008Dialect>())
..
.ExposeConfiguration(c =>
{
..
c.SetProperty("linqtohql.generatorsregistry", "APP.MyNAMESPACE.CustomLinqToHqlGeneratorsRegistry, APP.MyNAMESPACE");
..
})
.BuildSessionFactory();