NHibernate - How to write this Query: Select parents & find child for each parent that matches a condition - nhibernate

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.

Related

JSON Arrays for multiple values in ASP.NET API

I've built a simple API for querying a local SQL database (for a database course). I'm using Postman to test my endpoints, but the results I'm getting aren't formatted the way I'd like. For example, in this query I ask my database for data about 1 person but it's returning all the unique sets.
What I would like is something like this (multivalues in a list):
My API is calling a stored procedure that's pretty lengthy, but this is the select statement at the end (a lengthy inner join):
SELECT DISTINCT specializationType, memberSince, teamName, E.meetingsAttended, lifetimeScore
FROM participants AS A
INNER JOIN
ONTEAM AS B
ON A.participantKey = B.participantKey
INNER JOIN
MEMBERSHIP AS C
ON A.participantKey = C.participantKey
INNER JOIN
SPECIALIZATIONPART AS D
ON A.participantKey = D.participantKey
INNER JOIN
MEETINGCOUNT as E
ON A.participantKey = E.participantKey;
The endpoint that's calling this is:
// GET api/InfoSecDB/adminSelectsParticipant
[HttpGet]
[Route("adminSelectsParticipant")]
public ActionResult<IEnumerable<string>> adminSelectsParticipant([FromBody] JObject data)
{
string pName = (string)data["pName"];
List<string> distinctSpecializations = new List<string>();
List<string> allTeams = new List<string>();
List<string> myP = new List<string>();
DatabaseModel dbm = new DatabaseModel();
DataTable dt = dbm.adminSelectsParticipant(pName);
return Ok(dt);
}
I'm not sure if this something that's supposed to be done by the stored procedure or the API endpoint. Any suggestions would be greatly appreciated!
Something you may want to do for this is set up a Model with each of the fields that you want to display. You can then loop through your DataTable and assign it to your model, then return your model in the way you want with your second image above.
Here is something that I found, it should be similar to what you want:
How do I bind a datatable to model and then to a dropdownlist in mvc?
Your model would look something like this:
public class Model
{
public List<string> SpecializationType { get; set; }
public DateTime MemberSince { get; set; }
public List<string> TeamName { get; set; }
public int MeetingsAttended { get; set; }
public int LifetimeScore { get; set; }
}
Then you could do var model = new List(); and assign your DataTable values to it.

NHibernate JoinAlias on collection multiple times

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)

NHibernate: Populate new entities relative properties with foreign key ID

I have structured my NHibernate models to hide the foreign key IDs as seems to be best practice, so instead of having both the relationship and the key modeled like this:
public class CompanyOffice
{
public virtual int Id { get; set; }
public virtual int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
I simply have:
public class CompanyOffice
{
public virtual int Id { get; set; }
public virtual Company Company { get; set; }
}
And let NHibernate take car of the rest. I like this, but I have a few tables with multiple foreign keys that need setting and when I create a new entity, in an MVC action, I'm having to do this:
public ActionResult CreateNewThing(int stuffId, int whatId, int blahId)
{
var stuff = _service.GetStuffById(stuffId);
var what = _service.GetWhatById(whatId);
var blah = _service.GetBlahById(blahId);
var thing = new Thing { Stuff = stuff, What = what, Blah = blah };
}
Which means three trips to the database to get the entities, when I don't really need them as I already have the IDs and could have just done:
var thing = new Thing { StuffId = stuffId, WhatId = whatId, BlahId = blahId };
Is there another way to achieve what I'm trying to do without hitting the database for the entities?
Should I just bite the bullet and map the foreign keys as well as the relationships?
Or am I being to concerned about the database trips and should I just crack on?
You are right to avoid mapping the FK Id, to avoid 3 addtional trips do this:-
var thing = new Thing {
Stuff = session.Load<Stuff>(20),
What = session.Load<What>(21),
Blah = session.Load<Blah>(2234),
}
session.Load is slightly different to session.Get
Also it is worth noting that Thing.Stuff.Id also does not hit the database.
This is a great post, I quote:-
In session.load(), Hibernate will not hit the database (no select
statement in output) to retrieve the Stock object, it will return a
Stock proxy object – a fake object with given identify value. In this
scenario, a proxy object is enough for to save a stock transaction
record.

How to auto-load details (with conditions) associated with an entity using Ria Services?

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.

NHibernate Projection Components

Hello guys im trying to hydrate a DTO using projections in NHibernate this is my code
IList<PatientListViewModel> list =
y.CreateCriteria<Patient>()
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Birthdate"), "Birthdate")
.Add(Projections.Property("Doctor.Id"), "DoctorId")
.Add(Projections.Property("Gender"), "Gender")
.Add(Projections.Property("Id"), "PatientId")
.Add(Projections.Property("Patient.Name.Fullname"), "Fullname")
)
.SetResultTransformer(Transformers.AliasToBean<PatientListViewModel>())
.List<PatientListViewModel>();
this code is throwing an exception? anyone know what is the problem?
here is the error message
Message: could not resolve property: Patient.Name.Fullname of: OneCare.Domain.Entities.Patient
You have to create a join to your Parent.Name property.
So try before setting the projections to create in alias to your Patient.Name property
e.q.
IList<PatientListViewModel> list =
y.CreateCriteria<Patient>()
.CreateAlias("Name", "name")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Birthdate"), "Birthdate")
.Add(Projections.Property("Doctor.Id"), "DoctorId")
.Add(Projections.Property("Gender"), "Gender")
.Add(Projections.Property("Id"), "PatientId")
.Add(Projections.Property("name.Fullname"), "Fullname")
)
Sorry I did not check this, as all depend on your entities classes. But the idea is that you have to create an alias.
If you can not fix the issue, please provide the your classes.
Updated!
I've created two entities, Patient and Doctor:
public class Patient : AdvanceEntity
{
public virtual DateTime BirthDate { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual int Gender { get; set; }
public virtual string Name { get; set; }
}
public class Doctor : AdvanceEntity
{
public virtual string Name { get; set; }
}
Next the repository contains only yours query translated to Criteria API
public IList<Patient> GetPatientsForDoctor(long doctorId)
{
return this.Session.CreateCriteria(typeof(Patient), "patient")
.CreateAlias("patient.Doctor", "doc")
.Add(Restrictions.Eq("doc.Id", doctorId))
.List<Patient>()
;
}
And here is the unittest and the result of the query
[Test]
public void CanGetPatients()
{
var repository = new PatientRepository();
repository.GetPatientsForDoctor(1L);
}
and result is:
NHibernate: SELECT this_.patientId as patientId70_1_, this_.birthDate as birthDate70_1_, this_.gender as gender70_1_,
this_.name as name70_1_, this_.deletedDate as deletedD5_70_1_, this_.doctorId as doctorId70_1_,
this_.deletedById as deletedB7_70_1_, doc1_.doctorId as doctorId71_0_, doc1_.name as name71_0_,
doc1_.deletedDate as deletedD3_71_0_, doc1_.deletedById as deletedB4_71_0_
FROM Patients this_
inner join Doctors doc1_ on this_.doctorId=doc1_.doctorId
WHERE doc1_.doctorId = #p0;#p0 = 1
As I said you need just to create an Alias and join tables between them.
But I think, that using HQL is more plausible in this case. Use criteria only you have dynamic queries. As you can see, the criteria one select all fields which could create a performance lack. Of couse you are working with simple things, but in real application be very carefully with generated queries.
Have a nice day!