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.
Related
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
I have an index.chtml set up with about 10 ActionLinks. Those actionLinks trigger different ActionResult functions within the controller since each of them essentially perform unique queries on a data model.
I also have an entities object named db which has all the data. Instead of just displaying all the data, I want to perform complex filtering on the entities object to find where certain properties of records are null or where a property is greater than some input then returns a view of all columns on only those records that were filtered.
Find nulls:
public class printJobsController : Controller {
private PrintJobsEntities db = new PrintJobsEntities
public ActionResult IncompleteJobs {
//get jobs where processDate is null
...
}
}
Find where count is greater than 10:
public class printJobsController : Controller {
private PrintJobsEntities db = new PrintJobsEntities
public ActionResult JobsGreaterThan(int limit) {
//group by printerName and find counts greater than limit
...
}
}
How do I go about doing this?
Seems you are trying to populate the View with filtered data as per your request parameter in controller action.
You could follow the below steps to achieve what you are trying to:
Your imaginary Data Model
public class PrinterJob
{
[Key]
public int PrinterId { get; set; }
public string PrinterName { get; set; }
public int PrintedBy { get; set; }
public int TotalPrint { get; set; }
}
Sample Data In Database:
Controller Action:
public ActionResult <PrinterJob> JobsGreaterThan(int limit) {
var printCountByGroup =
(from objPrint in _context.PrinterJobs group objPrint by new {
objPrint.PrinterName, objPrint.PrintedBy, objPrint.TotalPrint
}
into grp where grp.Sum(p => p.TotalPrint) > limit
select new {
PrinterName = grp.Key.PrinterName, PrintedBy = grp.Key.PrintedBy,
TotalPrint = grp.Key.TotalPrint
});
return View(printCountByGroup);
}
Output After Applying Filter:
Note: Here I am trying to filter printer information which printed more then 30 printing jobs.
Hope it would help you to achieve your goal. If you still have any problem feel free to let me know.
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'm developing a project using Silverlight 4 and Entity Framework 4 and I'm trying to auto-load the details (with conditions) associated with an entity when the client loads the EntityQuery.
So far, I've been able to put in place a solution, using the Include attribute, that returns all the details associated with the master entity. What I'm missing here is to be able to filter out the details based on some criteria.
As an example, here's what my entities look like:
Entity Movie
Id (int)
[Include]
MovieLocalizedInformations (EntityCollection<MovieLocalizedInformation>)
Entity MovieLocalizedInformation
Id (int)
Movie_Id (int)
LanguageCode (eg.: en)
Title
On my DomainService object, I expose the following method:
public IQueryable<Movie> GetMovies( string languageCode )
{
return this.ObjectContext.Movies.Include( "MovieLocalizedInformations" );
}
This works fine. But when I try to add where clause to filter out the localized information based on the language code, only the movies get loaded on the client.
Is there a way to achieve the filtering in one query?
Note: I'm also using the DomainDataSource with paging on the client so the solution needs to work with that.
Any help would be greatly appreciated!
Thanks,
Jacques.
Not sure about Enitity Framework but with a LinqToSqlDomainService you use the LoadWith loadOption
to include the details entities and then use the AssociateWith LoadOption to filter the detail e.g
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Movies>(i => i.MovieLocalizedInformations);
options.AssociateWith<Movies>(i => i.MovieLocalizedInformations.Where(d=> myListOfIds.Contains(d.LocationId)));
Ok,
For efficiency reason, I decided to go with custom DTO object that fetches the localized information and flatten the result.
But, the same problem occurred when my custom DTO needed to reference another custom localized DTO.
Here is how I came to do the same as the .Include( "PropertyName" ) that the ObjectSet offers:
Entity LocalizedMovieCollection
public class LocalizedMovieCollection
{
[Key]
public int Id { get; set; }
public string Name { get; set; } (the result of a sub query based on the language)
[Include]
[Association( "LocalizedMovieCollection_LocalizedMovies", "Id", "MovieCollection_Id" )]
public IEnumerable<LocalizedMovie> Movies { get; set; }
}
Entity LocalizedMovie
public class LocalizedMovie
{
[Key]
public int Id { get; set; }
public string Name { get; set; } (the result of a sub query based on the language)
public int MovieCollection_Id { get; set; }
[Include]
[Association( "LocalizedMovie_LocalizedMovieCollection", "MovieCollection_Id", "Id", IsForeignKey = true]
public LocalizedMovieCollection MovieCollection { get; set; }
}
Then, I've declared two methods: One that returns an IQueryable of LocalizedMovieCollection and the other, an IQueryable of LocalizedMovie. (Note: There must be at least one method that returns each type of entity for the entity to get auto-generated on the Silverlight client)
My goal is to automatically load the MovieCollection associated with a Movie so the method definition to get the movies is as follow:
public IQueryable<LocalizedMovie> GetMovies( string languageCode )
{
return from movie in this.ObjectContext.Movies
join movieLocalizedInfo in this.ObjectContext.MovieLocalizedInformations
on movie equals movieLocalizedInfo.Movie
join movieCollection in this.ObjectContext.MovieCollections
on movie.MovieCollection equals movieCollection
join movieCollectionLocalizedInfo in this.ObjectContext.MovieCollectionLocalizedInformations
on movieCollection equals movieCollectionLocalizedInfo.MovieCollection
where movieLocalizedInfo.LanguageCode == languageCode && movieCollectionLocalizedInfo.LanguageCode == languageCode
select new LocalizedMovie()
{
Id = movie.Id,
Name = movieLocalizedInfo.Name
MovieCollection_Id = movieCollection.Id,
MovieCollection = new LocalizedMovieCollection(){ Id = movieCollection.Id, Name = movieCollectionLocalizedInfo.Name }
}
}
When the Silverlight client loads the query, all the LocalizedMovies and their associated LocalizedMovieCollections will be loaded into the context.
I'm trying to save a mapped entity using NHibernate but my insert to the database fails because the underlying table has a column that does not allow nulls and IS NOT mapped in my domain object. The reason it isn't mapped is because the column in question supports a legacy application and has no relevance to my application - so I'd like to not pollute my entity with the legacy property.
I know I could use a private field inside my class - but this still feels nasty to me. I've read that I can use an NHibernate interceptor and override the OnSave() method to add in the new column right before my entity is saved. This is proving difficult since I can't work out how to add an instance of Nhibernate.type.IType to the types parameter of my interceptor's OnSave.
My Entity roughly looks like this:
public class Client
{
public virtual int Id { get; set; }
public virtual int ParentId { get; set; }
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual string Url { get; set; }
}
And my interceptor
public class ClientInterceptor : EmptyInterceptor
{
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
{
if (entity is Client)
{
/*
manually add the COM_HOLD column to the Client entity
*/
List<string> pn_list = propertyNames.ToList();
pn_list.Add("COM_HOLD");
propertyNames = pn_list.ToArray();
List<Object> _state = state.ToList();
_state.Add(false);
state = _state.ToArray();
//somehow add an IType to types param ??
}
return base.OnSave(entity, id, state, propertyNames, types);
}
}
Does anyone have any ideas on how to do this properly?
I can't say for sure since I've never actually done this (like Stefan, I also prefer to just add a private property), but can you just add a NHibernate.Type.BooleanType to the types array?
List<IType> typeList = types.ToList();
typeList.Add(new BooleanType());
types = typesList.ToArray();
EDIT
Yes, it looks like you are right; the types have an internal constructor. I did some digging and found TypeFactory:
Applications should use static
methods and constants on
NHibernate.NHibernateUtil if the
default IType is good enough. For example, the TypeFactory should only
be used when the String needs to have a length of 300 instead of 255. At this point
NHibernate.String does not get you thecorrect IType. Instead use TypeFactory.GetString(300) and keep a
local variable that holds a reference to the IType.
So it looks like what you want is NHibernateUtil:
Provides access to the full range of
NHibernate built-in types. IType
instances may be used to bind values
to query parameters. Also a factory
for new Blobs and Clobs.
typeList.Add(NHibernateUtil.Boolean);
Personally I wouldn't do it so complicated. I would add the private property and assign it a default value - finished. You could also consider a default value in the database, then you don't need to do anything else.
private virtual bool COM_HOLD
{
get { return false; }
set { /* make NH happy */ }
}
Before writing a interceptor for that I would consider to write a database trigger. Because with the Interceptor you are "polluting" your data access layer. It could make it unstable and you could have strange problems.