QueryOver With Custom Projection and Query Parameter - nhibernate

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?

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

Related

Using FromSqlRaw or Linq query to Group and Sum data

I am trying to use a query with GroupBy and Sum. First I tried it with SQL:
string query = $"SELECT Year(Datum) AS y, Month(Datum) AS m, SUM(Bedrag) AS Total FROM Facturens GROUP BY Year(Datum), Month(Datum) ORDER BY y, m";
Grafiek = await _db.Facturens.FromSqlRaw(query).ToListAsync();
I get this error:
"InvalidOperationException: The required column 'FacturenID' was not present in the results of a 'FromSql' operation." "FacturenID" is the first column in the Facturens table.
The SQL query works fine when used directly.
I then tried Linq:
Grafiek = (IEnumerable<Factuur>)await _db.Facturens
.GroupBy(a => new { a.Datum.Value.Year, a.Datum.Value.Month }, (key, group) => new
{
jaar = key.Year,
maand = key.Month,
Total = group.Sum(b => b.Bedrag)
})
.Select(c => new { c.jaar, c.maand, c.Total })
.ToListAsync();
This results in error: "InvalidOperationException: Nullable object must have a value."
Factuur:
using System.ComponentModel.DataAnnotations;
namespace StallingRazor.Model
{
public class Factuur
{
[Key]
public int FacturenID { get; set; }
public int EigenarenID { get; set; }
[Display(Name = "Factuurdatum")]
[DataType(DataType.Date)]
[DisplayFormat(NullDisplayText = "")]
public DateTime? Datum { get; set; }
public decimal? Bedrag { get; set; }
public decimal? BTW { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(NullDisplayText = "")]
public DateTime? Betaaldatum { get; set; }
[Display(Name = "Betaald bedrag")]
public decimal? Betaald_bedrag { get; set; }
[Display(Name = "Totaal bedrag")]
public decimal? Totaal_bedrag { get; set; }
public int ObjectenID { get; set; }
[DataType(DataType.Date)]
public DateTime? Verzonden { get; set; }
public string? Mededeling { get; set; }
[Display(Name = "Begindatum")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{dd-MM-yyyy}", NullDisplayText = "")]
public DateTime? Begin_datum { get; set; }
[Display(Name = "Einddatum")]
[DataType(DataType.Date)]
[DisplayFormat(NullDisplayText = "")]
public DateTime? Eind_datum { get; set; }
}
}
When performing aggregate queries against a model using SQL, the result will not and in general cannot easily be the same structural form as the original model, the Set<T>.FromSqlRaw() method that you are using requires the SQL to resolve ALL of the properties for the specified type of T
FromSqlRaw Limitations
The SQL query must return data for all properties of the entity type.
The column names in the result set must match the column names that properties are mapped to. Note this behavior is different from EF6. EF6 ignored property to column mapping for raw SQL queries and result set column names had to match the property names.
The SQL query can't contain related data. However, in many cases you can compose on top of the query using the Include operator to return related data (see Including related data).
For aggregate queries, we would generally define a new type to hold the response from the SQL aggregate. In C# LINQ GroupBy behaves very differently to SQL, in SQL the detail rows are excluded and only the aggregate set is returned. In LINQ all of the rows are retained, but they are projected into groups by the key, there is no specific aggregation at all, after a LINQ groupby you would them perform any aggregate analysis you may require.
The first thing we need to do is define the structure of the response, something like this:
public class FactuurSamenvatting
{
public int? Jaar { get; set; }
public int? Maand { get; set; }
public int? Total { get; set; }
}
Then if this type is registered with the DBContext as a new DbSet:
/// <summary>Summary of Invoice Totals by Month</summary>
public Set<FactuurSamenvatting> FacturenOmmen { get;set; }
You can then use this raw SQL query:
string query = $"SELECT Year(Datum) AS Jaar, Month(Datum) AS Maand, SUM(Bedrag) AS Total FROM Facturens GROUP BY Year(Datum), Month(Datum) ORDER BY Jaar, Maand";
var grafiek = await _db.FacturenOmmen.FromSqlRaw(query).ToListAsync();
Ad-Hoc Generic Solution
Though the above solution is encouraged, it is possible to achieve the same thing without formally adding your aggregate type directly to your DbContext. Following this advice from #ErikEj and his updated reference on Github we can create a dynamic context that explicitly contains the setup for any generic type
public static class SqlQueryExtensions
{
public static IList<T> SqlQuery<T>(this DbContext db, string sql, params object[] parameters) where T : class
{
using (var db2 = new ContextForQueryType<T>(db.Database.GetDbConnection()))
{
return db2.Set<T>().FromSqlRaw(sql, parameters).ToList();
}
}
private class ContextForQueryType<T> : DbContext where T : class
{
private readonly DbConnection connection;
public ContextForQueryType(DbConnection connection)
{
this.connection = connection;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connection, options => options.EnableRetryOnFailure());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<T>().HasNoKey();
base.OnModelCreating(modelBuilder);
}
}
}
Now we do not need to pre-register the aggregate types at all, you can simply use this syntax to execute you query:
You can then use this raw SQL query:
string query = $"SELECT Year(Datum) AS Jaar, Month(Datum) AS Maand, SUM(Bedrag) AS Total FROM Facturens GROUP BY Year(Datum), Month(Datum) ORDER BY Jaar, Maand";
var grafiek = _db.SqlQuery<FactuurSamenvatting>(query).ToList();
Original Response
Updated after Factuur model posted
Below is a general walk through responding to the original post and the specific exceptions that were raised. I had originally assumed that OP was using an aggregate type definition, I had forgotten that to do so is itself an advanced technique, the following response is still helpful if you define your aggregate type correctly but still observe the same exceptions.
LINQ expressions in general that project into a known type will throw two common errors:
InvalidOperationException: The required column 'FacturenID' was not present...
This error is reasonably obvious, the model Factuur that you are projecting into has a required column called FacturenID, which your output does not provide. Your projection in the first attempt is expecting these columns in Factuur:
public int y { get;set; }
public int m { get;set; }
public int? Total { get;set; }
If you change the first query to use the matching property names of those existing in Factuur then you will most likekly still encounter the next issue...
The error InvalidOperationException: Nullable object must have a value. is experienced in two situations:
When your LINQ expression is operating in memory and tries to access a property on an object that is null, most likely in the case of the second query this can occur if any values of Datum are null, that would invalidate Datum.Value.
this syntax is allowed even if the field is null if the expression is being evaluated in SQL, the result will simply be null.
When a SQL result is projected into a c# type, when a value in one of the columns in the result set is null but the corresponding property of the type you are projecting into does not allow for nulls.
In this case one of the jaar,maand,Total columns needs to be null, usually it will be the result of the SUM aggregate but in this case that can only happen if Bedrag is nullable in your dataset.
Test your data by inspecting this recordset, notice that I am NOT casting the results to a specific type, we will leave them in the anonymous type form for this analysis, also we will exclude null datums. for this test.
var data = await _db.Facturens
.Where (f => f.Datum != null)
.GroupBy(a => new { a.Datum.Value.Year, a.Datum.Value.Month }, (key, group) => new
{
jaar = key.Year,
maand = key.Month,
Total = group.Sum(b => b.Bedrag)
})
.Select(c => new { c.jaar, c.maand, c.Total })
.ToListAsync();
In your original query, to account for the nulls and return zero for the Total instead of altering your model to accept nulls, then you could use this:
string query = $"SELECT Year(Datum) AS jaar, Month(Datum) AS maand, SUM(ISNULL(Bedrag,0)) AS Total FROM Facturens GROUP BY Year(Datum), Month(Datum) ORDER BY jaar, maand";
Grafiek = await _db.Facturens.FromSqlRaw(query).ToListAsync();
In this SQL we didn't need to exclude the null datums, these will be returned with respctive values of null for both of jaar and maand
Given that the only case where jaar and maand might be null is if the column Datum has a null value so you could use this SQL to return the same columns as the expected type without modifying the model, as long as these were all the columns in the model. In this case I would recommend excluding those records from the results with a simple WHERE clause
SELECT
Year(Datum) AS jaar
, Month(Datum) AS maand
, SUM(ISNULL(Bedrag,0)) AS Total
FROM Facturens
WHERE Datum IS NOT NULL
GROUP BY Year(Datum), Month(Datum) ORDER BY jaar, maand

