NHibernate - LINQ Limitations - linq

i've been using Nhibernate with LINQ a fair bit now and i have a few issues. Say i have the following entities:
public class User
{
public virtual int UserID { get; set; }
public virtual bool IsActive { get; set; }
public virtual bool SomeField { get { return 0; } }
public virtual DateTime DateRegistered { get; set; }
public virtual IList<Membership> Membership { get; set; }
public virtual Membership ValidMembership { get { return Membership.FirstOrDefault(m => m.IsValid); } }
}
public class User2
{
public virtual int UserID { get; set; }
public virtual int MembershipID { get; set; }
}
public class Membership
{
public virtual int MembershipID { get; set; }
public virtual bool IsValid { get; set; }
}
Now if i run the following query:
var users = service.Linq<User>()
.Where(u => u.IsActive) // This would work
.Where(u => u.SomeField > 0) // This would fail (i know i can map these using formulas but this is here to illustrate)
.Where(u => u.Membership.Any(m => m.IsValid)) // This would work
.Where(u => u.ValidMembership != null) // This would fail
.Where(u => u.DateRegistered > DateTime.UtcNow.AddDays(-1)) // This would work
.Where(u => u.DateRegistered.AddDays(1) > DateTime.UtcNow) // This would fail
.Select(u => new User2 { UserID = u.UserID }) // This would work
.Select(u => u.UserID) // This would work
.Select(u => new { UserID = u.UserID }) // This would fail
.Select(u => new User2 { UserID = u.UserID, MembershipID = u.Membership.Any(m => m.IsValid) ? u.Membership.Any(m => m.IsValid).First().MembershipID : 0 }); // This would fail
I've added a comment next to each one to indicated whether they would work or fail. Those are the scenarios i can think of at the moment. I've managed to overcome these issues by converting the data to list before it has to do anything too fancy. This obviously has an impact on performance. I was wondering whether future versions of the LINQ provider for NHibernate will support these? Also does anyone know how the entity framework would handle these scenarios. I'd imagine the entity framework would be an improvement but i don't want to jump ship if the same problems exist.
Appreciate your feedback. Thanks

NHibernate 3 supports more constructs than the contrib provider that you are using (Beta1 was just released, final version is expected before the end of the year)
However, as others pointed out, some constructs are hard (or impossible) to parse, while others require very specific code to translate the expression trees to SQL.
Fortunately, the new provider is also extensible, which means you can add your own db logic for methods of your own, or that are not supported out of the box.

This code shouldn't even compile. User.SomeField is a boolean property but you're trying to return 0 from the getter? SomeField and ValidMemberships shoudn't even be virtual because they are field that wouldn't even be managed by NHibernate.

This is an addition to the answer of Diego Mijelshon
Calculated properties can never be parsed by a Linq provider out of the box, because method bodies can not be converted into an expression tree.
Some or maybe even all not implemented issues are implemented in the current Linq provider.
Where(u => u.SomeField > 0) // Calculated property SomeField
Where(u => u.ValidMembership != null) // Calculated property ValidMembership
Where(u => u.DateRegistered.AddDays(1) > DateTime.UtcNow) // The method DateTime.AddDays is not implemented for this side of the date comparison operator greater than >
Select(u => new { UserID = u.UserID })
// Creating anonymous objects is not implemented
Select(u => new User2 { UserID = u.UserID, MembershipID = u.Membership.Any(m => m.IsValid) ? u.Membership.Any(m => m.IsValid).First().MembershipID : 0 });
// Ternary operator not implemented

Entity Framework will fail in the same cases as NHibernate (at least for your examples). Remember, Linq uses Deferred loading for Where()-operations - and everything in Linq2SQL (Entity Framework included) and Linq2NHibernate needs to translate to SQL in defered loading. Method calls cannot be converted to SQL - there is no representation of the method in SQL - and that is why it would fail.
When you ToList() - you force the previous Linq-statements to evaluate (to a database-call) and then working forwards you are working on an in-memory represenation allowing you to use the full Linq2Object Expression-trees (which have the possibility of fancy method-calls etc.)
As for your projections - I wouldn't use Linq2NHibernate for those - but instead use the Projections built into 'standard' NHibernate.

Related

Is there a way I can paginate the included list in a linq query

