使用 linq 查询映射为地图的集合(IDictionary)
Querying with linq a collection mapped as a map (IDictionary)
使用 NHibernate,我将一组实体映射为字典。
例如,class A 有一个名为 Children
的 B 集合,映射为 IDictionary<int, B>
。 B有一个属性Name
.
基于 B children 的某些条件查询 class A 与其字典索引无关,使用 HQL
:
非常简单
from A where A.Children.Name = 'aName'
运行完美。
但要实现与 LINQ
相同的效果,这就不那么简单了:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
消息失败 could not resolve property: Values of: B
所以是的,我们可以欺骗它
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
这确实有效并产生了预期的结果。
但这在我看来有点难看,我宁愿不必这样做 'invalid' 转换(至少 'invalid' 在 linq2NH 上下文之外)。
使用 Linq 和 NHibernate 查询映射为 IDictionary 的 children 集合是否有更好的方法?
作为练习,我决定扩展 linq-to-nhibernate 以支持 Values
。这给出了问题的解决方案。
有很多扩展 linq2NH 的方法,请参阅此 list. Here, I need to add a new 'generator', as in my answer to another question。
首先你需要一堆using
:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
然后,为Values
实现HQL翻译。
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
使用生成器扩展默认的 linq2NH 注册表:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
我很难搞清楚如何注册通用 class 属性 生成器。有对许多情况的内置支持,包括通过 GenericDictionaryRuntimeMethodHqlGeneratorBase
派生的 class 的通用字典方法,但显然不支持通用字典属性。所以我在属性的 TryGetGenerator
方法中结束了 'hard coding' 它。
现在配置 NH 以使用您的新注册表。使用 hibernate.cfg.xml,在 session-factory
节点下添加以下 property
节点:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
现在这行得通了:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
免责声明:仅作为练习完成,我什至没有在我的实际代码中提交。我目前不再在我的映射中使用任何 map
。我添加了一些只是为了测试,然后我全部撤消了。
使用 NHibernate,我将一组实体映射为字典。
例如,class A 有一个名为 Children
的 B 集合,映射为 IDictionary<int, B>
。 B有一个属性Name
.
基于 B children 的某些条件查询 class A 与其字典索引无关,使用 HQL
:
from A where A.Children.Name = 'aName'
运行完美。
但要实现与 LINQ
相同的效果,这就不那么简单了:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
消息失败 could not resolve property: Values of: B
所以是的,我们可以欺骗它
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
这确实有效并产生了预期的结果。
但这在我看来有点难看,我宁愿不必这样做 'invalid' 转换(至少 'invalid' 在 linq2NH 上下文之外)。
使用 Linq 和 NHibernate 查询映射为 IDictionary 的 children 集合是否有更好的方法?
作为练习,我决定扩展 linq-to-nhibernate 以支持 Values
。这给出了问题的解决方案。
有很多扩展 linq2NH 的方法,请参阅此 list. Here, I need to add a new 'generator', as in my answer to another question。
首先你需要一堆using
:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
然后,为Values
实现HQL翻译。
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
使用生成器扩展默认的 linq2NH 注册表:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
我很难搞清楚如何注册通用 class 属性 生成器。有对许多情况的内置支持,包括通过 GenericDictionaryRuntimeMethodHqlGeneratorBase
派生的 class 的通用字典方法,但显然不支持通用字典属性。所以我在属性的 TryGetGenerator
方法中结束了 'hard coding' 它。
现在配置 NH 以使用您的新注册表。使用 hibernate.cfg.xml,在 session-factory
节点下添加以下 property
节点:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
现在这行得通了:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
免责声明:仅作为练习完成,我什至没有在我的实际代码中提交。我目前不再在我的映射中使用任何 map
。我添加了一些只是为了测试,然后我全部撤消了。