how do we return all attributes of a node with neo4jclient?

below code(search function) works fine.
public class BookItem
{
public string Title { get; set; }
public string OriginalTitle { get; set; }
}
public IEnumerable<dynamic> Search(string keyword)
{
/*MATCH (n:`Book`) RETURN n*/
var query = client
.Cypher
.Match("(n:Book)")
.Return(n => n.As<BookItem>());
return query.Results;
}
However, i don't want to declare a class like BookItem. I just want all results in a dynamic object. Is there a way to do that?
For example below code runs and returns empty object, it doesn't return any attributes..
public IEnumerable<dynamic> Search(string keyword)
{
/*MATCH (n:`Book`) RETURN n*/
var query = client
.Cypher
.Match("(n:Book)")
.Return(n => n.As<dynamic>());
return query.Results;
}
The basic gist is in the answer to this question: Casting nodes of an unknown type
What you end up returning is Node<string> and parsing using Json.net into a dynamic object, there is no direct way of just doing x.As<dynamic>() unfortunately.

Automapper resolveusing not returning nulls

I'm working on an MVC 4 project and trying to convert a value in a KeyValue list to a nullable DateTime. I have used the following line in the mapper (I've not included the other properties as there are a lot)
.ForMember(d => d.Deadline, m => m.ResolveUsing<DeadlineResolver>())
My resolver looks like this:
public class DeadlineResolver : ValueResolver<Booking, DateTime?>
{
protected override DateTime? ResolveCore(Booking source, ResolutionResult resolutionResult)
{
KeyValue keyValue = source.KeyValues.FirstOrDefault(k => k.Key.KeyId == "DEADLINE");
return (keyValue != null) ? DateTime.Parse(keyValue.Value) : (DateTime?)null;
}
}
The value of deadline which is defined as shown below is never returned as null but DateTime.MinDate instead. I need it to be null when I'm the binding the result in a view so that I only show a value when there is a date.
public DateTime? Deadline { get; set; }
How do I make these values null without going over the values after mapping to look for min dates and set to null (temp hack I've put in place so the code runs)?
Using LinqPad and AutoMapper 2.2.1 the following gives me a valid date when KeyValue has a date, and a null DateTime when KeyValue is null. (Note there are minor changes to the resolver to simplify it as the class definitions weren't provided).
void Main()
{
AutoMapper.Mapper.CreateMap<Booking, dest>()
.ForMember(d => d.Deadline, m => m.ResolveUsing<DeadlineResolver>());
AutoMapper.Mapper.AssertConfigurationIsValid();
// Gives a valid DateTime
var booking = new Booking { KeyValue = "2013-01-01" };
booking.Dump();
var rc = AutoMapper.Mapper.Map<Booking, dest>(booking);
rc.Dump();
// Gives a null DateTime
booking = new Booking { KeyValue = null };
booking.Dump();
rc = AutoMapper.Mapper.Map<Booking, dest>(booking);
rc.Dump();
}
// Define other methods and classes here
public class Booking
{
public string KeyValue { get; set; }
}
public class dest
{
public DateTime? Deadline { get; set; }
}
public class DeadlineResolver : AutoMapper.ValueResolver<Booking, DateTime?>
{
protected override DateTime? ResolveCore(Booking source)
{
return (source.KeyValue != null)
? DateTime.Parse(source.KeyValue)
: (DateTime?)null;
}
}
Is this the functionality you were after? If so, then the issue could be with an older version of AutoMapper, or an unexpected KeyValue value.

NHibernate: HqlGenerator: Create calculated property by concatenating other properties

I am trying to add an HqlGenerator to allow NHibernate to query a calculated property on the following domain entity:
public class User
{
public string FirstName { get; set; }
public string LastName {get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
I have created an HQL generator like so:
public class UserFullName : BaseHqlGeneratorForProperty
{
public UserFullName()
{
var properties = new List<MemberInfo> { typeof(User).GetProperty("FullName") };
SupportedProperties = properties;
}
public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
var user = visitor.Visit(expression).AsExpression();
return treeBuilder.Concat(
treeBuilder.Dot(user, treeBuilder.Ident("FirstName")),
treeBuilder.Constant(" "),
treeBuilder.Dot(user, treeBuilder.Ident("LastName"))
);
}
}
The generator is wired up correctly into the configuration as the debugger will break within the BuildHql method when I run the query:
session.Query<User>().FirstOrDefault(x => x.FullName == "Aaron Janes");
however, later on in the NHibernate internals an exception is thrown:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException : Exception of type 'Antlr.Runtime.NoViableAltException' was thrown. [.FirstOrDefault[User](NHibernate.Linq.NhQueryable`1[User], Quote((x, ) => (String.op_Equality(x.FullName, Aaron Janes))), )]
Can anyone spot what I am doing wrong?
In your BuildHQL() you include the subexpression 'user' multiple times. This is not possible. You need to use
var user1 = visitor.Visit(expression).AsExpression();
var user2 = visitor.Visit(expression).AsExpression();
and use each of those once in the Concat expression.

Fluent NHibernate Combine Date and Time field into DateTime object

I have a database table (which I cannot change) which stores the date and time in separate fields but my class only has one DateTime property (Opened).
DateOpened 2011-05-10 00:00:00.000
TimeOpened 1899-12-30 09:53:00.000
In SQL I could just do
SELECT DateOpened + TimeOpened AS 'Opened'
How can I map this in Fluent NHibernate? I'm using Fluent Mapping.
I have tried
Map(x => x.Opened).Columns.Add(new string[] { "DateOpened", "TimeOpened" });
but I get the following error
property mapping has wrong number of columns: CBS.Tigerpaw.Data.ServiceOrder.Opened type: DateTime
if IUsertype is an option
public class DateTimeUserType : ImmutableUserType
{
public override object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var date = (DateTime)NHibernateUtil.DateTime.NullSafeGet(rs, names[0]);
var time = (DateTime)NHibernateUtil.DateTime.NullSafeGet(rs, names[0]);
return new DateTime(date.Year, ..., time.Hours, ...);
}
public override void NullSafeSet(IDbCommand cmd, object value, int index)
{
DateTime dt = (DateTime)(value ?? DateTime.MinValue);
NHibernateUtil.DateTime.NullSafeSet(cmd, dt.Date, index);
NHibernateUtil.DateTime.NullSafeSet(cmd, new DateTime(1899, 12, 30, dt.Hours, dt.Minutes, dt.Seconds), index + 1);
}
public override Type ReturnedType
{
get { return typeof(DateTime); }
}
public override SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.DateTime, SqlTypeFactory.DateTime }; }
}
}
Map(x => x.Opened)
.Columns.Add("DateOpened", "TimeOpened")
.CustomType<DateTimeUserType>();
you can define that DateOpened + TimeOpened by using the .Map(...).Formula(...) & additional property in your class which has a private setter.
You might consider mapping the two separate columns independently and then having your class offer an additional (and un-mapped) "Opened" property that integrates their values. Regrettably, the two mapped properties would still be visible as public properties, since Fluent NHibernate requires this so that the lambda expressions in the mapping class can get at them.