Domanda

Let's assume that we've got following entity:

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

    public virtual string FirstName { get; set; }

    public virtual string LastName { get; set; }
}

In addition there is following extension method:

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

Now, I want to perform this query:

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

I though that creating and registering following hql generator will be enough:

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);
    }
}

Unfortunately exception NotSupportedException is thrown with message MemberInit. During investingation I found comment in Linq\Visitors\SelectClauseHqlNominator.cs that HQL does not support New and MemberInit expression.

My question is: is it possible to create method that will be used in select clause of LINQ query and will be used to create and fill DTO object?

UPDATE: I don't want to make IQueryable<Card> from IQueryable<Customer> but I'm looking for solution that allows me to extract Card from Customer in any place, e.g. I've got Order entity with reference to Customer, I'd like to invoke query like:

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

Soluzione

Currently the thing you are asking for is not easy to implement, because to handle projections NHibernate uses ClientSideResultOperator additional to HQL (see QueryModelVisitor.VisitSelectClause methd)

For example for following query

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

NHibernate translates projection to array based projection

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

And adds folowing transformation result

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

But it may work only for following simplier case:

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

Alternative solution

I can suggest pre-process query expression to inline GetCart method. You can do it manually, or use DelegateDecompiler. DelegateDecompiler has extension method .Decompile(this IQueryable<T> self) which looks expression tree for methods and properties marked with [Decomile] or [Computed] attributes and inlines these methods and properties.

So you can do following

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
        };
}

The query will be transformed to

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
            }) 
        };
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top