scott-pascoe
scott-pascoe

Reputation: 1483

QueryOver With Custom Projection and Query Parameter

I've defined a query in a class with a property, but am trying to build a fairly complex query using the property and have run into NHibernate telling me that it could not resolve property: DueDate.

My Query class looks like this:

public class SomeQuery {
  public DateTime DueDate { get; private set; }
  public SomeQuery(DateTime dueDate) {
    DueDate = dueDate;
  }

  public QueryOver GetQueryOver() {

    PrimaryObject po = null;
    SubObject so = null;

    return QueryOver.Of<PrimaryObject>(() => po)
      .JoinAlias(() => so.SubObjects, () => so)
      .Where(
        Restrictions.Le(
          DateProjections.DateDiff("d", () so.Value, () = DueDate), 
          0
        )
      );
  }
}

I've implemented the DateProjections Class exactly as described in Andrew Whitaker's blog QueryOver Series - Part 7: Using SQL Functions

The contents of the PrimaryObject and SubObject aren't really important to the example except in the following:

public class PrimaryObject {
   public virtual Guid Id { get; set; }
   public List<SubObject> Implementations { get; set; }
}

public class SubObject {
  public virtual Guid Id { get; set; }
  public virtual string Value { get; set; }
}

For Mappings, you can assume that these fields are mapped to the database in sensible ways, as I don't feel like that is where the issue is.

When I try to use this query in a test, like the following:

var testDate = new DateTime(2015, 06, 01);
IEnumerable<PrimaryObject> result = repository.FindAll(new SomeQuery(testDate));

I get a NHibernate.QueryException:

NHibernate.QueryException : could not resolve property: DueDate of: PrimaryObject

Clearly, I've got an unmapped property, and that is causing the projection to have heartburn.

Looking for a minimal ceremony solution to getting the DueDate mapped. I've looked at Andrew's examples in QueryOver Series - Part 9: Extending QueryOver to Use Custom Methods and Properties, but it felt like a lot of ceremony.

I've also googled for solutions, but my google foo failed me..

Suggestions? Solutions?

Upvotes: 1

Views: 938

Answers (1)

Andrew Shepherd
Andrew Shepherd

Reputation: 45232

The DateDiff implementation on the blog is assuming you wish to calculate the difference between database fields. This isn't what you want: you want to compare one database field with a constant.

You'll have to refactor the set of DateProjections methods to allow you to pass a constant as a parameter:

public static class DateProjections
{
    private const string DateDiffFormat = "datediff({0}, ?1, ?2)";


     // Here's the overload you need
    public static IProjection DateDiff
                  (
                    string datepart,
                    Expression<Func<object>> startDate,
                    DateTime endDate
                  )
   {
         return DateDiff(
                           datePart,
                           Projections.Property(startDate),
                           Projections.Constant(endDate)
                        );
   }        
    // Keeping Andrew Whitaker's original signature
    public static IProjection DateDiff
                             (
                                string datepart, 
                                Expression<Func<object>> startDate,
                                Expression<Func<object>> endDate
                             )
    {
        return DateDiff(
                         datePart,
                         Projections.Property(startDate),
                         Projections.Property(endDate)
                       );
    }
    // Added a function that's shared by 
    // all of the overloads
    public static IProjection DateDiff(
             string datepart,
             IProjection startDate,
             IProjection endDate)
    {
        // Build the function template based on the date part.
        string functionTemplate = string.Format(DateDiffFormat, datepart);

        return Projections.SqlFunction(
            new SQLFunctionTemplate(NHibernateUtil.Int32, functionTemplate),
            NHibernateUtil.Int32,
            startDate,
            endDate);
    }
}

Now you can invoke it like so:

public QueryOver GetQueryOver() {

   PrimaryObject po = null;
   SubObject so = null;

   return QueryOver.Of<PrimaryObject>(() => po)
     .JoinAlias(() => so.SubObjects, () => so)
     .Where(
        Restrictions.Le(
          DateProjections.DateDiff("d", () => so.Value, DueDate), 
          0
        )
  );
}

Upvotes: 1

Related Questions