I have simple set of objects stored in RavenDB:
public class Question
{
public string Id { get; set; }
public DateTime CreatedOn { get; set; }
public ICollection<User> Supporters { get; set; }
public ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public string Id { get; set; }
public bool IsOfficial { get; set; }
}
Now I want to query RavenDB to give me set of questions, ordered firstly by number of supporters, next by condition - if a question has any official answer, and in the and, by question creation date. So I've written a query:
var questions = DocumentSession.Query<Question>().AsQueryable();
questions = questions
.OrderByDescending(x => x.Supporters.Count)
.ThenByDescending(x => x.Answers.Any(a => a.IsOfficial)) //EDIT: source of exception
.ThenByDescending(x => x.CreatedOn)
.Take(15);
var result = questions.ToList();
which throws an exception:
System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'
The query is logically correct and works, when I use linq-to-objects, and simply add .ToList() to first line:
var questions = DocumentSession.Query<Question>().Tolist().AsQueryable();
// next lines stay unchanged
I don't want to do it because of performance issues (this change forces all questions to be loaded from database into memory before filtering).
How to make this working without performance impact ? Maybe shell I define an index ? How it should looks like then ?
A custom index for your purposes is basically going to be a recreation of your class with extra fields in it (and some logic to support it). It seems like you don't want to have to add more fields to your current class, are you okay with adding more classes to your project?
Here's an example:
public class Question_WithAnyOfficial: AbstractIndexCreationTask<Question>
{
public class Question_WithAnyOfficial()
{
Map = questions => from question in questions
// New Anonymous Type
select new
{
Id = question.Id,
CreatedOn = question.CreatedOn,
Supporters = question.Supporters,
Answers = question.Answers,
AnyOfficial = question.Answers.Where(a => a.IsOfficial).Any()
};
}
}
Then you can query this:
var questions = DocumentSession.Query<Question_WithAnyOfficial>()
.OrderByDescending(x => x.Supporters.Count)
.ThenByDescending(x => x.AnyOfficial)
.ThenByDescending(x => x.CreatedOn)
.Take(15)
.ToList();
Don't forget that you'll have to register the index when your app starts.
Raven can't support calculations like that inside the LINQ query, so this should work (problem clause removed):
var questions = DocumentSession.Query<Question>()
.OrderByDescending(x => x.Supporters.Count)
//.ThenByDescending(x => x.Answers.Any(a => a.IsOfficial))
.ThenByDescending(x => x.CreatedOn)
.Take(15);
var result = questions.ToList();
If you want to include that logic, you need a field on your class called AreAllAnswersOfficial (or something similar). Then you can put that inside the clause:
var questions = DocumentSession.Query<Question>()
.OrderByDescending(x => x.Supporters.Count)
.ThenByDescending(x => x.AreAllAnswersOfficial)
.ThenByDescending(x => x.CreatedOn)
.Take(15);
var result = questions.ToList();
Based on Bear Alexander response, I've done this like that:
public class QuestionByAnyOfficial : AbstractIndexCreationTask<Question, QuestionByAnyOfficial.Result>
{
public class Result
{
public string Id;
public bool AnyOfficial;
public int SupportersCount;
public DateTime CreatedOn;
}
public QuestionByAnyOfficial()
{
Map = questions => from question in questions
select new
{
Id = question.Id,
AnyOfficial = question.Answers.Any(a => a.IsOfficial),
SupportersCount = question.Supporters.Count,
CreatedOn = question.CreatedOn
};
}
}
var questionIds = DocumentSession.Query<QuestionByAnyOfficial.Result, QuestionByAnyOfficial>()
.OrderByDescending(x => x.SupportersCount)
.ThenByDescending(x => x.AnyOfficial)
.ThenByDescending(x => x.CreatedOn)
.Take(NumberOfQuestions)
.Select(x => x.Id);
var questions = DocumentSession.Load<Question>(questionIds);
var result = questions.ToList();
It works and I believe it is more efficient than my original version. If it can be done in any more elegant way, I'd appreciate any ideas. Regards.
Related
I'm trying to reuse an EF6 extension method with an associated entity (one to many relationship). Contrived example:
public class Parent
{
public string State { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public string Value { get; set; }
public Parent Parent { get; set; }
}
public static ParentNamedScopes
{
public static IQueryable<Parent> IsReady(this IQueryable<Parent> queryable)
{
return queryable.Where(p => p.State == "Ready" || p.State == "New");
}
}
// ...
var children = db.Children
// my goal, but can't cast Parent to IQueryable<Parent>
// ------------------v
.Where(c => c.Parent.IsReady())
.Where(c => c.Value == "Foobar");
I've seen examples of using AsQueryable() on associated collections in sub queries, but that isn't an options since Parent is a single record. I'm sure I'm missing something obvious and I apologize since my google foo has not turned up the answer today.
One option would be to start your query with the Parents:
var children = db.Parents.IsReady()
.SelectMany(p => p.Children)
.Where(c => c.Value == "Foobar");
The idea that IsReady would convert an IQueryable seems a little off to me, though. If your use case gets more complex, you may need to change that to just give you an Expression<Func<Parent, bool>>, and use something like LINQKit to manipulate your query to make it reusable:
Expression<Func<Parent, bool>> parentIsReady = ParentCriteria.IsReady();
var readyParents = db.Parents.Where(parentIsReady);
var childrenWithReadyParents = db.Children.AsExpandable()
.Where(c => parentIsReady.Invoke(c.Parent))
.Where(c => c.Value == "Foobar");
Consider the following class:
public class TaxType
{
public int Id {get;set;}
public decimal TotalTaxCollected {get;set;}
public string DetailXml {get;set;}
}
I got the following LINQ query somewhere in the code:
GetTaxTypesFromTheDataSource()
.Where(/*blah blah blah*/)
.GroupBy(t => new { t.Id })
.Select(g => new TaxType
{
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.Aggregate(SumOfInnerElementsOfXml).DetailXml
})
.ToList();
Where SumOfInnerElementsOfXml is declared as follows:
private TaxType SumOfInnerElementsOfXml(TaxType t1, TaxType t2)
{
//(a) Deserialize the t1.DetailXml & t2.DetailXml into 2 different arrays.
//(b) Aggregate both arrays based on their IDs.
//(c) Serialize the result into an xml string.
//(d) Instantiate a new TaxType and set its DetailXml to the generated xml string.
//return (d)
}
The above solution works fine, however I'd like to create my own aggregate function so that I can use it as follows:
GetTaxTypesFromTheDataSource()
.Where(/*blah blah blah*/)
.GroupBy(t => new { t.Id })
.Select(g => new TaxType
{
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.MyCustomAggregateFunction(n => n.DetailXml)
})
.ToList();
How's that possible? I've tried several extension methods but unfortunately none of them worked.
Your initial solution seems pretty fine to me. However, if you don't like it...
To create a predefined function (not lambda), I'd recommend first to get rid of anonymous type in your query - change:
.GroupBy(t => new { t.Id })
to
.GroupBy(t => t.Id)
This is also generally better, as you get rid of one boxing/unboxing per TaxType and transform IGrouping<{anonymous},TaxType> into IGrouping<int,TaxType> (easier to understand the semantics if someone inspects the code later)
Then, you can declare the method as follows:
public static class Extensions {
public static string MyCustomAggregateFunction(this IGrouping<int,TaxType> source, Func<TaxType,string> selector) {
// blah-blah-blah
//return something
}
}
You can later make it generic if the need arises.
I'd also recommend getting rid of the dependency on IGrouping, as you later may need to apply that aggregation elsewhere:
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.Select(n => n.DetailXml).AggregateTaxXML()
and
public static string AggregateTaxXML(this IEnumerable<string> source) {
// blah-blah-blah
//return something
}
Sources/further reading: MSDN
I'm using projection of query results to a custom type, which isn't a part of entity data model:
public sealed class AlgoVersionCacheItem : NotificationObject
{
public int OrderId { get; set; }
public string OrderTitle { get; set; }
public int? CurrentVersion { get; set; }
public int CachedVersion { get; set; }
public IEnumerable<int> AvailableVersions { get; set; }
}
I want AvailableVersions to be sorted in descending order. Hence, I've tried to add sorting for AvailableVersions in projection:
return someQueryable
.Select(version => new AlgoVersionCacheItem
{
OrderId = version.OrderId,
OrderTitle = version.Order.Title,
CurrentVersion = version.Order.CurrentAlgoVersionId,
CachedVersion = version.Id,
AvailableVersions = version
.Order
.AlgoVersions
.Where(v => (allowUncommittedVersions || v.Statuses.Any(s => s.AlgoVersionStatusListItemId == ModelConstants.AlgoVersionCommitted_StatusId)) && v.Id != version.Id)
.OrderByDescending(v => v.Id) // this line will cause exception
.Select(v => v.Id)
})
.Where(item => item.AvailableVersions.Any())
.OrderByDescending(item => item.OrderId)
.ToArray();
With sorting, execution of the query throws an System.Data.EntityCommandCompilationException with System.InvalidCastException as inner exception:
Unable to cast object of type
'System.Data.Entity.Core.Query.InternalTrees.SortOp' to type
'System.Data.Entity.Core.Query.InternalTrees.ProjectOp'
Without .OrderByDescending(v => v.Id) everything works fine.
Is this yet another feature, that isn't supported in Entity Framework, or I've missed something?
P.S. I know, that I can sort items later at client side, but I'm wondering about sorting at the server side.
This is a bug in EF. I was able to repro this on both EF5 and EF6. I think you should be able to workaround the bug by filtering records before creating the results i.e.:
return someQueryable
.Where(version => version.Order.AlgoVersions.Any(v => (allowUncommittedVersions || v.Statuses.Any(s => s.AlgoVersionStatusListItemId == ModelConstants.AlgoVersionCommitted_StatusId)) && v.Id != version.Id))
.Select(version => new AlgoVersionCacheItem
{
OrderId = version.OrderId,
OrderTitle = version.Order.Title,
CurrentVersion = version.Order.CurrentAlgoVersionId,
CachedVersion = version.Id,
AvailableVersions = version
.Order
.AlgoVersions
.Where(v => (allowUncommittedVersions || v.Statuses.Any(s => s.AlgoVersionStatusListItemId == ModelConstants.AlgoVersionCommitted_StatusId)) && v.Id != version.Id)
.OrderByDescending(v => v.Id) // this line will cause exception
.Select(v => v.Id)
})
.OrderByDescending(item => item.OrderId)
.ToArray();
I also have a feeling that this query could be simplified if you go from the other side of relationship (i.e. from Orders) but it may depend on how the someQueryable is created.
In my case the Linq query was selecting a new anonymous construct with one property being set to a collection ToList() extension method. I removed the embedded execution, the error went away, and the system still worked fine.
i'm a C# developer and i have a trouble with Entity Framework 5.
I have mapped my database with Entity using the default code generation strategy. In particolar there are three classes: menus, submenus and submenuitems.
The relationships about three classes are:
one menu -> to many submenus
one submenu -> to many submenuitems.
All classes have a boolean attribute called "Active".
Now, i want to filter all the Menus with the SubMenus active, and the SubMenus with the SubMenuItems active.
To get this i've tried this:
var tmp = _model.Menus.Where(m => m.Active)
.Select =>
new
{
Menu = x,
SubMenu = x.SubMenus.Where(sb => sb.Active)
.Select(y =>
new
{
SubMenu = y,
SubMenuItem = y.SubMenuItems.Where(sbi => sbi.Active)
})
})
.Select(x => x.Menu).ToList();
But didn't work.
Someone can help me?
Thank you for your help!
Hi have you see this post? Entity Framework: Querying child entities. there are some difference from your code, maybe this helps you.
UPDATE: Projection is when the result of a query is output to a different type than the one queried, it can be to an anonymous type, but could also be to a concrete type. And so using Data transfer object can be usefull to pass data between processes you can read a full explaination here Data Transfer objects
public class MenuDto
{
public int MenuId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<MenuDto> SubMenus { get; set; }
}
_model.Menus.Where(m => m.Active)
.Select(p => new MenuDto
{
MenuId = p.idField,
Name = p.NameField,
Url = p.UrlField,
SubMenus = p.SubMenus.Where(sb => sb.Active)
.Select(y => new MenuDto
{
MenuId = y.idField,
Name = y.NameField,
Url = y.UrlField,
SubMenuItem = y.SubMenuItems.Where(sbi => sbi.Active)
.Select(z => new MenuDto
{
MenuId = z.idField,
Name = z.NameField,
Url = z.UrlField
})
})
}).ToList();
I hope this can solve your problem
can you try this:
List<SubMenuItems> tmp = _model.menus.Where(a => a.active)
.SelectMany(b => b.SubMenus.Where(a => a.active)).ToList()
.SelectMany(c => c.SubMenuItems.Where(a => a.active)).ToList();
I hope it's helping.
I'm trying to write this specific sql query in nHibernate QueryOver language, which I am not very familiar with:
SELECT MessageThreadId FROM MessageThreadAccesses
WHERE ProfileId IN (arr)
GROUP BY MessageThreadId
HAVING COUNT(MessageThreadId) = arr.Count
where arr is a array of integers(user Ids) I'm passing as argument and MessageThreadAccess entity looks like this:
public virtual MessageThread MessageThread { get; set; }
public virtual Profile Profile { get; set; }
....
After reading multiple stack overflow threads and experimenting I got this far with my query (trying to get MessageThread object - it should always be just one or none), but it still doesn't work and I'm not really sure what else to try. The query always seems to be returning the MessageThreadAccess object, but when reading it's MessageThread property it's always NULL.
var access = Session.QueryOver<MessageThreadAccess>()
.WhereRestrictionOn(x => x.Profile).IsIn(participants.ToArray())
.Select(Projections.ProjectionList()
.Add(Projections.Group<MessageThreadAccess>(x => x.MessageThread))
)
.Where(
Restrictions.Eq(Projections.Count<MessageThreadAccess>(x => x.MessageThread.Id), participants.Count)
)
.TransformUsing(Transformers.AliasToBean<MessageThreadAccess>())
.SingleOrDefault();
return Session.QueryOver<MessageThread>()
.Where(x => x.Id == access.MessageThread.Id)
.SingleOrDefault();
Can someone point me in the right direction, or explain what am I doing wrong?
Thanks in advance.
I guess you may try using a DTO for storing the result, instead of trying to fit the result in a MessageThreadAccess, when it is not one (no Profile).
Maybe you can try :
public class MessageThreadCountDTO
{
public MessageThread Thread { get; set; }
public int Nb { get; set; }
}
then
var profiles = new int[] { 1,2,3,4 };
MessageThreadCountDTO mtcDto = null;
var myResult =
_laSession.QueryOver<MessageThreadAccess>()
.WhereRestrictionOn(x => x.Profile.Id).IsIn(profiles)
.SelectList(list =>
list.SelectGroup(x => x.MessageThread).WithAlias(() => mtcDto.Thread).
SelectCount(x => x.MessageThread).WithAlias(() => mtcDto.Nb)
)
.Where(Restrictions.Eq(Projections.Count<MessageThreadAccess>(x => x.MessageThread), profiles.Count()))
.TransformUsing(Transformers.AliasToBean<MessageThreadCountDTO>())
.List<MessageThreadCountDTO>().FirstOrDefault();
would profiles be a Profile[], and not an int[], then the following line :
.WhereRestrictionOn(x => x.Profile.Id).IsIn(profiles)
should be :
.WhereRestrictionOn(x => x.Profile).IsIn(profiles)
Hope this will help