2

假设我们有以下实体:

public class Customer
{
    public virtual int Id { get; set; }

    public virtual string FirstName { get; set; }

    public virtual string LastName { get; set; }
}

此外还有以下扩展方法:

public static string GetCard(this Customer @this)
   {
         throw new InvalidOperationException("Use only in IQueryable");
   }

现在,我想执行这个查询:

var q = from c in this.Session.Query<Customer>()
        select new { Id = c.Id, InfoCard = c.GetCard() };

我虽然创建和注册以下 hql 生成器就足够了:

class GetCardGenerator : BaseHqlGeneratorForMethod
{
    public GetCardGenerator()
    {
        SupportedMethods = new[]
        {
            ReflectionHelper.GetMethodDefinition(() => CustomerExtensions.GetCard(null))
        };
    }

    public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        Expression<Func<Customer, Card>> definition = c => new Card
            {
                FirstName = c.FirstName,
                LastName = c.LastName,
                FullName = c.FirstName + " " + c.LastName
            };

        return visitor.Visit(definition);
    }
}

不幸的是,异常 NotSupportedException 与消息 MemberInit 一起引发。在投资过程中,我在 Linq\Visitors\SelectClauseHqlNominator.cs 中发现 HQL 不支持 New 和 MemberInit 表达式的评论。

我的问题是:是否可以创建将在 LINQ 查询的 select 子句中使用并用于创建和填充 DTO 对象的方法?

更新:我不想从 IQueryable<Customer> 制作 IQueryable<Card> 但我正在寻找允许我在任何地方从客户中提取卡的解决方案,例如,我有参考客户的订单实体,我'想调用如下查询:

from o in this.Session.Query<Order>
select new  { Amount = o.OrderAmount, Customer = o.Customer.GetCard() };
4

1 回答 1

4

目前你所要求的东西不容易实现,因为处理投影 NHibernate 使用 ClientSideResultOperator 附加到 HQL (见方法)QueryModelVisitor.VisitSelectClause

例如对于以下查询

var q = from c in this.Session.Query<Customer>()
        select new Card
        {
            FirstName = c.FirstName,
            LastName = c.LastName,
            FullName = c.FirstName + " " + c.LastName
        };

NHibernate 将投影转换为基于数组的投影

var q = from c in this.Session.Query<Customer>()
        select new object[]
        {
            c.FirstName,
            c.LastName,
            c.FirstName + " " + c.LastName
        };

并添加以下转换结果

Expression<Func<object[], Card>> projectionExpression = array => new Card
{
    FirstName = (string)array[0],
    LastName = (string)array[1],
    FullName = (string)array[2]
};

但它可能仅适用于以下更简单的情况:

var q = from c in this.Session.Query<Customer>()
        select c.GetCart();

替代解决方案

我可以建议对内联GetCart方法进行预处理查询表达式。您可以手动完成,也可以使用DelegateDecompilerDelegateDecompiler具有扩展方法.Decompile(this IQueryable<T> self),该方法查找表达式树以查找标有[Decomile][Computed]属性的方法和属性,并内联这些方法和属性。

所以你可以做以下

var q = (from c in this.Session.Query<Customer>()
         select new { Id = c.Id, InfoCard = c.GetCard() }).Decompile();

[Decompile]
public static string GetCard(this Customer @this)
{
    return new Card
        {
            FirstName = @this.FirstName,
            LastName = @this.LastName,
            FullName = @this.FirstName + " " + @this.LastName
        };
}

查询将转换为

var q = from c in this.Session.Query<Customer>()
        select new 
        { 
            Id = c.Id, 
            InfoCard = new Card
            {
                FirstName = c.FirstName,
                LastName = c.LastName,
                FullName = c.FirstName + " " + c.LastName
            }) 
        };
于 2013-04-21T13:19:51.883 回答