JPQL Aggregation return - jpa

Lets say I have the following JPQL query
SELECT e.column1, e.column2, SUM(e.column3), SUM(e.column4) FROM Entity e GROUP BY e.column1, e.column2
Obviously I wont be returning an Entity object but something a bit more complex. How do I return this in the method?
public List<???> query1() {
Query q = entityManager.createQuery("...");
List<Something???> list = q.getResultList();
return list;
}

Such a query returns a List<Object[]>, where each element is thus an array of Objects. The first element of the array will have the type of Entity.column1, the second one will have the type of Entity.column2, and the last 2 ones will be (with Hibernate at least) of type Long (check with EclipseLink).
It's up to you to transform the List<Object[]> in a List<Foo>, by simply looping over the list of objects and transforming each one into a Foo. You may also use the constructor notation directly in the query (provided Foo has such a constructor), but I personally dislike it, because it isn't refactorable:
select new com.baz.bar.Foo(e.column1, e.column2, SUM(e.column3), SUM(e.column4)) from ...

Related

Selecting Subcollections in Union-Query

I'm trying to select objects from the database with Entity Framework into an anonymous type. When using Union and Selecting a Subcollection, I get an exception:
System.ArgumentException: The 'Distinct' operation cannot be applied to the collection ResultType of the specified argument.
My model contains several types derived from BaseType. This base type has a reference to RefType which contains a collection of ItemType. The types derived from BaseType are stored in separate tables, thus the Union.
The query looks like this:
var q1 = ctx.Set<Type1>().Select(x => new { x.Id, x.Ref.Items });
var q2 = ctx.Set<Type2>().Select(x => new { x.Id, x.Ref.Items });
q1.Union(q2).ToList();
But to reproduce the error, you can even union queries of the same type, as long as you select a collection.
I would do the select after the union, but to union Type1, Type2, etc. I must cast them to BaseType, which is not allowed in LINQ-to-SQL.
Any way to do this in the same query?
The exception emerges from Entity Framework's query generation pipeline when the ExpressionConverter tries to translate the expression q1.Union(q2) into SQL.
In a valid query you'll see that EF adds a DISTINCT clause to the SQL query. A type with collection properties (x.Ref.Items) doesn't pass as a valid argument for a Distinct operation and EF throws the exception you see.
Unfortunately, using Concat instead of Union is not a valid work-around. EF will also throw an exception:
The nested query is not supported. Operation1='UnionAll' Operation2='MultiStreamNest'
Which means that it's simply not supported to concat nested queries containing types with collection properties.
So you have to do the Union in memory:
var result = q1.AsEnumerable() // Continues the query in memory
.Union(q2).ToList();
C# doesn't have any problem with equating anonymous types containing collections: it simply considers any collection member as unequal to another collections member. This does mean that the query can produce a collection containing non-unique results (same Id, same Items) which may not be expected when relying on Union's implicit Distinct.
I am not sure why, for some reason distinct is failing, maybe because it is anonymous type, and it is still IQuerable, I would suggest firing the query something like this
var q1 = ctx.Set<Type1>().Select(x => new { x.Id, x.Ref.Items }).ToList<object>();
var q2 = ctx.Set<Type2>().Select(x => new { x.Id, x.Ref.Items }).ToList<object>();
q1.Union(q2).ToList();
Note that in this case, Distinct will check for all properties equality, meaning if 2 objects have the same id but different items, both will be there.
if you don't care about distinct values, you can also use concat
if you care about distinct and first option didn't work, you can use group by and implement your own distinct,
something like this
var q1 = ctx.Set<Type1>().Select(x => new { Id = x.Id, Items =x.Ref.Items });
var q2 = ctx.Set<Type2>().Select(x => new { Id = x.Id, Items = x.Ref.Items });
//this will group by id, and select the first object items
var qFinal = q1.concat(q2).GroupBy(e => e.id)
.select(e => new {e.key, e.First().Items})
.ToList();
maybe you don't want First(), you can use whatever you want

Foreign Key Object Missing in LINQ GroupBy?

I am using GroupBy in my LINQ queries. My query is working fine, except my foreign key objects are missing. Then, I tried to add Include in my query. Following is my code:
public ActionResult GetEmployees()
{
var Emloyees = db.Employees.AsQueryable();
Emloyees.GroupBy(e=> new {e.JobTitleId, e.GenderId})
.Select(tr => new MyObject
SalaryTotal = tr.Sum(r=>r.Salary)
}).Include(tr=>tr.JobTitle).Include(tr=>tr.Gender).ToList();
}
I am getting this exception:
The result type of the query is neither an EntityType nor a CollectionType with an entity element type. An Include path can only be specified for a query with one of these result types.
I tried to add it before GroupBy and directly in db.Employees.AsQueryable(), but nothing worked. What am I doing wrong?
The problem is that you are doing a projection with the .Select(...), after which your includes cannot be resolved. The result of that query will be a list MyObject, which does not work for includes that are actually on Employee.
So Try it like this:
Emloyees
.GroupBy(e=> new {e.JobTitleId, e.GenderId})
.Select(tr => new MyObject {
SalaryTotal = tr.Sum(r=>r.Salary),
JobTitle = tr.FirstOrDefault().JobTitle,
Gender = tr.FirstOrDefault().Gender
}).ToList();
You will have to extend MyObject with 2 additional properties, but the result of your query will be what you want (I assume).

