具有不同根实体类型的 QueryOver ProjectionList

QueryOver ProjectionList with different root entity types

我在尝试在 NHibernate QueryOver 中重用 ProjectionList 时遇到问题。我不知道如何为不同的根实体重用东西。

对象模型大致表示为:

早餐一对多糕点多对零或一咖啡

两个单独的查询大致是:

session.QueryOver<Breakfast>()
    .Where(b => b.Id == searchId)
    .Inner.JoinQueryOver(b => b.Pastries, () => pastry)
    .Left.JoinAlias(p => p.Coffee, () => coffee)
    .Select(projections)
    .TransformUsing(Transformers.AliasToBean<TargetDto>())
    .List<TargetDto>();

session.QueryOver<Coffee>()
    .Where(c => c.Id == searchId)
    .Inner.JoinQueryOver(c => c.Pastries, () => pastry)
    .Select(projections)
    .TransformUsing(Transformers.AliasToBean<TargetDto>())
    .List<TargetDto>();

我尝试重用的常见投影如下所示:

var projections = Projections.ProjectionList()
    .Add(Projections.Property(() => pastry.Name, () => target.PastryName))
    .Add(Projections.Property(() => coffee.Name, () => target.CoffeeName));

这些使用别名的投影对于第一个查询(根:Breakfast)工作正常,因为它们不会尝试提取该根实体上的属性。在第二个查询(root:Coffee)中,它爆炸说它无法在 Coffee 上找到 'coffee.Name',因为它不喜欢别名。 QueryOver(() => coffee) 语法没有帮助,因为它实际上并没有将 'coffee' 注册为别名,它只是将其用于类型推断。 天啊见鬼,这就是问题所在。有一个愚蠢的应用程序基础架构破坏了别名语法,实际上不使用下面的别名版本。

第二个查询希望投影看起来像:

var projections = Projections.ProjectionList()
    .Add(Projections.Property(() => pastry.Name, () => target.PastryName))
    .Add(Projections.Property<Coffee>(c => c.Name, () => target.CoffeeName));

然而,这现在与第一个查询不兼容。

有什么方法可以使这项工作起作用,这样我就可以在不知道根实体类型是什么的情况下投影属性吗?

我认为您需要做的就是在 session.QueryOver<Coffee> 调用中分配 coffee 别名:

Coffee coffee = null;

session.QueryOver<Coffee>(() => coffee)
    /* etc */

以下内容可能与您正在做的事情完全无关,但我想我会包括它以防其他人正在编写传递 QueryOver 别名的代码。


我要提醒一句——像这样在不同的查询中重复使用别名可能有点危险。

NHibernate 采用表达式 () => coffee 并从表达式中获取您正在使用的别名的名称(在本例中为 "coffee"),然后在生成的 SQL 作为别名。这意味着根据您的代码结构,如果别名更改,像这样的共享投影可能会中断。

例如,假设您有以下方法来 return 一些共享投影:

public ProjectionList GetSharedProjections()
{
    Coffee coffee = null;
    TargetDTO target;

    var projections = Projections.ProjectionList()
        .Add(Projections.Property(() => coffee.CoffeeName)
            .WithAlias(() => target.CoffeeName));

    return projections;
}

然后你有一些代码调用你的辅助方法:

session.QueryOver<Coffee>(() => coffee)
    .Select(GetSharedProjections());

一切都会正常进行——只要您的别名匹配。但是,第二个任何人更改其中任何一个别名,查询都会失败。

您可能想将别名传递给这样的方法:

public ProjectionList GetSharedProjections(Coffee coffeeAlias)
{
    /* Same as above except with "coffeeAlias"
}

然后传入你的别名:

session.QueryOver<Coffee>(() => coffee)
    .Select(GetSharedProjections(coffee));

但这也行不通。请记住,NHibernate 正在获取别名的名称并直接在生成的 SQL 中使用它。上面的代码将尝试在生成的 SQL 中同时使用 "coffee""coffeeAlias",但会失败。

正确执行此操作的一种方法(不只是希望没有人更改别名)是传递表达式并使用它们来重建具有正确别名的 属性 名称。

您将创建一个辅助方法,使用别名和 属性:

构建 属性 访问
public static PropertyProjection BuildProjection<T>(
    Expression<Func<object>> aliasExpression,
    Expression<Func<T, object>> propertyExpression)
{
    string alias = ExpressionProcessor.FindMemberExpression(aliasExpression.Body);
    string property = ExpressionProcessor.FindMemberExpression(propertyExpression.Body);

    return Projections.Property(string.Format("{0}.{1}", alias, property));
}

然后,您可以更改 GetSharedProjections 方法以采用表达式形式的别名:

public ProjectionList GetSharedProjection(Expression<Func<Coffee>> coffeeAlias)
{
    TargetDTO target = null; 

    var projections = Projections.ProjectionList()
        .Add(BuildProjection<Coffee>(coffeeAlias, c => c.CoffeeName))
            .WithAlias(() => target.CoffeeName);
}

现在,调用您的方法如下所示:

session.QueryOver<Coffee>(() => coffee)
    .Select(GetSharedProjections(() => coffee));

当有人更改您的别名时,您就会受到保护。您还可以在许多查询中安全地使用此方法,而不必担心别名变量的实际名称是什么。

免责声明:以下是link转自我的个人博客

您可以通过这种方式找到有关构建 QueryOver 查询的更多信息here