Hope you're doing well,
I was trying to optimize my reads with entity framework, where I arrived at a position, where I get a record from database by id, and I want to include a one-to-many related list, but I don't want to get all data of the list, just a few, so I want to kind of paginate it.
I want to do this process as long as data is in IQueryable state, I don't want to load all data of list in memory and that paginate it as enumerable.
Let's say the query is like below:
var author = await _dbContext.Authors.Where(x => x.Id == id)
.Include(x => x.Books) // <-- paginate this !!??
.FirstOrDefaultAsync();
Entities represent Data state. Pagination and presentation concerns are View state. Entity Framework can help bridge that gap, but it does so by enabling projection so that you can build View state from Data state. Don't pass entities to views, instead build and pass ViewModels to represent the data in accordance to translations and limitations you want for the view.
For instance if you want to pass Author details with their 5 most recent books:
public class AuthorViewModel
{
public int Id { get; set; }
public string Name { get; set; }
// Any other relevant fields...
public ICollection<BookViewModel> RecentBooks = new List<BookViewModel>();
}
public class BookViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime PublishedDate { get; set; }
// Any other relevant fields...
}
var author = await _dbContext.Authors
.Where(x => x.Id == id)
.Select( x => new AuthorViewModel
{
Id = x.Id,
Name = x.Name,
RecentBooks = x.Books
.OrderByDescending(b => b.PublishedDate)
.Select(b => new BookViewModel
{
Id = b.Id,
Name = b.Name,
PublishedDate = b.PublishedDate
}).Take(5)
.ToList()
}).SingleOrDefault();
This gives you the benefit of structuring the data how you want to present it, while generating efficient queries against the database. You can configure tools like Automapper to perform this kind of mapping to use it's ProjectTo<AuthorViewModel>() as a more succinct alternative.

How to AND predicates in the WHERE clause in a LINQ to RavenDB query

I have the following model:
public class Clip {
public Guid ClipId { get; set; }
public IList<StateChange> StateChanges { get; set; }
}
public class StateChange {
public string OldState { get; set; }
public string NewState { get; set; }
public DateTime ChangedAt { get; set; }
}
And this is how a query raven:
var now = DateTime.UtcNow;
var since = now.AddSeconds(60);
string state = "some state of interest";
using (var session = docStore.OpenSession()) {
RavenQueryStatistics stats;
session.Query<Clip>()
.Statistics(out stats)
.Where(
p => p.StateChanges.Any(a => (since > a.ChangedAt && a.NewState == state))
&& !p.StateChanges.Any(a => (a.OldState == state && now > a.ChangedAt)))
.ToArray();
return stats.TotalResults;
}
I want to get the count for all Clip records that have a (StateChange.CreatedAt before since and NewState is "some state of interest") AND NOT have a (StateChange.CreatedAt before now and OldState is "some state of interest").
While the predicate used above works in linq to object, it doesn't seem to work in linq to raven (i.e. does not return the expected result). I suspect that this is because the expression && !.p.StateChanges.Any.... is never evaluated if the expression on the left-hand side evaluates to true. Is there a way to work around this?
It's not related to the evaluation of conditions. && works just fine.
The problem is that RavenDB doesn't properly handle queries that use .All(...) or !.Any(...). This is due to the way raven's dynamic index engine evaluates your linq statement. It wants to build a separate index entry for each of your StateChange entries, which won't work for operations that need to consider multiple related items, such as the different state changes.
There is an issue already logged for this here. It was closed in build 2151 to throw back a meaningful exception when you try to query in this way. Maybe at some future date they can reassess if there's some way to actually evaluate these types of queries properly instead.
Update
I've been thinking about your challenge, and another related one, and was able to come up with a new technique that will allow you to do this. It will require a static index and lucene query:
public class Clips_ByStateChange : AbstractIndexCreationTask<Clip>
{
public Clips_ByStateChange()
{
Map = clips =>
from clip in clips
select new {
OldState = clip.StateChanges
.Select(x => x.OldState + "|" + x.ChangedAt.ToString("o")),
NewState = clip.StateChanges
.Select(x => x.NewState + "|" + x.ChangedAt.ToString("o"))
};
}
}
var results = session.Advanced.LuceneQuery<Clip, Clips_ByStateChange>()
.Where(string.Format(
"NewState: {{{0}|* TO {0}|{1}}} AND -OldState: {{{0}|* TO {0}|{2}}}",
state, since.ToString("o"), now.ToString("o")));
Of course, you can still just take statistics on it if that's what you want.

