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
Related
I want to execute a raw SQL query as shown below:
select material, method, count(*)
from treatment
group by material, method
and return it as a JSON object.
Because EF 6 allows the execution of raw query (i.e., Database.SQLQuery()), I use it to create a JSON Object.
My code looks like this.
// GET: Dummy
public string Dummy()
{
string query = "select material, method, count(*) as cnt from Treatment "
+ " group by material, method";
var result = db.Database.SqlQuery<List<String>>(query);
var jsonResult = JsonConvert.SerializeObject(result);
return jsonResult;
}
However, instead of getting a JSON object with the material and methods, I get an empty json object instead.
[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
Is it possible to return the correct JSON object without having the model for the the raw query? If so, how to update my code to return the correct result?
Thanks in advance
You can't resolve that query to a list of strings, you need to resolve it to a (list of) class(es), like this :
public static string Dummy()
{
using (var db = new TestDbEntities())
{
string query = "select 'material' as material , 'method' as method, 1 as cnt ";
var result = db.Database.SqlQuery<MyClass>(query);
var res = result.ToList();
var jsonResult = JsonConvert.SerializeObject(res);
return jsonResult;
}
}
where your class will be something like this :
public class MyClass
{
public string Material { get; set; }
public string Method { get; set; }
public int Cnt { get; set; }
}
You need to capture query result sets with multiple columns using a class with matching property names (make sure they have exactly same name and proper casing) and parameterless constructor:
public string Dummy()
{
string query = "select material as Material, method as Method, count(*) as Cnt from Treatment group by material, method";
var result = db.Database.SqlQuery<ResultSet>(query).ToList();
var jsonResult = JsonConvert.SerializeObject(result);
return jsonResult;
}
public class ResultSet
{
public string Material { get; set; }
public string Method { get; set; }
public int Cnt { get; set; }
}
Note that Database.SqlQuery<T> will return an IEnumerable<T> with type System.Data.Entity.Internal.InternalSqlQuery<T>, so that a List<string> is not fit in this context. If you just return single result set, Database.SqlQuery<string>(...).ToList() should be used instead of Database.SqlQuery<List<string>>.
Similar issue:
Entity framework raw SQL Query
Use FOR JSON AUTO:
string query = "select material, method, count(*) as cnt from Treatment "
+ " group by material, method FOR JSON AUTO";
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
)
);
}
I'm using NHibernate 3.33 and QueryOver with Postgre 9.2.
I've got two entities:
public class User {
public virtual string Name { get; set; }
public virtual IList<Reports> Reports { get; set; }
}
and
public class Report {
public virtual string Type { get; set; }
public virtual DateTime ReportDate { get; set; }
public virtual User Author { get; set; }
}
with association - one-to-many (I didn't append additional fields to entities like Id or Name to snippets above). Some report's types are avaliable - month, day.
My goal is to get summary for user - find out whether user has day-report and month-report for current day.
Note: month-report's ReportDate looks like first day of month. Also I want to get it as one row (if it was an SQL) to transform to dto:
public class UserSummaryDto {
public bool HasDayReport { get; set; }
public bool HasMonthReport { get; set; }
}
To achieve my goal I've tried following:
Report dayReport = null;
Report monthReport = null;
var currentDay; // some value of current day
var firstDay; // some value of first day of month
var report = session.QueryOver<User>
.Left.JoinAlias(u => u.Reports, () => dayReport, r => r.ReportDate == currentDay)
.Left.JoinAlias(u => u.Reports, () => monthReport, r => r.ReportDate == firstDat)
.SelectList(
// some logic to check whether user has reports
.TransformUsing(Transformers.AliasToBean<UserSummaryDto>())
.List<UserSummaryDto>()
And I've got error:
'duplicate association path:Reports'.
Is it possible to avoid this problem or it's a limitation of HNibernate?
To answer your question:
...Is it possible to avoid this problem or it's a limitation of HNibernate?
Have to say NO.
For more information see similar Q & A: Rename NHibernate criteria
We are not querying the DB, not using SQL (which does allow to do a lot). Here we work with "mapped" domain model, and that could bring some limitations - as the one discussed here...
If that could help, the workaround is to map such property twice and use the WHERE clause: 6.2. Mapping a Collection
where="" (optional) specify an arbitrary SQL WHERE condition to be used when retrieving or removing the collection (useful if the collection should contain only a subset of the available data)
i have to retrive some data and count of the rows based on the criteria
the bellow is my code snippet
criteria.SetProjection(Projections.ProjectionList().Add(Projections.Property("LastUpdatedUserName"),"OperatorName")
.Add(Projections.Property("Created"),"enrollmentdate")
.Add(Projections.Count("NIK"), "enrollmentcount")
.Add(Projections.GroupProperty("LastUpdatedUserName"))
.Add(Projections.GroupProperty("Created")))
.SetResultTransformer(NHibernate.Transform.Transformers.AliasToEntityMap);
var result = criteria.List<Demographic>()
and this snippet resulting in exception while running
here goes the exception
ex.message=Unable to perform find[SQL: SQL not available]
ex.innerexception={"The value \"System.Collections.Hashtable\" is not of type \"Indo.Id.Data.Infrastructure.Entities.Demographic\" and cannot be used in this generic collection.\r\nParameter name: value"}
and the stack trace is
at System.ThrowHelper.ThrowWrongValueTypeArgumentException(Object value, Type targetType)
at System.Collections.Generic.List1.VerifyValueType(Object value)
at System.Collections.Generic.List1.System.Collections.IList.Add(Object item)
at NHibernate.Util.ArrayHelper.AddAll(IList to, IList from) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Util\ArrayHelper.cs:line 233
at NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1948
type casting of the transformation to demographic will work to max extent but here in demo graphics i have approx 40 columns and i have declared a new class for holding the result like
public class operatorenrollment
{
public string OperatorName { get; set; }
public DateTime enrollmentdate { get; set; }
public int enrollmentcount { get; set; }
}
can i now transform this to new class like
NHibernate.Transform.Transformers.AliasToBean(typeof(operatorenrollment))
any help here is very much appriciable
thanks in adv
The error is pretty clear.
You are using Transformers.AliasToEntityMap, which transforms your projection into an IDictionary, and trying to get back a list of Demographic.
Use Transformers.AliasToBean<Demographic>() instead.
OK, first my simple Domain Model is 2 classes with a one-to-many relationship, a simple Parent -> child relationship. A 'Tweet' has one or more 'Votes', but each Vote belongs to just one Tweets etc.
public class Tweet
{
public virtual long Id { get; set; }
public virtual string Username { get; set; }
public virtual string Message { get; set; }
public virtual ISet<Vote> Votes { get; set; }
}
public class Vote
{
public virtual long Id { get; set; }
public virtual long TwitterUserId { get; set; }
public virtual DateTime VotedDate { get; set; }
public virtual Tweet Tweet { get; set; }
}
I'm trying to write a query in either HQL, ICriteria or NHibernate LINQ, that selects all Tweets, but also add two columns that selects:
A count of the number of votes, and...
Whether a particular user has voted for that tweet, based on a particular TwitterUserId
With the two extra columns, I'm not expecting to get Tweet domain object back, and would probably need to run a Report query, that's OK. But I'm struggling to figure out how to write this query.
I know how to write this as a Stored Procedure, or using LINQ 2 SQL. If it helps I will express this as a LINQ to SQL query.
long userId = 123;
var tweets = from t in dataContext.Tweets
where t.Application == app
orderby t.PostedDate desc
select new TweetReport()
{
Id = t.Id,
Username = t.Username,
Message = t.Message,
TotalVotes = t.Votes.Count(),
HasVoted = t.Votes.Any(v => v.TwitterUserId == userId)
};
I know this will work in LINQ 2 SQL, and generate reasonably efficient T-SQL, but I can't figure out how to write this in NHibernate.
Update: I tried running the above LINQ query in NHibernate by using the NHibernate LINQ provider built for NHibernate v2, eg:
var tweets = from t in Session.Linq<Tweet>()
where (snip)
But it didn't work. Has LINQ support in Nhibernate 3.0 improved? I'm a bit reluctant to use version 3.0 because it's still alpha, but if this will work, then I might give it a go.
Update 2: Thanks to Diego Mijelshon's suggestion, I upgraded to NHibernate 3.0 alpha 2 and wrote the query in LINQ:
var tweets = from t in Session.Query<Tweet>()
where t.App == app
orderby t.PostedDate descending
select t;
int totalRecords = tweets.Count();
var pagedTweets = (from t in tweets
select new TweetReport()
{
Id = t.Id,
TwitterId = t.TweetId,
Username = t.Username,
ProfileImageUrl = t.ImageUrl,
Message = t.Message,
DatePosted = t.PostedDate,
DeviceName = t.Device.Name,
DeviceUrl = t.Device.Url,
TotalVotes = t.Votes.Count(),
HasVoted = t.Votes.Any(v => v.TwitterUserId == userId)
})
.Skip(startIndex)
.Take(recordsPerPage)
.ToList();
return new PagedList<TweetReport>(pagedTweets,
recordsPerPage, pageNumber, totalRecords);
It's exactly the same with NHibernate 3; just replace dataContext.Tweets with session.Query<Tweet>.
Alternatively, you can create a context class that exposes session.Query<Tweet> as an IQueryable<Tweet> Tweets property, then the code would be 100% unchanged.