使用 Orchard CMS 中的接口通过 NHibernate 进行查询

Querying through NHibernate using a Interface in Orchard CMS

我存储记录是为了 link 2 部分记录一起在 Orchard (CMS) 中。
这些记录将引用它们正在 linking 的 2 个项目。
有几个记录表这样做,它们都实现相同的接口。

//One example of a record implementing the common interface
//This relation links a member to a home
public class HomeToMemberRelationRecord : IRelationResultable
{
    public virtual int Id { get; set; }
    [Aggregate]
    public virtual MemberPartRecord MemberPartRecord { get; set; }
    [Aggregate]
    public virtual HomePartRecord HomePartRecord { get; set; }

    //The interface is implemented here
    //...
}

我正在尝试使用界面以通用方式查询这些记录。
我有一个接受类型并从该类型解析 IRepository 的通用方法。
问题是我想在查询中使用的属性因每条记录而异。
在上面的记录中,我可能想从 Home 中获取 Member,但是另一个记录可能是 linking a Dog to a DogHouse 和我想在 DogHouse.
中找到所有 Dog 获取关系的 UI 元素将在需要时解析存储库:

Resolve<IRepository<T>>();

所以无法知道查询是否应该是:

.Where(x => x.HomePartRecord.ContentItemRecord.Id == id)
//or
.Where(x => x.DogHousePartRecord.ContentItemRecord.Id == id)

所以关系记录实现的接口必须定义它们自己如何被查询。

我尝试使用一种方法来 return 我需要查询的 属性,但 NHibernate 不喜欢那样并为我提供了 NotSupportedException

// Method in the interface
int GetId();

// Attempt to call above method from a query.
var repo = _services.WorkContext.Resolve<IRepository<T>>(); //Constraint: where T : IMemberSearchResultable, new()
var relations = repo.Table.Where(x => x.GetRelevantId() == id);

我尝试在我的界面中使用 属性,但是 NHibernate 只会在数据库的记录中查找 属性(它只存在于模型中)。

// Attempt to use a property in the interface instead of a method.
int RelevantId { get; set; } //Usage: .Where(x => x.RelevantId == id)

所以我尝试构建一个表达式:
(剧透:这也失败了。)

    public static Expression<Func<T, bool>> GetExpression<T>(string propertyName, int filterValue)
    {
        var parameter = Expression.Parameter(typeof(T));
        var property = Expression.Property(parameter, propertyName);
        var method = typeof(int).GetMethod("Equals", new[] { typeof(int) });
        var body = Expression.Call(property, method, Expression.Constant(filterValue));
        return (Expression<Func<T, bool>>)Expression.Lambda(body, parameter);
    }

    public Expression<Func<T, bool>> TestGet<T>(int id)
    {
        return GetExpression<T>("MemberPartRecord.ContentItemRecord.Id", id);
    }

我尝试了多种命名 属性 的方法,但找不到有效的方法。
我当然可以在不使用界面的情况下构建我在每种情况下需要的特定查询。这让我相信应该可以使用该界面构建相同的查询。

我是不是构建了错误的表达方式,是错误的方法还是整个想法牵强附会?

我发布了目前的答案,但我希望有人有更好的解决方案:

我已经设法为我的特定问题找到了可行的解决方案,但这不是一个完美的解决方案,因为它不允许您在查询中使用引用。 我基本上只能引用映射到数据库的 C# 记录中存在的属性。

这是我目前使用的方法:

    public static Expression<Func<T, bool>> GetExpression<T, U>(string propertyName, U filterValue)
    {
        var parameter = Expression.Parameter(typeof(T));
        var predicate = Expression.Lambda<Func<T, bool>>(
            Expression.Equal(Expression.Property(parameter, propertyName), 
            Expression.Constant(filterValue)), parameter);
        return predicate;
    }

我这样称呼它:

GetExpression<T, U>("MemberPartRecord", rec);

这里的显着区别是我现在查询 MemberPartRecord 而不是尝试访问 MemberPartRecord.ContentItemRecord.Id
对于我目前的需求,这已经足够了,但这只是因为我能够提供查询所需的记录。

我不会接受这个答案,希望有人能提供一个完整的答案,允许在查询中使用引用的记录。

更改映射

如果可以选择更改映射(也可能是整个界面),您应该尝试将您的实体映射为从您的通用界面继承,如 inheritance mapping documentation 中所示。它应该在没有基础 class.

的情况下工作

但这意味着您的接口将定义一些 HomeBase 属性(类型为基础 class 或另一个通用接口),它们将被映射,并且将作为在你的实体中。

然后你会在你的实体上添加一些特殊的 ConcreteHomeDogHouse 属性,而不是映射,并将 HomeBase 投射到具体的家。

可能的问题

当心代理,这样的设置可能会迫使您在 HomeBase 属性 映射上使用 lazy="no-proxy"lazy="false"

此外,a post 写道这在 Fluent 映射中不受支持(我使用 .hbm 文件)。

此外,如果您需要查询相关的主页特定属性(不属于 HomeBase),您将遇到新的麻烦。

深入了解运行时类型检查

你可以用你目前的方法走得更远。但这需要相当多的修补。

目的是得到一个更有能力的帮手:

public static Expression<Func<T, bool>> GetExpression<T, U>(
    Expression<Func<I, BaseHome>> interfaceHomePropertySelector, U filterValue)

helper 实现应该获取接口 属性 memberInfo,然后在 T 实体中推断相应的成员。由此,它将构建适当的表达式。

出于性能原因,应缓存生成的推断以减少后续使用的运行时成本。

肮脏的推断

您可以通过检查其所有属性来推断 T 中的具体 属性,并取第一个与接口 属性 兼容的,但与 属性 的名称不同界面 属性。这要求您的每个实体中只有一个主页 属性。

更难推断

您可能会采用更详细的方法来获得正确的 属性:在 T 的实例上访问接口 属性 时实际调用的内容的运行时评估。为此,您需要使用与 NHibernate 用于处理延迟加载相同的代理方法。

实例化一个新的 T 代理虚拟实例,用于在每次 属性 访问时调用您的回调。访问界面属性。您的回调应该至少触发两次:在接口 属性 访问时,然后在具体 属性 访问接口实现时应该执行。

所以在你的回调中你必须检查调用堆栈来检查你是哪种情况,并推断出 T.属性 的具体