Using RavenDB, how do I efficiently retrieve a list of items related by a 'foreign key'

I store User objects in RavenDB. Each User has a User.Id property.
I also have a Relationship class that links two User.Ids together to create a Mentor/Mentee relationship, like this:
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
... more properties
}
public class Relationship
{
public string Id { get; set; }
public string MentorId { get; set; }
public string MenteeId { get; set; }
public RelationshipStatus Status { get; set; }
}
Now I want to retrieve a list of Mentees for a given Mentor. I have done this in the following way:
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
var mentees = new List<User>();
db.Query<Relationship>()
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id)));
return mentees;
}
This seems to work just fine but the coding-angel on my shoulder is wrinkling her nose at the smells emanating from the nested use of the IDocumentSession (db) and the need for multiple Load calls to fill the Mentees List.
How can I optimise this method using best practice RavenDB syntax?
Edit
Thanks to #Jonah Himango (see accepted answer below) who solved the problem of multiple calls to the database for me. In addition I have also created a new extension method called 'Memoize' to eliminate the need for the external 'mentees' result List (see code above).
Here is the optimised code. Please feel free to comment and refine further.
The Linq
public static List<User> GetMentees(IDocumentSession db, string mentorId)
{
return db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Memoize()
.Select(r => db.Load<User>(r.MenteeId))
.ToList();
}
The extension method
public static List<T> Memoize<T>(this IQueryable<T> target)
{
return target.ToList();
}
Note : This extension method may seem completely superfluous (it is really) but it irritates my geek-gland that I have to call a function called ToList(), not to create a list, but to force the execution of the Linq statement. So my extension method just renames ToList() to the far more accurate Memoize().
You'll want to use the .Include query customization to tell Raven to include the related user object off of each Relationship:
db.Query<Relationship>()
.Customize(x => x.Include<Relationship>(o => o.MenteeId))
.Where(r => r.MentorId == mentorId)
.Select(r => r.MenteeId)
.ForEach(id => mentees.Add(db.Load<User>(id))); // .Load no longer makes a call to the DB; it's already loaded into the session!
Relevant documentation here:
The call to Load() is resolved completely client side (i.e. without
additional requests to the RavenDB server) because the [related]
object has already been retrieved via the .Include call.

NHibernate: Where clause containing child collection as subquery building improper T-SQL statements

