I have used too much time to find a nice way to eager load child collections. so far this is what I got. It's working but I find it hard to believe that this is the best way to write this query
[Fact]
public void EagerLoadQueryOverWithFutureTest()
{
const long journalNr = 1470;
var query = _repo.QueryOver().Where(s => s.JournalNr == journalNr).Future<Sag>();
var sagsansoegning = _repo.QueryOver().Where(an => an.JournalNr == journalNr).Fetch(x => x.Sagsansoegning).Eager.Future<Sag>();
var noter = _repo.QueryOver().Where(n => n.JournalNr == journalNr).Fetch(x => x.Noter).Eager.Future<Sag>();
var filer = _repo.QueryOver().Where(f => f.JournalNr == journalNr).Fetch(x => x.Filer).Eager.Future<Sag>();
var emails = _repo.QueryOver().Where(e => e.JournalNr == journalNr).Fetch(x => x.Emails).Eager.Future<Sag>();
var journal = _repo.QueryOver().Where(j => j.JournalNr == journalNr).Fetch(x => x.Journal).Eager.Future<Sag>();
var sagsTilstand = _repo.QueryOver().Where(t => t.JournalNr == journalNr).Fetch(x => x.Tilstand).Eager.Future<Sag>();
var boligsocialEvalueringer = _repo.QueryOver().Where(b => b.JournalNr == journalNr).Fetch(x => x.BoligsocialEvaluering).Eager.Future<Sag>();
var result = query.SingleOrDefault();
result.JournalNr.Should().Be(journalNr);
result.Emails.Should().HaveCount(c => c > 20);
result.Filer.Should().HaveCount(c => c > 20);
}
I would like to append different point of view, not the answer how to use "future" to make only one round-trip to DB.
Not sure why you need to fetch all the properties. I guess it could be 1) to be used by your code, e.g. to render UI or 2) as the API which somehow serializes that and passes it to the client.
In the first scenario, I would suggest to use the session per request and use the lazy load. In the second, I would suggest to introduce one step: building the DTO before passing it to client. That could be done while session is still open, therefore use the lazy load.
Lazy load in both scenarios. So, in both cases I would suggest to use different approach then explicit fetching. It will use more or less the same amount of selects and round-trips to DB. But at the end it could be more effective. Because all that job would be left on the NHibernate
The thing is to change the eager-fetch (manual) loading with the batch-size (native) loading. See more here 19.1.5. Using batch fetching. In this case, NHibernate will process your query, and then will go again with few more queries to load the remaining data. But only if really needed
The advantage is, that you are not a prisoner of your queries. You can consume any property which could/should be available on the entity, until the session is open (I.e. during View rendering or serializing). You do not have to go and append another explicit query over join to get eager data.
So, while Future could be solution to the world were your connectivity to DB from APP is very slow... it is not the world were to use NHibernate (ORM). The advantage of the lazy mapping and ad hoc loading (with power of batching) is from maintenance perspective the right way... I'd say
Related
Before I go creating my own SQL scripts by hand for this, I have a scenario where I want to get the ids of a foreign key, but not the entirety of the foreign entities, using EF Core.
Right now, I'm getting the ids manually by looping through the related entities and extracting the ids one at a time, like so:
List<int> ClientIds = new List<int>();
for (var i = 0; i < Clients.length; i++){
ClientIds.add(Clients.ElementAt(i).Id);
}
To my understanding, this will either cause data returns much larger than needed (my entity + every related entity) or a completely separate query to be run for each related entity I access, which obviously I don't want to do if I can avoid it.
Is there a straightforward way to accomplish this in EF Core, or do I need to head over the SQL side and handle it myself?
Model:
public class UserViewModel {
public UserViewModel(UserModel userModel){
ClientIds = new List<int>();
for (var i = 0; i < UserModel.Clients.length; i++){
ClientIds.add(Clients.ElementAt(i).Id);
}
//...all the other class asignments
}
public IEnumerable<int> ClientIds {get;set;}
//...all the other irrelevant properties
}
Basically, I need my front-end to know which Client to ask for later.
It looks like you are trying to query this from within the parent entity. I.e.
public class Parent
{
public virtual ICollection<Client> Clients { get; set; }
public void SomeMethod()
{
// ...
List<int> ClientIds = new List<int>();
for (var i = 0; i < Clients.length; i++)
{
ClientIds.add(Clients.ElementAt(i).Id);
}
// ...
}
}
This is not ideal because unless your Clients were eager loaded when the Parent was loaded, this would trigger a lazy load to load all of the Clients data when all you want is the IDs. Still, it's not terrible as it would only result in one DB call to load the clients.
If they are already loaded, there is a more succinct way to get the IDs:
List<int> ClientIds = Clients.Select(x => x.Id).ToList();
Otherwise, if you have business logic involving the Parent and Clients where-by you want to be more selective about when and how the data is loaded, it is better to leave the entity definition to just represent the data state and basic rules/logic about the data, and move selective business logic outside of the entity into a business logic container that scopes the DbContext and queries against the entities to fetch what it needs.
For instance, if the calling code went and did this:
var parent = _context.Parents.Single(x => x.ParentId == parentId);
parent.SomeMethod(); // which resulted in checking the Client IDs...
The simplest way to avoid the extra DB call is to ensure the related entities are eager loaded.
var parent = _context.Parents
.Include(x => x.Clients)
.Single(x => x.ParentId == parentId);
parent.SomeMethod(); // which resulted in checking the Client IDs...
The problem with this approach is that it will still load all details about all of the Clients, and you end up in a situation where you end up defaulting to eager loading everything all of the time because the code might call something like that SomeMethod() which expects to find related entity details. This is the use-case for leveraging lazy loading, but that does have the performance overheads of the ad-hoc DB hits and ensuring that the entity's DbContext is always available to perform the read if necessary.
Instead, if you move the logic out of the entity and into the caller or another container that can take the relevant details, so that this caller projects down the data it will need from the entities in an efficient query:
var parentDetails = _context.Parents
.Where(x => x.ParentId == parentId)
.Select(x => new
{
x.ParentId,
// other details from parent or related entities...
ClientIds = x.Clients.Select(c => c.Id).ToList()
}).Single();
// Do logic that SomeMethod() would have done here, or pass these
// loaded details to a method / service to do the work rather than
// embedding it in the Entity.
This doesn't load a Parent entity, but rather executes a query to load just the details about the parent and related entities that we need. In this example it is projected into an anonymous type to hold the information we can later consume, but if you are querying the data to send to a view then you can project it directly into a view model or DTO class to serialize and send.
I'm trying to get to grips with QueryOver, and I was expecting this to return me a Summary item with its ReportRows collection eagerly loaded.
Update
The first block of code wasn't in my original question but it was the cause of my problem - thanks to dotjoe for the answer
// some code to create a Summary and a whole graph of child records
...
// then...
session.Save(summary);
session.Flush(); // write the changes
session.Evict(summary); // clear out session to check my query below is 'fresh'
// Originally-posted code
Summary summaryAlias = null;
Report reportAlias = null;
var summaryQuery =
session.QueryOver<Summary>(() => summaryAlias)
.Fetch(x => summaryAlias.Reports).Eager
.Left.JoinAlias(() => summaryAlias.Reports, () => reportAlias)
.Where(() => summaryAlias.Id == workItemId);
Summary summary = summaryQuery.SingleOrDefault<Summary>();
session.Close();
However when I hit a breakpoint after session.Close() has been called (to prevent any further lazy loading), I find that everything in my Summary class has been populated, not just the ReportRows collection.
Some examples of things that have been populated even though I wasn't expecting them to be:
ReportRow.Student
ReportRow.Programme
ReportRow.Programme.Modules (a collection)
ReportRow.Programme.Modules.Components (another collection inside each 'Module')
I'm using Fluent automappings, and I've configured it to be lazy-loaded just to be sure using:
.Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Always())
and also tried...
.Conventions.Add(FluentNHibernate.Conventions.Helpers.LazyLoad.Always())
Why is it loading everything?
Thanks
Could you post the generated hbm.xml? example
Also, try this query and see what happens if you access any lazy properties after the using statements...
Summary summary = null;
using(var session = factory.OpenSession())
using(var tx = session.BeginTransaction())
{
summary = session.QueryOver<Summary>()
//.Fetch(x => x.Reports).Eager
.Where(x => x.Id == workItemId)
.SingleOrDefault<Summary>();
tx.Commit();
}
I'm trying to wire NCommon and NH to multiple databases via the guidance at http://codeinsanity.com (see 'Configuring NCommon for multiple database support') and it works via the fluent approach suggested, below:
var configuration = NCommon.Configure.Using(adapter).ConfigureState<DefaultStateConfiguration>();
configuration.ConfigureData<NHConfiguration>(config => config.WithSessionFactory(() => _sessionFactories[0]).WithSessionFactory(() => _sessionFactories[1]));
This works as expected but as you can see the sessionFactories are hardcoded. What I'd really like to do is something like this:
foreach(ISessionFactory sessionFactory in _sessionFactories)
{
configuration.ConfigureData<NHConfiguration>(config => config.WithSessionFactory(() => sessionFactory));
}
But this throws the following exception:
Component NCommon.Data.NHibernate.NHUnitOfWorkFactory could not be registered. There is already a component with that name. Did you want to modify the existing component instead? If not, make sure you specify a unique name.
My hope is there's a proper way to wire-up n-SessionFactories without hardcoding them - but I'm just not seeing a solution. Any advice?
I thought I got this to work by delegating the SessionFactory piece to a method, as below:
configuration.ConfigureData<NHConfiguration>(config => ConfigureSessionFactories(config));
private void ConfigureSessionFactories(NHConfiguration configuration)
{
foreach (ISessionFactory sessionFactory in _sessionFactories)
{
configuration.WithSessionFactory(() => sessionFactory);
}
}
However, that only appears to be a solution. Say you have multiple session factories, you can use all of them to query, but only the last-added session factory will hit the database and return results. Queries against the other session factories will never hit the actual database and will return zero entities.
The only way I've been able to get this to work is:
configuration.ConfigureData<NHConfiguration>(config => config.WithSessionFactory(() => _sessionFactories[0]).WithSessionFactory(() => _sessionFactories[1]));
I'd rather not hard-code that though.. I'd much rather iterate over a loop of n-session factories... does anyone have an idea of how to accomplish this?
Given 1000 documents with a complex data structure. for e.g. a Car class that has three properties, Make and Model and one Id property.
What is the most efficient way in C# to push these documents to raven db (preferably in a batch) without having to query the raven collection individually to find which to update and which to insert. At the moment I have to going like so. Which is totally inefficient.
note : _session is a wrapper on the IDocumentSession where Commit calls SaveChanges and Add calls Store.
private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
var page = 0;
const int total = 30;
do
{
var paged = sales.Skip(page*total).Take(total);
if (!paged.Any()) return;
foreach (var sale in paged)
{
var current = sale;
var existing = _session.Query<Sale>().FirstOrDefault(s => s.Id == current.Id);
if (existing != null)
existing = current;
else
_session.Add(current);
}
_session.Commit();
page++;
} while (true);
}
Your session code doesn't seem to track with the RavenDB api (we don't have Add or Commit).
Here is how you do this in RavenDB
private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
sales.ForEach(session.Store);
session.SaveChanges();
}
Your code sample doesn't work at all. The main problem is that you cannot just switch out the references and expect RavenDB to recognize that:
if (existing != null)
existing = current;
Instead you have to update each property one-by-one:
existing.Model = current.Model;
existing.Make = current.Model;
This is the way you can facilitate change-tracking in RavenDB and many other frameworks (e.g. NHibernate). If you want to avoid writing this uinteresting piece of code I recommend to use AutoMapper:
existing = Mapper.Map<Sale>(current, existing);
Another problem with your code is that you use Session.Query where you should use Session.Load. Remember: If you query for a document by its id, you will always want to use Load!
The main difference is that one uses the local cache and the other not (the same applies to the equivalent NHibernate methods).
Ok, so now I can answer your question:
If I understand you correctly you want to save a bunch of Sale-instances to your database while they should either be added if they didn't exist or updated if they existed. Right?
One way is to correct your sample code with the hints above and let it work. However that will issue one unnecessary request (Session.Load(existingId)) for each iteration. You can easily avoid that if you setup an index that selects all the Ids of all documents inside your Sales-collection. Before you then loop through your items you can load all the existing Ids.
However, I would like to know what you actually want to do. What is your domain/use-case?
This is what works for me right now. Note: The InjectFrom method comes from Omu.ValueInjecter (nuget package)
private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
var ids = sales.Select(i => i.Id);
var existingSales = _ravenSession.Load<Sale>(ids);
existingSales.ForEach(s => s.InjectFrom(sales.Single(i => i.Id == s.Id)));
var existingIds = existingSales.Select(i => i.Id);
var nonExistingSales = sales.Where(i => !existingIds.Any(x => x == i.Id));
nonExistingSales.ForEach(i => _ravenSession.Store(i));
_ravenSession.SaveChanges();
}
This NHibernate blog entry notes how detached QueryOver queries (analogous to DetachedCriteria) can be created (using QueryOver.Of<T>()). However, looking this over, it doesn't look analogous to me at all.
With DetachedCriteria, I would create my instance and set it up however I need, and afterwards call GetExecutableCriteria() to then assign the session and execute the query. With the "detached" QueryOver, most of the API is unavailable (ie, to add restrictions, joins, ordering, etc...) until I call GetExecutableQueryOver, which requires takes an ISession or IStatelessSession, at which point you are no longer disconnected.
How do you work with detached QueryOver instances?
EDIT:
Actual problem was related to how I'm storing the detached QueryOver instance:
public class CriteriaQuery<T>
{
internal protected QueryOver<T> _QueryOver { get; set; }
public CriteriaQuery()
{
_QueryOver = QueryOver.Of<T>();
}
// Snip
}
It should be a QueryOver<T, T>.
I'm using NHibernate 3.1.0.4000. The following code compiles successfully:
Employee salesRepAlias = null;
var query = QueryOver.Of<Customer>()
.JoinAlias(x => x.SalesRep, () => salesRepAlias)
.Where(x => x.LastName == "Smith")
.Where(() => salesRepAlias.Office.Id == 23)
.OrderBy(x => x.LastName).Asc
.ThenBy(x => x.FirstName).Asc;
return query.GetExecutableQueryOver(session)
.List();
This illustrates using restrictions, joins, and ordering on a detached QueryOver just like you would with a regular one.
Could you please post the code that demonstrates the API features that are unavailable?