NHibernate criteria query question - nhibernate

I have 3 related objects (Entry, GamePlay, Prize) and I'm trying to find the best way to query them for what I need using NHibernate. When a request comes in, I need to query the Entries table for a matching entry and, if found, get a) the latest game play along with the first game play that has a prize attached. Prize is a child of GamePlay and each Entry object has a GamePlays property (IList).
Currently, I'm working on a method that pulls the matching Entry and eagerly loads all game plays and associated prizes, but it seems wasteful to load all game plays just to find the latest one and any that contain a prize.
Right now, my query looks like this:
var entry = session.CreateCriteria<Entry>()
.Add(Restrictions.Eq("Phone", phone))
.AddOrder(Order.Desc("Created"))
.SetFetchMode("GamePlays", FetchMode.Join)
.SetMaxResults(1).UniqueResult<Entry>();
Two problems with this:
It loads all game plays up front. With 365 days of data, this could easily balloon to 300k of data per query.
It doesn't eagerly load the Prize child property for each game. Therefore, my code that loops through the GamePlays list looking for a non-null Prize must make a call to load each Prize property I check.
I'm not an nhibernate expert, but I know there has to be a better way to do this. Ideally, I'd like to do the following (pseudocode):
entry = findEntry(phoneNumber)
lastPlay = getLatestGamePlay(Entry)
firstWinningPlay = getFirstWinningGamePlay(Entry)
The end result of course is that I have the entry details, the latest game play, and the first winning game play. The catch is that I want to do this in as few database calls as possible, otherwise I'd just execute 3 separate queries.
The object definitions look like:
public class Entry
{
public Guid Id {get;set;}
public string Phone {get;set;}
public IList<GamePlay> GamePlays {get;set;}
// ... other properties
}
public class GamePlay
{
public Guid Id {get;set;}
public Entry Entry {get;set;}
public Prize Prize {get;set;}
// ... other properties
}
public class Prize
{
public Guid Id {get;set;}
// ... other properties
}
The proper NHibernate mappings are in place, so I just need help figuring out how to set up the criteria query (not looking for HQL, don't use it).

since you are doing this in each request maybe it should be better to set up two formula-properties in your entity.
The first one should fetch the latest Gameplay-Id and the other the first Gameplay-Id with a not Null property
this could be as such in the xml mapping file of Entry
<property name="LatestGameplay" formula="select top(1)gp.Id from Gameplay gp where gp.FK_EntryId = PK_EntryId order by gp.InsertDate desc" />
this leaves you with the Gameplay Id's on the Entry entity and after you fetch it it would require another round trip to the DB to GetById-fetch the gameplay's
Alternatively you could work-around using filters.
Set the collection back to "lazy"
and create these nice filters
Gameplay latest = NHibernateSession.CreateFilter(entry.GamePlays , "order by InsertDate desc").SetMaxResults(1).SetFirstResult(1).UniqueResult<Gameplay>();
Gameplay winner = NHibernateSession.CreateFilter(entry.GamePlays , "where FK_PrizeId is not null order by InsertDate asc ").SetMaxResults(1).SetFirstResult(1).UniqueResult<Gameplay>();
And IFilters can be used in a multiquery as so have 2 db hits: one for the original Entry and one for the multiquery.
Last but not least, you could define 2 bags in the Entry entity, one IList<GamePlay> Latest and one IList<Gameplay> Winner which in the Entry mapping file would be filtered with the appropriate query (although i don't remember now if you can define TOP clauses in the filters) and set those as non-lazy. Then with a single round-trip you can have all the data you want with the following (ugly) syntax
Entry entry = findEntry(phoneNumber);
Gameplay winner = entry.Winner[0]; //check this if null first
Gameplay Latest = entry.Latest[0]; //ditto
note that of all the solutions the 3rd is the one that provides a mechanism to generate additional queries, as the bag can be used in a Criteria/HQL query

Related

How to use LINQs Include in SQL

In LINQ I have written a simple query where I am searching for an animal using the ID property. However, I am also including the Farm the animal belongs using the Include property.
I want to write the same LINQ query in SQL where I can include Farm. How can I include Farm using SQL. I have an incomplete SQL syntax below. Can anyone help me out.
LINQ
await _dbContext.Animals.Where(x => x.id == 1)
.Include(x => x.Farm)
.ToListAsync();
SQL
select * from Animals where id = 1;
Apparently your database has a table with Animals and a table with Farms. There seems to be a one-to-many relation between Animals and Farms: on every Farm live zero or more Animals; every Animal lives on exactly one Farm, namely the Farm that the foreign key refers to.
I think you will have classes similar to the following:
class Farm
{
public int Id {get; set;}
public string Name {get; set;}
... // etc
// Every Farm has zero or more Animals (one-to-many)
public virtual ICollection<Animal> {get; set;}
}
class Animal
{
public int Id {get; set;}
public string Name {get; set;}
... // etc
// Every Animal lives on exactly one Farm, using foreign key
public int FarmId {get; set;}
public virtual Farm Farm {get; set;}
}
I want to write the same LINQ query in SQL where I can include Farm.
A small trick: if you want to know the SQL code generated by Entity Framework, use property DbContext.Database.Log.
using (var dbContext = new DbContext())
{
// Log generated SQL to debug window:
dbContext.Database.Log = System.Diagnostics.Debug.Write;
// execute your LINQ:
var fetchedAnimals = _dbContext.Animals.Where(x => x.id == 1)
.Include(x => x.Farm)
.ToList();
}
Write your own SQL
You'll have to join Animals with Farms, and keep only the Animal with ID = 1:
See SQL Join
// Select only the properties of Animals and Farms that you actually plan to use
SELECT Animals.Id, Animals.Name, ...,
Farms.Id, Farms.Name, ...
FROM Animals INNER JOIN Farms
ON Animals.FarmId = Farm.Id
WHERE Animals.Id = 1
You should not use "" to fetch everything. If Farm [10] Has 5000 Chickens, then every Chicken will have a foreign key with a value 10. If you use "" you will transfer this value 10 more than 5000 times, while you already know the value of the foreign key.
There's room for improvement
When using entity framework to fetch data, always use Select, and select only the properties that you plan to use, even if you Select all properties. Only omit Select and / or use Include if you plan to change / update the fetched data.
The reason is, that fetching data without using Select is not very efficient.
If you fetch data without using Select, entity framework will put the fetched item in the DbContext.ChangeTracker, together with a copy of the fetched item. You get a reference to the copy. Whenever you change properties of the fetched item, you change the copy in the ChangeTracker. When you call DbContext.SaveChanges, the original is compared with the copy, property per property to see which properties are changed, and thus need to be updated in the database.
So if you don't plan to change the fetched data, it would be a waste of processing power to put this data AND a copy in the ChangeTracker. Hence: always use Select, unless you plan to update the fetched data.

SQL Join tables on different column types

I have two tables:
dbo.Dashboards
Id (int PK) Title(nvarchar) WidgetIds(nvarchar)
1 Test [1,2]
dbo.Widgets
Id (int PK) Details(nvarchar)
1 {'text': 'some data'}
2 {'text': 'test'}
Expected output:
Dashboard.Id Dashboard.Title Widget.Id Widget.Details
1 Test 1 {'text': 'some data'}
1 Test 2 {'text': 'test'}
I would like to get dashboards with assigned widgets by using Entity Framework.
My first solution is to get dbo.Dashboards and then dbo.Widgets. After that I can merge it in a backend, but it is not the best practice.
Is there any option to get Dashboards with assigned Widget list?
Function Include() is not working because there isn't FK relationship between tables.
It seems to me that you have a many-to-many relationship between Dashboards and Widgets: Every Dashboard has zero or more Widgets and every Widget is used by zero or more Dashboards.
In a proper database you would have a separate junction table. Apparently you chose not to use this pattern, but create a string that contains a textual representation of the widgets that a 'Dashboard` has.
If you plan to create a serious application I strongly advise you to
use the standard pattern in many-to-many relationships
If you don't, all your queries will be more difficult. Imagine the problems you'll experience if you want to delete a Widget. You'd have to check the textual representation of every Dashboard to check if the widget that you want to remove is used somewhere and change it.
If you want to configure your many-to-many relations ship according to the Entity Framework Code-First Conventions, you will have something like this:
class Dashboard
{
public int Id {get; set;}
public string Title {get; set;}
// every Dashboard has zero or more Widgets
public virtual ICollection<Widget> Widgets {get; set;}
... // other properties
}
class Widget
{
public int Id {get; set;}
// every Widget is used in zero or more Dashboards
public virtual ICollection<Dashboard> Dashboards{get; set;}
... // other widget properties
}
class MyDbContext : DbContext
{
public DbSet<Dashboard> Dashboards {get; set;}
public DbSet<Widget> Widgets {get; set;}
}
Because you stuck to the conventions, this is all that entity framework needs to know to understand that you want to configure a many-to-many relationship between Dashboards and Widgets. Entity Framework will create the junction table for you. It will automatically update this table whenever you add a Widget to a Dashboard. It will also create the proper joins whenever you want to fetch Dashboards with their Widgets, or Widgets with the Dasheboards that use them.
Your query will be fairly simple:
var DashBoardsWithTheirWidgets = myDbcontext.Dashboards
// I only want to see the super dashboards
.Where(dashboard => dashboard.Type = DashboardType.Super)
.Select(dashboard => new
{
// Select only the properties you plan to use:
Id = dashboard.Id,
Title = dashboard.Title,
// select only the Widgets you plan to use:
Widgets = dashboard.Widgets
.Where(widget => widget.Price > 100.00)
.Select(widget => new
{
// again select only the properties you plan to use
Name = widget.Name,
Price = widget.Price,
})
.ToList();
});
See how easy it is if you stick to the conventions?
If you really want your obscure method of using foreign keys, you need a function to remove the square brackets and the commas from the widgetIds, split the string into sub-strings, Parse them to numbers, and do a join.
But before you plan to continue on this path, experiment on how to add a Widget and a Dashboard. How to add a Widget to a Dashboard, how to remove a Widget. I think the time needed to reform your database into proper format is much less than the time you'll need to implement those functions
Solution 1:
You need to restructure the dbo.dashboards table. Change the column layout of dbo.dashboards to
Auto_Generated_ID, Unique_Identifier(PK), Title, WidgetIds
I know the above column restructuring is done in a bad way. But still this will work in your case.
After redesigning it you can use join between dbo.dashboards and dbo.widgets to retrieve it in an efficient way.
Solution 2:
The below-normalized tables will work in your case
dbo.dashboard
id, title (columns)
dbo.dashboard_widget
id, dashboard_id, widget_id (columns)
dbo.widgets
id, details (columns)
Query:
select d.id, d.title, dw.widget_ids, w. details from dbo.dashboard d INNER JOIN dbo.dashboard_widget dw ON d.id = dw.dashboard_id INNER JOIN dbo.widgets w ON dw.widget_id = w.id where d.id = << id number >>

Need a concept on fetching data with HQL while three or more tables are in use

A small briefing on what I am trying to do.
I have three tables Content(contentId, body, timeofcreation), ContentAttachmentMap(contentId, attachmentId) and Attachment(attachmentId, resourceLocation).
The reason I adopted to create the mapping table because in future application the attachment can also be shared with different content.
Now I am using HQL to get data. My objectives is as follows:
Get All contents with/without Attachments
I have seen some examples in the internet like you can create an objective specific class (not POJO) and put the attribute name from the select statement within its constructor and the List of that Class object is returned.
For e.g. the HQL will be SELECT new com.mydomain.myclass(cont.id, cont.body) ..... and so on.
In my case I am looking for the following SELECT new com.mydomain.contentClass(cont.id, cont.body, List<Attachment>) FROM ...`. Yes, I want to have the resultList contain contentid, contentbody and List of its Attachments as a single result List item. If there are no attachments then it will return (cont.id, contentbody, null).
Is this possible? Also tell me how to write the SQL statements.
Thanks in advance.
I feel you are using Hibernate in a fundamentally wrong way. You should use Hibernate to view your domain entity, not to use it as exposing the underlying table.
You don't need to have that contentClass special value object for all these. Simply selecting the Content entity serves what you need.
I think it will be easier to have actual example.
In your application, you are not seeing it as "3 tables", you should see it as 2 entities, which is something look like:
#Entity
public class Content {
#Id
Long id;
#Column(...)
String content;
#ManyToMany
#JoinTable(name="ContentAttachmentMap")
List<Attachment> attachments;
}
#Entity
public class Attachment {
#Id
Long id;
#Column(...)
String resourceLocation
}
And, the result you are looking for is simply the result of HQL of something like
from Content where attachments IS EMPTY
I believe you can join fetch too in order to save DB access:
from Content c left join fetch c.attachments where c.attachments IS EMPTY

NHibernate - How do I update more than one boolean field at a time

I am using NHibernate in a web application I'm building. The user can subscript to zero or more mailing-lists (there are a total of 8). This is represented on the screen with a checkbox for each mailing-list.
I would like to use NHibernate to update these in one go. A very simple sql query would be:
update mail_subscriptions set subscribed = true where mailing_list_id in (21,14,15,19) and user_id = 'me'
What is the cleanest way to perform this update via NHibernate so that I can make a single round trip to the database?
Thanks in advance
JP
NHibernate might not be able to update the mail_subscriptions in the way you have shown above but it can do it in a single round trip to the DB using batched queries.
This example considers Subscriptions mapped as a HasMany using Component although roughly the same technique can be used if the mapping was just a plain HasMany. I am also assuming that each user already has rows in the mail_subscriptions table for each mailing list set to false for subscribed.
public class User{
public virtual string Id {get; set;}
public virtual IList<MailSubscription> Subscriptions {get; set;}
}
public class MailSubscription{
public virtual int ListId {get; set;}
public virtual bool Subscribed {get; set;}
}
public void UpdateSubscriptions(string userid, int[] mailingListIds){
var user = session.Get<User>(userid);
foreach(var sub in
user.Subscriptions.Where(x=> mailingListIds.Contains(x.ListId))){
sub.Subscribed=true;
}
session.Update(user);
}
Now when the unit of work completes you should see SQL like this produced sent as a single round trip to the DB.
update mail_subscriptions set subscribed=true where user_id='me' and listid=21
update mail_subscriptions set subscribed=true where user_id='me' and listid=14
update mail_subscriptions set subscribed=true where user_id='me' and listid=15
update mail_subscriptions set subscribed=true where user_id='me' and listid=19
I think the NHibernate feature you seek is known as Executable DML.
Ayende has a blog post giving an example at http://ayende.com/blog/4037/nhibernate-executable-dml .
Depending on your names of your entities and their properties, and assuming you have an ISession instance variable called session, you would need to execute an HQL query something like:
session.CreateQuery("update MailSubscriptions set Subscribed = true where MailingList.Id in (21,14,15,19) and User.Id = 'me'")
.ExecuteUpdate();
Now, having said that, I think in the use case you describe (updating a handful of entries within a collection on a single aggregate root), there is no need to use Executable DML. Mark Perry has the right idea - you should simply modify the booleans on the appropriate entities and flush the session in the usual way. If ADO.NET batching is configured appropriately, then the child entries will cause multiple update statements to be sent to the RDBMS in a single database call.

nhibernate - disable automatic\lazy loading of child records for one to many relationsihps

I would like to know if there is a way to disable automatic loading of child records in nHibernate ( for one:many relationships ).
We can easily switch off lazy loading on properties but what I want is to disable any kind of automatic loading ( lazy and non lazy both ). I only want to load data via query ( i.e. HQL or Criteria )
I would still like to define the relationship between parent child records in the mapping file to facilitate HQL and be able to join parent child entities, but I do not want the child records to be loaded as part of the parent record unless a query on the parent record
explicitly states that ( via eager fetch, etc ).
Example:
Fetching Department record from the database should not fetch all employee records from the database because it may never be needed.
One option here is to set the Employees collection on Department as lazy load. The problem with this approach is that once the object is given to the calling API it can 'touch' the lazy load property and that will fetch the entire list from the db.
I tried to use 'evict' - to disconnect the object but it does not seem to be working at all times and does not do a deep evict on the object.
Plus it abstracts the lazy loaded property type with a proxy class that plays havoc later in the code where we are trying to operate on the object via reflection and it encounters unexpended type on the object.
I am a beginner to nHibernate, any pointers or help would be of great help.
Given your request, you could simply not map from Department to Employees, nor have an Employees property on your department. This would mean you always have to make a database hit to find the employees of a database.
Aplogies if these code examples don't work out of the box, I'm not near a compiler at the moment
So, your department class might look like:
public class Department
{
public int Id { get; protected set; }
public string Name { get; set; }
/* Equality and GetHashCode here */
}
and your Employee would look like:
public class Employee
{
public int Id { get; protected set; }
public Name Name { get; set; }
public Department Department { get; set; }
/* Equality and GetHashCode here */
}
Any time you wanted to find Employees for a department, you've have to call:
/*...*/
session.CreateCriteria(typeof(Employee))
.Add(Restrictions.Eq("Department", department)
.List<Employee>();
Simply because your spec says "Departments have many Employees", doesn't mean you have to map it as a bi-directional association. If you can keep your associated uni-directional, you can really get your data-access to fly too.
Google "Domain Driven Design" Aggregate, or see Page 125 of Eric Evan's book on Domain Driven Design for more information
You can have the lazy attribute on the collection. In your example, Department has n employees, if lazy is enabled, the employees will not be loaded by default when you load a department : http://www.nhforge.org/doc/nh/en/#collections-lazy
You can have queries that explicitly load department AND employees together. It's the "fetch" option : http://www.nhforge.org/doc/nh/en/#performance-fetching-lazy