I am using NHibernate 3.x, along with Fluent NHibernate, and have not had any issues constructing database queries until now.
To simplify my objects for the purposes of this post, I've included a subset of my object and mapping structures below:
IssueItem POCO entity class:
public class IssueItem : DomainEntity, IKeyed<Guid> {
public virtual Guid ID { get; set; }
public virtual string Subject { get; set; }
public virtual string Description { get; set; }
public virtual IList<IssueLocation> Locations { get; set; }
}
Location POCO entity class:
public class Location : DomainEntity, IKeyed<Guid> {
public virtual Guid ID { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string Zip { get; set; }
public virtual string Organization { get; set; }
public virtual IssueItem Issue { get; set; }
}
IssueItem Fluent NHibernate map:
public class IssueItemMap : DomainEntityMapping<IssueItem> {
public IssueItemMap()
{
Table("IssueItem");
LazyLoad();
Map(x => x.ID).Column("ID");
Map(x => x.Subject).Column("Subject");
Map(x => x.Description).Column("Description");
HasMany(x => x.Locations).KeyColumn("IssueItemID").LazyLoad().ReadOnly().Inverse();
}
}
Location Fluent NHibernate map:
public class LocationMap : DomainEntityMapping<Location> {
public LocationMap()
{
Table("Location");
LazyLoad();
Map(x => x.ID).Column("ID");
Map(x => x.City).Column("City");
Map(x => x.State).Column("State");
Map(x => x.Zip).Column("Zip");
Map(x => x.Organization).Column("Organization");
References(x => x.IssueItem).ForeignKey("IssueItemID").LazyLoad().ReadOnly();
}
}
Now, I'm using a Unit of Work and Service/Repository pattern in my MVC app. Therefore, I have a domain layer of my project that contains my basic POCO entities, as well as validators and services. In my data layer, I've got my NHibernate-related stuff, such as my repositories that my domain layer access from my services. This is where my NHibernate maps live as well.
In order to ensure that no NHibernate-specific logic creeps into my domain layer (in case I want to use a different ORM in the future), I perform my LINQ statements in my services within my domain layer against IQueryable objects returned from the repositories in my data layer. Therefore, when I write my queries, I am using System.Linq and System.Linq.Expressions instead of the NHibernate.Linq class.
That said, here's my LINQ query I'm having issues with from within one of my service classes in my domain layer:
var issues = _issueRepo.All();
if (!string.IsNullOrWhiteSpace(searchWords)) {
issues = issues.Where(i => i.Subject.Contains(searchWords)
|| i.Description.Contains(searchWords)
|| i.Locations.Where(l => l.Organization.Contains(searchWords)
|| l.City.Contains(searchWords))
.Select(x => x.IssueItemID).Contains(i.ID)
);
}
Now, the IssueItems are queried just fine. However, the one-to-many table (Locations) is not properly queried. This is what I mean...
The generated T-SQL statement is perfect except for the very end of it. Example:
select TOP(100) issueitem0_.ID as ID2_, issueitem0_.Subject as Subject2_, issueitem0_.Description as Description2_
from IssueItem issueitem0_
where issueitem0_.Subject like ('%test%') or issueitem0_.Description like ('%test%')
or exists (select location1_.IssueItemID from Location location1_ where
issueitem0_.ID=location1_.IssueItemID and (location1_.Organization like ('%test%')
or location1_.City like ('%test%')) and location1_.ID=issueitem0_.ID)
See that last bit? It throws in that last "and" statement (and location1_.ID=issueitem0_.ID) that throws a wrench in the whole system. I have tweaked every configuration parameter I could think of with my mapping and have tried many different LINQ statements and I cannot get rid of that last part. I don't know why it adds it.
If I construct the same LINQ statement in LINQPad, it properly generates the T-SQL statement without the last part (and location1_.ID=issueitem0_.ID).
Any ideas?
Thanks!
Joel
Add Any() when you query locations. It will come true if any location property contains what you are looking for. You are trying to select in where clause, then trying to get IssueID from there. I think you will see this query is clearer.
var issues = _issueRepo.All();
if (!string.IsNullOrWhiteSpace(searchWords))
{
issues = issues.Where(i => i.Subject.Contains(searchWords)
|| i.Description.Contains(searchWords)
|| i.Locations.Any(l => l.Organization.Contains(searchWords))
|| i.Locations.Any(l => l.City.Contains(searchWords)) )
}

Linq to NHibernate projection to anon. type results in mystifying cast error

I have an TaxWork entity which is persisted using NHibernate. This entity has the following properties (among others):
public virtual TaxWorkType Type { get; set; } //Kctc.TaxWorkType is an enumeration
public virtual TaxWorkStatus Status { get; set; } //Kctc.TaxWorkStatus is an enumeration
public virtual LegalWorkPriority Priority { get; set; } //Kctc.LegalWorkType is an enumeration
public virtual User Handler { get; set; } //Kctc.BusinessLayer.Entities.User is another entity
public virtual int? CaseNumber { get; set; }
I am using Linq to NHibernate to pull of a subset of the tax work objects as follows (taxWorkRepository.All obviously returns an IQueryable):
foreach (TaxWork taxWork in taxWorkRepository.All.Where(x => x.CaseNumber == _caseNumber).OrderBy(x => x.DateCreated))
{
...
}
This works fine. I want to use projection in order to query only the columns that are required in this case. I am usnig the following code:
foreach (var taxWorkFragment in taxWorkRepository.All.Where(x => x.CaseNumber == _caseNumber).OrderBy(x => x.DateCreated).Select(x => new { Type = x.Type, DateCreated = x.DateCreated, Handler = x.Handler, Status = x.Status, Priority = x.Priority }))
{
...
}
However, I'm getting the following error when trying to create the anonymous type:
Invalid cast from 'Kctc.TaxWorkStatus' to 'Kctc.BusinessLayer.Entities.User'.
Where on earth is it getting the idea that it should be casting a TaxWorkStatus to a User?
Any suggestions whatsoever what might be going wrong?
Try to make like this:
foreach (var taxWorkFragment in taxWorkRepository.All.Where(x => x.CaseNumber == _caseNumber).OrderBy(x => x.DateCreated)
.Select(x => new TaxWork { Type = x.Type, DateCreated = x.DateCreated, Handler = x.Handler, Status = x.Status, Priority = x.Priority }))
{
...
}
It should help

Resources