使用 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 集合是否有更好的方法?

作为练习,我决定扩展 以支持 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。我添加了一些只是为了测试,然后我全部撤消了。