How do I get NHibernate to do a join? - c#

I've used Fluent NHibernate to hook up a store and employee class where Stores can have many employees as follows:
public class Store
{
public virtual IList<Employee> Employees { get; set; }
//other store properties
}
public class Employee
{
public virtual Store Store { get; set; }
public virtual bool? SomeStatus1 { get; set; }
}
I'm needing to get all stores that have employees that do not have SomeStatus1 set to true.
My feable attempt here has failed:
Session.CreateCriteria(typeof(Store))
.Add(Restrictions.Not(Restrictions.Eq("Employees.SomeStatus1", true))
.List<Store>();
Any idea how I go about doing that?
The reason my attempt has failed is because the list Employees doesn't have a property of SomeStatus1...which is fairly obvious.
What I dont know, is how to get NHibernate to only get stores which have employees in the state I'm looking for...
I think what I'm wanting to ask NHibernate is to do a join...but I don't know how to ask it to do that...

you join by creating sub criteria
var criteria = Session.CreateCriteria(typeof(Store));
var join = criteria.CreateCriteria("Employees");
join.Add(Restrictions.Not(Restrictions.Eq("SomeStatus1", true));
return criteria.List<Store>();
Untested (obv) hope it works, but you get the idea. That's how I do it with N:1 but you have 1:N
EDIT: Ok, I did a bit of research after posting. It seems the code I did should work, but will cause loading of the employees collection. The same basic code is found on ayende's blog. There is a sample there which does the same thing without causing the collection to be reloaded. Hope that helps.

Try:
Session.CreateCriteria(typeof(Store))
.CreateAlias("Employees", "e")
.Add(Restrictions.Not(Restrictions.Eq("e.SomeStatus1", true))
.List<Store>();

I would suggest you use the Linq to NHibernate API instead of the Criteria API. With it, your query would be as follows:
var query = Session.Linq<Store>()
.Where(store => store.SomeStatus1 != true);
var result = query.ToList();
More help here.

Related

C# & EF Core 5.x AutoInclude() for many-to-many relation

Sorry for my English :)
When I am configuring a model in OnModelCreating(), I'm using AutoInclude() method:
modelBuilder.Entity(tableType).Navigation(tableType.NavigationPropertyName).AutoInclude();
And this is works fine for one-to-one or one-to-many relations.
But for many-to-many relation when I query the data, I get this exception:
Cycle detected while auto-including navigations: 'FirstTable.Second', 'SecondTable.First'. To fix this issue, either don't configure at least one navigation in the cycle as auto included in OnModelCreating or call 'IgnoreAutoInclude' method on the query.
Example
public class FirstTable
{
public int Id { get; set; }
public virtual List<SecondTable> Second { get; set; } = new();
}
public class SecondTable
{
public int Id { get; set; }
public virtual List<FirstTable> First { get; set; } = new();
}
//somewhere in the program
using var db = new MyContext();
var data = db.Set<FirstTable>(); // <-- Exception happens
Hence my question: does anyone know how to use AutoInclude() for such cases so that it would work correctly?
If I follow the advice from the exception, it turns out that for one of the tables the links will not be automatically pulled up, but I don't want this.
I searched for information on the Internet, but something did not find anything suitable for me. I would appreciate any help and answers.
Thanks.

EF Lambda include navigation properties

I have the following object, called Filter with the following properties:
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Type> Types{ get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Flow> Flows { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
When I select a list of Filters from the database, I have no idea how to include the collections (Types, Steps, Flows, Rooms). My code is as follows:
var filters = (
from filter in dbContext.DbSet<Filter>()
let rooms = (
from r in dbContext.DbSet<Room>()
select r
)
let eventTypes = (
from t in dbContext.DbSet<Type>()
select t
)
let processFlows = (
from f in dbContext.DbSet<Flow>()
select f
)
let processFlowSteps = (
from s in dbContext.DbSet<Step>()
select s
)
select filter
).ToList();
My collection of Filter is returned, but the collections inside are empty. Could you please tell me how can I achieve this?
Ps: I do not want to use Include because of performance issues, I don't like how Entity Framework generates the query and I would like to do it this way.
Your method works, you are just doing it slighly wrong.
To include a navigation property, all you have to do is a subselect (using linq), example:
var filters = (from filter in dbContext.DbSet<Filter>()
select new Filter
{
filter.Id,
filter.Name,
Rooms = (from r in dbContext.DbSet<Room>()
where r.FilterId == filter.Id
select r).ToList()
}).ToList();
Keep in mind that EF won't execute the query until you call a return method (ToList, Any, FirstOrDefault, etc). With this, instead of doing those ugly queries you want to avoid by not using Include(), it will simply fire two queries and properly assign the values in the object you want.
You need to use Include extension method:
var filters=dbContext.DbSet<Filter>()
.Include(f=>f.Types)
.Include(f=>f.Steps)
.Include(f=>f.Flows)
.Include(f=>f.Rooms)
.ToList()
Update
#MrSilent, Include extension method was made exactly for the purpose of loading related entities, I think the other option you have is executing a raw sql, but the way you are doing is not the way to go you have four roundtrips to your database and you need to use join instead in order to get the related entities, Include generates those joins for you and it's just one roundtrip.
This is, eg, another way I guess you could do it, but again, it is against the purpose of using EF, the idea of your model is also to represent the relationship between your tables, not just to represent them individually
var query= from f in context.DbSet<Filter>()
from s in f.Steps
from r in f.Rooms
from t in f.Types
from fl in f.Flows
select new {f, s, r, t, fl};
You can use lazy loading , how :
You first need to get the properties that are Include inclined to be virtual and then an empty constructor that is protected access type to do your job well.
public virtual ICollection<Type> Types{ get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Flow> Flows { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
And
//FOR EF !
protected Filter() { }
I think this solution will solve your problem.

Loading virtual properties

I am retrieving data from the database (with Entity Framework) using DTOs:
IQueryable<User> users = repository.ListFiltered<User>(n => n.Active);
var usersDTO = from user in users
select new UserAccountDTO{
UserId = user.Id,
UserName = user.Name,
InstitutionName = user.Institution.Name,
InstitutionCountry = user.Institution.Country.Name
};
I'm doing this because the user entity, and the institution entity, have a lot of data i don't need right now, so i don't want to retrieve it from the database.
But the problem is that i don't like the code, i would like to split the code and concatenate the selects, is there anyway to do this?
I'd like to arrive to something like:
users.LoadUserData().LoadInstitutionData();
What do you say? is it possible?
The problem is what LoadUserData() and LoadInstitutionData() should look like. Let's say that the end product of this method chain should be an IEnumerable<UserAccountDTO>. The last method in fluent syntax always determines the output. So LoadInstitutionData() should return the required IEnumerable. Clear.
But what about the input? I see two alternatives:
The input is IEnumerable<User> (or IQueryable<User>). OK, that would mean that LoadInstitutionData() can't do anything else than the code you already have. And that's not a solution, because you don't like the code. Moreover, the method requires that user.Institution and the Country must be loaded or lazy loadable, which is hard to express in any form of code contract.
The input is IEnumerable<UserAccountDTO> in which LoadUserData has set the direct user properties and that LoadInstitutionData should replenish with Institution data. Now the question is: how should LoadInstitutionData do that? Anyhow, what was done in one query now will take at least two queries, maybe even 1 + n.
More complex alternatives could consist of passing and composing expressions, but surely you'll be jumping from the frying pan into the fire, reinventing LINQ's extension methods, or AutoMapper, or...
Wait, AutoMapper.
Did you know that AutoMapper could do this for you in a way that may appeal to you?
All you need is a class
class UserAccountDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string InstitutionName { get; set; }
public string InstitutionCountryName { get; set; } // Notice the Name part!
};
A mapping
AutoMapper.Mapper.CreateMap<User, UserAccountDTO>();
A using:
using AutoMapper.QueryableExtensions;
And a concise syntax:
var usersDTO = users.Project().To<UserAccountDTO>();
(And AutoMapper from NuGet, of course).
Automapper will resolve a property like InstitutionCountryName as user.Institution.Country.Name and the result of the projection is one expression that is translated into one SQL statement with joins and all. That's code I do like.

Does AsQueryable() on ICollection really makes lazy execution?

I am using Entity Framework CodeFirst where I have used Parent Child relations using ICollection as
public class Person
{
public string UserName { get;set}
public ICollection<Blog> Blogs { get; set;}
}
public class Blog
{
public int id { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
Ok, so far everything is working ok, but my concern is, whenever I want to get the Blogs of a person, I get it as
var thePerson = _context.Persons.Where(x => x.UserName = 'xxx').SingleOrDefault();
var theBlogs = thePerson.Blogs.OrderBy(id).Take(5);
Now, I understand that, when the line is executed, all Blogs for that person is loaded into the memory and then sorting and selecting is done from memory. That is not ideal for a record of Person who has large number of blogs. I want to make the Blog Child as IQueryable so that the Sorting and Selecting is done in SQL database before pulling to Memory.
I know I could declare the Blogs as IQueryable in my context so that I could directly query as
var theBlogs = _context.Blogs.Where(.....)
but that is not feasible for me due to design choice, I want to avoid any circular reference as much as possible due to serialization problem. So, I did not make any reference of the parent entity in my child.
I found that, i can call AsQueryable() method on the blogs as
var theBlogs = thePerson.Blogs.AsQueryable().OrderBy(id).Take(5);
That looks like a magic for me and seems too good to be true. So my question. Does this AsQueryable really make the ICollection as IQueryable in reality and makes all Query process in SQL Server (Lazy loading) OR it is just a casting where Blogs are loaded into memory as like before, but change the interface from ICollection to IQueryable ?
So actually it appears that writing your navigation property as IQueryable<T> is not possible.
What you could do is adding a navigation property to Blog:
public class Blog
{
public int id { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public virtual Person Owner { get; set; }
}
From that, you can query as follows so it won't load everything into memory:
var thePerson = _context.Persons.Where(x => x.UserName = 'xxx').SingleOrDefault();
var results = _context.Blogs.Where(z => z.Person.Name = thePerson.Name).OrderBy(id).Take(5)
I suggest you to try LINQPad to see how LINQ is translated into SQL, and what is actually requested from the DB.
A better approach is described in Ladislav's answer. In your case:
var theBlogs = _context.Entry(thePerson)
.Collection(x => x.Blogs)
.Query()
.OrderBy(x => x.id)
.Take(5);

Partially Populate Child Collection with NHibernate

I've been struggling with this for a while, and can't seem to figure it out...
I've got a BlogPost class, which has a collection of Comments, and each of the comments has a DatePosted field.
What I need to do is query for a BlogPost and return it with a partially loaded Comments collection, say all comments posted on the 1 Aug 2009.
I've got this query:
BlogPost post = session.CreateCriteria<BlogPost>()
.Add(Restrictions.Eq("Id", 1))
.CreateAlias("Comments", "c")
.Add(Restrictions.Eq("c.DatePosted", new DateTime(2009, 8, 1)))
.UniqueResult<BlogPost>();
When I run this query and check out the generated sql, it first runs a query against the BlogPost table, joining to the Comment table with the correct date restriction in, then runs a second query just on the Comment table that returns everything.
Result is the Comments collection of the BlogPost class totally filled up!
What am I doing wrong?
I've got code samples if anyone needs more info...!
There is a result transformer for this, see the documentation.
Quote:
Note that the kittens collections held
by the Cat instances returned by the
previous two queries are not
pre-filtered by the criteria! If you
wish to retrieve just the kittens that
match the criteria, you must use
SetResultTransformer(CriteriaUtil.AliasToEntityMap).
IList cats =
sess.CreateCriteria(typeof(Cat))
.CreateCriteria("Kittens", "kt")
.Add( Expression.Eq("Name", "F%") )
.SetResultTransformer(CriteriaUtil.AliasToEntityMap)
.List();
You could also use filters that get activated using session.EnableFilter(name).
There is a similar question here.
You're not really doing anything wrong - hibernate just doesn't work that way.
If you navigate from the BlogPost to the Comments, Hibernate will populate the comments based on the association mapping that you've specified, not the query you used to retrieve the BlogPost. Presumably your mapping is just doing a join
on a key column. You can use a filter to get the effect you're looking for. But I think that will still fetch all the comments and then do a post-filter.
More simply, just query for what you want:
List<Comments> comments = session.CreateCriteria<BlogPost>()
.Add(Restrictions.Eq("Id", 1))
.CreateAlias("Comments", "c")
.Add(Restrictions.Eq("c.DatePosted", new DateTime(2009, 8, 1)))
.list();
This will in fact return only the comments from the specified date.
if it makes you feel better, you can then set them like this:
post.setComments(comments); //having already retreived the post elsewhere
I was also surprised by this behaviour when I first encountered it. It seems like a bug, but I've been told its by design.
thanks for the response, i guess i kinda understand why its by design, but i would have thought that there would be a built in method to enable this, your solution works, but feels like a bit of a hack!
my problem is that the child collection is HUGE if not filtered (the example i gave of posts and comments was to protect the names of the innocent!) and there is now way i can be pulling all the data back every time.
i've run Sql Profiler on this and its still pulling all the data back.
when i run the following code the first query does what you expect, just the one post comes back, but as soon as the second query is executed, two queries go to the database, the first to retrieve the filtered comments (bingo!), and then a second to populate the post.Comments property with all the comments, just what i'm trying to avoid!
var post = session.CreateCriteria<BlogPost>()
.Add(Restrictions.Eq("Id", 1))
.UniqueResult<BlogPost>();
var comments = session.CreateCriteria<Comment>()
.Add(Restrictions.Eq("BlogPostId", 1))
.Add(Restrictions.Eq("DatePosted", new DateTime(2009, 8, 1)))
.List<Comment>();
post.Comments = comments;
this is very strange, its not like i'm enumerating over the post.Comments list, so why is it populating it?! here are my classes and maps:
public class BlogPostMap : ClassMap<BlogPost>
{
public BlogPostMap()
{
Id(b => b.Id);
Map(b => b.Title);
Map(b => b.Body);
HasMany(b => b.Comments).KeyColumnNames.Add("BlogPostId");
}
}
public class CommentMap : ClassMap<Comment>
{
public CommentMap()
{
Id(c => c.Id);
Map(c => c.BlogPostId);
Map(c => c.Text);
Map(c => c.DatePosted);
}
}
public class BlogPost
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Body { get; set; }
public virtual IList<Comment> Comments { get; set; }
}
public class Comment
{
public virtual int Id { get; set; }
public virtual int BlogPostId { get; set; }
public virtual string Text { get; set; }
public virtual DateTime DatePosted { get; set; }
}
any ideas?
I agree it feels like a hack to manually populate the collection.
You can use a custom loader instead. Something like this:
<query name="loadComments">
<return alias="comments" class="Comment"/>
<load-collection alias="comments" role="Post.comments"/>
from Comments c where c.Id = ? and c.DatePosted = SYSDATE
</query>
Also, you can use sql-query if you want more control.
I've occasionally stooped to writing custom loaders when I couldn't get hibernate to generate the query I wanted. Anyway, don't know why I didn't think of that in the first place.
Make the Comments Collection lazy, so that hibernate doesn't fetch it when you're getting the BlogPost. Then use a filter on Comments collection.
comments = session.CreateFilter(blogPost.Comments, ... ).List();

Categories