TypedQuery<x> returns vector of Object[] instead of list of x-type object

I have a method:
public List<Timetable> getTimetableTableForRegion(String id) {
List<Timetable> timetables;
TypedQuery<Timetable> query = em_read.createQuery("SELECT ..stuff.. where R.id = :id", Timetable.class).setParameter("id", Long.parseLong(id));
timetables = query.getResultList();
return timetables;
}
which returns this:
so, what am I missing in order to return a list of Timetable's?
ok, so, ..stuff.. part of my JPQL contained an inner join to other table. Even through in SELECT there were selected fields just from one table, which was used as type - Timetable, Eclipslink was unable to determine if this fields are part of that entity and instead of returning list of defined entity returned list of Object[].
So in conclusion: Use #OneToMany/#ManyToOne mappings (or flat table design) and query just for ONE table in your JPQL to be able to typize returned entities.
Not sure it might be something is looking for, but I had similar problem and converted Vector to ArrayList like this:
final ArrayList<YourClazz> results = new ArrayList<YourClazz>();;
for ( YourClazzkey : (Vector<YourClazz>) query.getResultList() )
{
results.add(key);
}
i have faced the same problem. and my entity has no one to one or one to many relationship. then also jpql was giving me queryresult as vector of objects. i changed my solution to query to criteria builder. and that worked for me.
code snippet is as below:
CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Timetable> criteria = builder.createQuery(Timetable.class);
Root<Enumeration> root = criteria.from(Timetable.class);
criteria.where(builder.equal(root.get("id"), id));
List<Timetable> topics = this.entityManager.createQuery(criteria) .getResultList();
return topics;

Entity Framework: selecting from multiple tables

I have a statement:
var items = from e in db.Elements
join a in db.LookUp
on e.ID equals a.ElementID
where e.Something == something
select new Element
{
ID = e.ID,
LookUpID = a.ID
// some other data get populated here as well
};
As you can see, all I need is a collection of Element objects with data from both tables - Elements and LookUp. This works fine. But then I need to know the number of elements selected:
int count = items.Count();
... this call throws System.NotSupportedException:
"The entity or complex type 'Database.Element' cannot be constructed in a LINQ to Entities query."
How am I supposed to select values from multiple tables into one object in Entity Framework? Thanks for any help!
You are not allowed to create an Entity class in your projection, you have to either project to a new class or an anonymous type
select new
{
ID = e.ID,
LookUpID = a.ID
// some other data get populated here as well
};
Your code doesn't work at all. The part you think worked has never been executed. The first time you executed it was when you called Count.
As exception says you cannot construct mapped entity in projection. Projection can be made only to anonymous or non mapped types. Also it is not clear why you even need this. If your class is correctly mapped you should simply call:
var items = from e in db.Elements
where e.Something == something
select e;
If LookupID is mapped property of your Element class it will be filled. If it is not mapped property you will not be able to load it with single query to Element.

EF4 inheritance and Stored procedures

I implemented inheritance with a discriminator field so all my records are in the same table. My basetype is Person (also the name of the table) and Driver and Passenger inherit from it. I receive instances of the correct type (Driver and Passenger) when I perform a query on the object context to Person. example:
var q = from d in ctx.Person
select d;
But I also create a function that calls a stored procedure and mapped the output of the function to the type Person. But now I get a list of Person and not Drivers or Passengers when I execute this method.
Anybody an idea how to solve this or is this a bug in EF4?
AFAIK, you can't use discriminator mapping (e.g TPH) when dealing with stored procedure mappings.
The stored procedure must be mapped to a complex type or custom entity (e.g POCO), the mapping cannot be conditional.
What you could do is map it to a regular POCO, but then project that result set into the relevant derived type (manual discrimination).
E.g:
public ICollection<Person> GetPeople()
{
var results = ExecuteFunction<Person>(); // result is ObjectResult<Person>
ICollection<Person> people = new List<Person>();
foreach (var result in results)
{
if (result.FieldWhichIsYourDiscriminator == discriminatorForDriver)
{
people.Add((Driver)result);
}
// other discriminators
}
}
If your always expecting a collection of one type (e.g only Drivers), then you wouldn't need the foreach loop, you could just add the range. The above is in case you are expecting a mixed bag of different people types.
Would be interested to see other answers, and if there is a better way though - but the above should work.