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

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.

Related

linq any clause on multiple values

I am trying to create a linq statement to filter otu results using an any clause. My issue is that I dont have a single value to compare against.
In the example below I have a PropertyTaxBill entity that is the parent. Each one has a collection of TaxPropertyAssessmentDetails attached to it.
In this query people can specify that they only want to deal with bills pertaining to a specific class strata so I check to see if any values exist in the classStrata variable. If so then the user selected specific ones. I was trying to do an any clause on the classStrata but instead of giving it a single value to match on I was trying to select all the values in the TaxPropertyAssessmentDetails collection attached to the PropertyTaxBill. Is this possible?
using (var dataContext = contextProvider.GetContext())
{
var query = dataContext.PropertyTaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => classStrata.Any(y => y == x.TaxPropertyAssessmentDetails.SelectMany(z => z.PropertyTaxClassStrataId)));
}
}
You need to use contains so that ef is able to translate to a ef query.
Not sure that i understood your model correctly. Here's a example with a model. Please tell me if i understood incorrect so i can fix it.
public void TestMethod1()
{
IEnumerable<TaxBill> TaxBills = new List<TaxBill>();
var query = TaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => x.AssessmentDetails.Any(ad => ad.TaxClassStrataId.Any(cs => classStrata.Contains(cs))));
}
}
And the entities
public class TaxBill
{
public int Id { get; set; }
public ICollection<AsessmentDetails> AssessmentDetails { get; set; }
}
public class AsessmentDetails
{
public ICollection<int> TaxClassStrataId { get; set; }
}

Linq Grouping looses the child entities

I have the following query:
var _customers = (from c in _db.UserProfiles.Include(x=>x.ParentCompanies).Include(x=>x.cProfile).Include(x=>x.cProfile.PhoneNumbers).Include(x=>x.cProfile.Addresses)
where (c.ParentCompanies.Any(pc => pc.CompanyUsers.Any(cu => cu.UserName == userName)) && c.cProfile != null)
group c by c.FirstName.Substring(0, 1).ToUpper() into customerGroup
select new ContactsViewModel
{
FirstLetter = customerGroup.Key,
Customers = customerGroup
}).OrderBy(letter => letter.FirstLetter);
if I take out the group, it works well and includes all the children (parentCompanies, cProfile, ...) as soon as I put the group back in it looses all of the children. How do I solve this issue?
update
I guess I should also include the view model that I'm usign to put the result in.
public class ContactsViewModel
{
public string FirstLetter { get; set; }
public IEnumerable<UserProfile> Customers { get; set; }
}
Include only applies to items in the query results (i.e. the final projection) and cannot contain operations that change the type of the result between Include and the outermost operation (e.g. GroupBy())
http://wildermuth.com/2008/12/28/Caution_when_Eager_Loading_in_the_Entity_Framework
If you want to eager load, do the grouping client-side (i.e. enumerate the query then call the GroupBy method on the results)

How to dynamically add properties to a LINQ GroupBy clause

I have a class Product:
class Product
{
int Id { get; set; }
string Name { get; set; }
int CategoryId { get; set; }
int PlantId { get; set; }
DateTime ProductionDate { get; set; }
}
I would like to use the LINQ GroupBy on multiple properties but I do not know in advance how many and which properties. For instance I might want to group by just CategoryId, just PlantId or both. I found an article on the net that describes how to use LINQ GrouBy dinamically.
This might work good indeed but if I want to perform the Group By on ProductionDate.Year and ProductionDate.Month without knowing the granularity in advance? As granularity I mean whether I want to group all the Products produced in a specific year or narrow the group by to the month.
The only logical solution that I found is:
public ProductInfo GetGroupedProducts(int? year, int? month, int? selectedCategoryId, int? selectedPlantId)
{
List<Product> products = GetProducts();
var groupedProducts = products.GroupBy(p => new { (year == null ? p.ProductionDate.Year : string.Empty),
(month == null ? p.ProductionDate.Month : string.Empty),
(selectedCategoryId == null ? p.CategoryId : string.Empty),
(selectedPlantId == null ? p.PlantId : string.Empty)
});
//perform some additional filtering and assignments
}
But I guess there could be a cleaner and more proper solution. With the old style way of building queries, based on strings, this task was much easier to accomplish. If there is no other way, I really think this is a part of LINQ that needs to be improved.
The cleaner solution is to use this extension method:
public static TResult With<TInput, TResult>(this TInput? o, Func<TInput, TResult>
selector, TResult defaultResult = default(TResult)) where TInput : struct
{
return o.HasValue ? selector(o.Value) : defaultResult;
}
Like this:
string result = year.With(T => p.ProductionDate.Year, string.Empty);
of this, if nulls are okay:
string result = year.With(T => p.ProductionDate.Year);
or something with T which is int in case the int? has value.
But, you know, the better solution is out there, so feel free to expand your code so I could analyze it.
If I understand what you are asking, I had a similar issue Reversing typeof to use Linq Field<T>
I would do something like
public static IEnumerable<IGrouping<string, TElement>> GroupMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
return elements.GroupBy(e => string.Join(":", groupSelectors.Select(s => s(e))));
}
then you can call your function like
var groupedProducts = products.GroupMany(p => p.CategoryId , p => p.ProductionDate.Month);
The function groups via a string of the properties divided by a colon. The reason why I did this is because the hashcode for strings are guaranteed to be the same as opposed to a class.

Passing an expression to a method in NHibernate results in Object of type 'ConstantExpression' cannot be converted to type 'LambdaExpression'

This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:
System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.
Classes involvend:
public class A
{
public virtual int Id { get; set; }
public virtual ISet<B> DataFields { get; set; }
}
public class B
{
public virtual int Id { get; set; }
}
Sample test code:
[TestMethod]
public void TestMethod1()
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
Console.Write("Number of records is {0}", records.Count());
}
}
[TestMethod]
public void TestMethod2()
{
GetAsWhereB(b => b.Id == 1);
}
private void GetAsWhereB(Func<B, bool> where)
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(where));
Console.Write("Number of records is {0}", records.Count());
}
}
This is one problem:
private void GetAsWhereB(Func<B, bool> where)
That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:
private void GetAsWhereB(Expression<Func<B, bool>> where)
As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:
var records = session.Query<A>().Where<A>(a => a.DataFields.
Any(b => b.Id == 1));
you make it clear that the "Any" call is on DataFields:
var records = session.Query<A>().Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)
Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.
private void GetAsWhereB(Func<B, bool> where)
{
Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(w));
Console.Write("Number of records is {0}", records.Count());
}
}

NHibernate - LINQ Limitations

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.

Resources