Maybe it's simple but I'm stuck with it and I didn't find any answer on how it could be done. I have a parent entity User with a collection of child entities Operations. These two entities are just for UI so they are a kinf of views. Here is the pseudo code
public class User
{
public int Id {get; set;}
public IEnumerable<Operation> Operations {get; set;}
public int TotalSuccessfulAccesses {get; set;} // not mapped to the database
public int TotalFailedAccesses {get; set;} // not mapped to the database
}
public class Operation
{
public int Id {get; set; }
public int UserId {get; set; } // FK
public int NbSuccessfulAccesses {get; set; }
public int NbFailedAccesses {get; set; }
}
What I would like to do it's to get the User with TotalSuccesfulAccesses and TotalFailedAccesses initialized from the child collection in one round trip to the database.
For each user we should calculate Sum(Operation.NbSuccessfulAccesses) and Sum(Operation.NbFailedAccesse) and make a projection respectively to the User.TotalSuccesfulAccesses and User.TotalFailedAccesses.
I tried to play with multicriteria and several queries but I'm not satisfied with it. I would like to know if maybe there is a simple way to do it with projection or something other. Or maybe I missed something.
What would you recommend ?
Thanks in advance for you help.
I was able to get rid of the magic alias strings in the following way:
UserViewModel userView = null;
Add(Projections.Sum<User>(x => operations.NbSuccessfulAccesses).WithAlias(() => userView.TotalSuccessfulAccesses))
You probably need to separate your view models and your domain entities. I assume in your domain you have got a User class having a list of Operation and these entities are mapped accordingly.
You could then create a view model:
public class UserViewModel
{
public int UserId { get; set; }
public int TotalSuccessfulAccesses { get; set; }
public int TotalFailedAccesses {get; set;}
}
Using ICriteria you can create the following query:
var criteria = Session.CreateCriteria(typeof(User));
criteria.CreateAlias("Operations", "operations", JoinType.LeftOuterJoin);
var projList = Projections.ProjectionList();
projList.Add(Projections.GroupProperty("Id"));
projList.Add(Projections.Sum("operations.NbSuccessfulAccesses"), "TotalSuccessfulAccesses");
projList.Add(Projections.Sum("operations.NbFailedAccesses"), "TotalFailedAccesses");
criteria.SetProjection(projList);
criteria.SetResultTransformer(Transformers.AliasToBean<UserViewModel>());
var ret = criteria.List<UserViewModel>();
Create the view model according to your needs and add any properties in the projection list accordingly.
Hope that helps.
Thanks to Kay, I came up with the following translation :
Operation operations = null;
var q = GetSession().QueryOver<User>().Where(u => u.AccessKeyId == accessKeyId)
.Left.JoinQueryOver(x => x.Operations, () => operations)
.Select(Projections.ProjectionList()
.Add(Projections.Sum<User>(x => operations.NbSuccessfulAccesses), "TotalSuccessfulAccesses"))
.Add(Projections.Sum<User>(x => operations.NbFailedAccesses), "TotalFailedAccesses"))
.TransformUsing(Transformers.AliasToBean<UserViewModel>()).List< UserViewModel >();
However I would like to know if there is a mean to get rid of the magic string "TotalSuccessfulAccesses" and "TotalFailedAccesses".
if I use something like that
UserViewModel userView = null;
Add(Projections.Sum<User>(x => operations.NbSuccessfulAccesses), () => userView.TotalSuccessfulAccesses)
NHibernate yields an error :
Could not find a setter for property 'userView.TotalSuccessfulAccesses' in class 'Domain.Query.UserViewModel'
which is not true because there is a setter for TotalSuccessfulAccesses' property.
Any ideas ?
Thanks
Related
I am trying to join 2 tables, and project directly to DTOs (NHibernate 5).
I have following entities:
public class Person {
public Guid Id {get;set;}
public string Name {get;set;}
}
public class Car {
public Guid Id {get;set;}
public string Brand {get;set;}
public Person Owner {get;set;}
}
as we see, there is just reference from Car to Person (car knows its owner), and this is ok in my whole project.
However, there is one place, where I need to query all Persons, and make each person with collection of owned cars.
I created such DTOs:
public class PersonDto {
public Guid Id {get;set;}
public string Name {get;set;}
public IList<CarDto> {get;set;}
}
public class CarDto {
public Guid Id {get;set;}
public string Brand {get;set;}
}
it is kind of present the data linked together upside-down.
This task seems trivial using SQL or LINQ (GroupJoin) however I found it extremly hard to do in NH, since GroupJoin is not implemented in NH.
Can you please help me how to solve above issue?
Just add a Car collection to the existing Person entity and mark the collection inverse. Collections in NHibernate are lazy by default, so when you query Person, it won't read the cars from the DB, until you start iterating them. In other words adding the Car collection won't affect the way your code works.
When you want to efficiently query persons together with cars, force NH to do a join
.QueryOver<Person>.Fetch(person => person.Cars).Eager
I'd like to answer my own question. Thanks Rafal Rutkowski for his input.
To get data that has association in only 1 direction, we need to introduce a new data model, and then, manually convert result to our entities. I hope following example is best possible answer:
1) create such data model to store NH response:
private class PersonCarModelDto
{
public Guid PersonId { get; set; }
public string PersonName { get; set; }
public Guid CarId { get; set; }
public string CarBrand { get; set; }
}
2) create such a model to store hierarchical data as output:
private class PersonModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public IList<CarModel> Cars { get; set; }
}
private class CarModel
{
public Guid Id { get; set; }
public string Brand { get; set; }
}
3) now the NH query:
Person personAlias = null;
Car carAlias = null;
PersonCarModelDto resultAlias = null;
var response = GetCurrentSession().QueryOver<Car>(() => carAlias) // notice we are going from from 'downside' entity to 'up'
.Right.JoinAlias(c => c.Owner, () => personAlias) // notice Right join here, to have also Persons without any car
.SelectList(list => list
.Select(() => personAlias.Id).WithAlias(() => resultAlias.PersonId)
.Select(() => personAlias.Name).WithAlias(() => resultAlias.PersonName)
.Select(() => carAlias.Id).WithAlias(() => resultAlias.CarId)
.Select(() => carAlias.Brand).WithAlias(() => resultAlias.CarBrand)
.TransformUsing(Transformers.AliasToBean<PersonCarModelDto>())
.List<PersonCarModelDto>()
;
4) now we have flat data as a list of PersonCarModelDto, but we want to make output model:
var modelResult = response.GroupBy(p => p.PersonId)
.Select(x => new PersonModel
{
Id = x.Key,
Name = x.Select(y => y.PersonName).First(), // First() because each PersonName in that group is the same
Cars = x.Select(y => new CarModel
{
Id = y.CarId,
Name = y.CarBrand
})
.ToList()
})
.ToList()
;
Conclusions:
the problem is much easier to solve having bidirectional associations
if for some reason, you don't want bidirectional associations, use this technique
this approach is also useful if your entities has a lot of other properties, but for some reason you need just a small part of them
(optimiziation of data returned for DB)
How would a programmer add sample data to a Person class so it pre-populates the fields on the view?
Here is what I'm thinking but it doesn't work:
public class Person
{
private Boolean prepopulate = false;
public Person() { if (prepopulate) { Person(prepopulate); }}
public Person(Boolean prepopulate)
{
if (prepopulate)
{
this.prepopulate = prepopulate;
SampleData.Fill(ref this);
}
}
int Id {get; set;}
string Name {get; set;}
}
My create statement might look like this:
//
// GET: /Person/Create
public ActionResult Create()
{
Person person = new Person(prepopulate=true);
return View(person);
}
The fill would do something like this reference: link.
This is all you need to do to fix your problem, whenever a new entity is created it will automatically give it the properties inside the public Person() constructor. I would highly recommend reading up on the constructors and how they work so you understand it better and understand why it works.
public class Person
{
int Id {get; set;}
string Name {get; set;}
public Person()
{
Name = "Sample";
}
}
I have next entities:
class Topic
{
public virtual int Id {get; private set;}
public virtual ICollection<Vote> Votes {get; private set; }
}
class Vote
{
public virtual Topic Topic {get; private set;}
public virtual VoteType VotedTo {get; private set;} // enum VotedUp, VotedDown
}
I need to load from the db the next info - all topics (IDs, actually Names, but in this demo case it does not matter) and two more fields CountOfVotedUp, CountOfVotedDown (aggregates). So as I understand in SQL world we need joins, group by, case and count.
Is it posible to get this info with LINQ with less operations with db? I mean N+1, additional selects, connections etc.
All that I tried - is to use NH's LINQ, but it's Query aggregates only on Topic.Id and I could not count any of Votes collection.
Provided you have a summary class to store your result :
public class SummaryDTO
{
public int TopicId { get; set; }
public VoteType TypeOfVote { get; set; }
public int VoteCount { get; set; }
}
then
Vote voteAlias = null;
SummaryDTO result = null;
youNHSession.QueryOver<Topic>()
.JoinAlias(x=> x.Votes, ()=>voteAlias)
.SelectList(
list=>list
.SelectGroup(topic=>topic.Id).WithAlias(()=>result.TopicId)
.SelectGroup(()=>voteAlias.VotedTo).WithAlias(()=>result.TypeOfVote)
.SelectCount(()=>voteAlias.VotedTo).WithAlias(()=>result.VoteCount ))
.TransformUsing(Transformers.AliasToBean<SummaryDTO>())
.List<SummaryDTO>();
I guess that's not exactly what you are looking for, but hope this will set you on a good track.
I have an object from my domain model that has a child object. How can I use a criteria query to order based on a property of the child?
For example:
class FooType
{
public int Id { get; set; }
public string Name { get; set; }
public BarType Bar { get; set; }
}
class BarType
{
public int Id { get; set; }
public string Color { get; set; }
}
...
// WORKS GREAT
var orderedByName = _session.CreateCriteria<FooType>().AddOrder(Order.Asc("Name")).List();
// THROWS "could not resolve property: Bar.Color of: FooType"
var orderedByColor = _session.CreateCriteria<FooType>().AddOrder(Order.Asc("Bar.Color")).List();
What do I need to do to enable this scenario? I'm using NHibernate 2.1. Thanks!
You need to either add an alias or create a nested criteria for your child. Not sure how to do this in NHibernate, in Hibernate it's done via createCriteria() and createAlias() methods.
You would then use the alias as prefix in order by.
Update Hibernate code sample:
Criteria criteria = session.createCriteria(FooType.class);
criteria.createAlias("bar", "b");
criteria.addOrder(Order.asc("b.color"));
I imagine in NHibernate it would be quite similar, though with property/entity names uppercased. Here's an example from NHibernate documentation.
I'm not sure if it's a correct behavior or something done wrong on my side:
I've got a very simple parent-child relationship
public class SubmittedBatch
{
public virtual Guid Id { get; set; }
public virtual IList<SubmittedBatchParameter> Parameters { get; private set; }
}
public class SubmittedBatchParameter
{
public virtual string Value { get; set; }
}
And with FluentNH it is configured like:
mapping.HasMany<SubmittedBatchParameter>(sb => sb.Parameters).Cascade.All();
I do something easy like adding a new element to the Parameters collection and then call SaveOrUpdate on the parent element.
Looking at the trace of the SQL Statements I get an insert:
INSERT INTO [SubmittedBatchParameter]
(Value)
VALUES ('Disabled' /* #p0 */)
select SCOPE_IDENTITY()
and then an update:
UPDATE [SubmittedBatchParameter]
SET SubmittedBatch_id = '209971b7-c311-46bd-b989-9cf80113654c' /* #p0_0 */
WHERE Id = 39 /* #p1_0 */
Why isn't NH just doing the Insert with also the Guid specified?
Is this correct, or am I doing something wrong?
Thank you
Simone
You have not mapped the reverse parent relationship explicitly. Therefore the only way that NHibernate knows to save the SubmittedBatch_id column value is when you save the parent object. It can't save both objects at once, so what it does is to save the child, then when it saves the parent, save the relationship.
In fact, even if you were to map both directions of the relationship, you would still have to specify which is the "master" by marking the other as an "inverse" relationship. The field is then updated by saving the master side.
So, if you were to map a SubmittedBatch property in the SubmittedBatchParameter class, map this as the "master" (i.e. set the collection mapping as the inverse using .Inverse in Fluent), and then set that when you add the parameter to the batch, then you would see just the insert that you expect.
Here's what I mean:
public class SubmittedBatch
{
public virtual Guid Id { get; set; }
public virtual IList<SubmittedBatchParameter> Parameters { get; private set; }
}
public class SubmittedBatchParameter
{
public virtual SubmittedBatch SubmittedBatch { get; set; }
public virtual string Value { get; set; }
}
Then in child mapping:
HasMany<SubmittedBatchParameter>(sb => sb.Parameters).Inverse();
and in parent mapping:
References(sbp => sbp.SubmittedBatch);