How to use dynamic linq to query through nested arrays? - linq

If I have class structure as follows:
private class A
{
int afoo { get; set; }
B[] bList { get; set; }
}
private class B
{
String bfoo { get; set; }
C[] cList { get; set; }
}
private class C
{
String cfoo { get; set; }
int cfoo2 { get; set; }
}
public class Master
{
A[] aList;
//qry for all where A.B.C.cfoo = "test"
}
How would I construct a dynamic LINQ statement to query for items in class C? Something like this
1) var qry = aList.blist.clist.AsQueryable().Where("cfoo = \"Test\"").Select();
My ultimate solution would be to pass the entire path in the dynamic part like this:
2) var qry = aList.AsQueryable().Where("bList.cList.cFoo = ""Test"").Select();
But from what I have tried you can not have the nested objects in the Where. So I am going to live with using templates to build the methods as in 1) above.
[Also, I am using the dynamic library from Scott Gu for the dynamic part.]
But, I can't get that to work. Any suggestions?
Thanks!

aList.SelectMany(a => a.bList)
.SelectMany(b => b.cList)
.Where(c => c.cfoo == "\"Test\"");

Try this:
aList.SelectMany(a => a.bList.SelectMany(b => b.cList))
.Where(c => c.cf002 == "Test")
.Select();

Related

LINQ Lazy load or query incorrect

LINQ Query not populating
Model extract is as follows
public class ServiceBulletin
{
[Key]
public int Id { get; set; }
public virtual ICollection<ServiceBulletinProducts> ApplicableProducts { get; set; }
}
public class ServiceBulletinProducts
{
[Key]
public int Id { get; set; }
public int ServiceBulletinId { get; set; }
public virtual Product Product{ get; set; }
}
I'm using the following code at the moment to populate a collection
var x = from m in _dc.ServiceBulletins.Include(p => p.ApplicableProducts)
.Include(m => m.Manufacturer)
where m.DeleteStatus == DeleteStatus.Active
select m;
var x1 = new List<ServiceBulletin>();
foreach (var item in x)
{
var p = from m1 in _dc.ServiceBulletinsProducts.Include(p2=>p2.Product)
where m1.Product.DeleteStatus == DeleteStatus.Active &&
m1.ServiceBulletinId == item.Id
select m1;
var p99 = p.ToList();
item.ApplicableProducts = p99;
x1.Add(item);
};
So this is intended to have a Parent Child relationship and I’m trying to do a query which populates a collection of ServiceBulletins with a ApplicableProducts item with a fully populated collection of ServiceBulletinProducts for the ServiceBulletin with the values of the Product populated
The collection is populated but the ServiceBulletinProducts are always set to null and I can’t seem to add an Include such as .Include(p => p.ApplicableProducts.Products) to try and populate the product details – which is resulting in me iterating around the collection to populate the items.
Am I missing something to enable the population on the 1st query for the Include statement or do I need to do the query in a different way ?
Figured out the following should do the trick.
var x = from m in _dc.ServiceBulletins.Include(p => p.ApplicableProducts.Select(p2=>p2.Product))
.Include(m => m.Manufacturer)
where m.DeleteStatus == DeleteStatus.Active
select m;

Linq Queryable Expression Tree: SqlException: Incorrect syntax near '&'

I am getting an error: { SqlException: Incorrect syntax near '&' } which I cannot seem to figure out. I have a class QuestionResult (seen below).
public class QuestionResult
{
public int Id { get; set; }
public float? Result { get; set; }
public string ResultText { get; set; }
//Foreign Keys & Connections
public virtual SurveyResult SurveyResult { get; set; }
public virtual Question Question { get; set; }
public int QuestionId { get; set; }
public int SurveyResultId { get; set; }
}
which I wish to filter by QuestionId & Result, for example {QuestionId == 48 && Result >= 24}. The issue appears to be around using Expression.And. I tried this test without using it, using just one of the conditions and it ran fine. I have previously used Expression.And and it has worked.
public static List<int> GetIdList()
{
IQueryable<QuestionResult> questionResultList = _context.QuestionResult.AsQueryable();
var questionResult = Expression.Parameter(typeof(QuestionResult), "qr");
MemberExpression questionId = Expression.Property(questionResult, "QuestionId");
MemberExpression result = Expression.Property(questionResult, "Result");
var conditionA = Expression.Equal(questionId, Expression.Constant(48));
var conditionB = Expression.GreaterThanOrEqual(result, Expression.Convert(Expression.Constant(24), result.Type));
var and = Expression.And(conditionA, conditionB);
var predicateBody = Expression.Lambda<Func<QuestionResult, bool>>(and, questionResult);
MethodCallExpression query = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { questionResultList.ElementType },
questionResultList.Expression,
predicateBody
);
var questionResultIdList = questionResultList.Provider.CreateQuery<QuestionResult>(query).Select(i => i.Id).ToList();
return questionResultIdList;
}
Can you see somewhere that I have gone wrong with this? Debugging the query before it has run shows:
{value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[SurveyV3.Models.DatabaseModels.QuestionResult]).Where(qr => ((qr.QuestionId == 48) And (qr.Result >= Convert(24))))}
which looks right to me. I have been using questions on here and this Microsoft page for assistance https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-use-expression-trees-to-build-dynamic-queries
Thanks, Oj
var and = Expression.And(conditionA, conditionB);
Expression.And does a bitwise AND operation.
You want Expression.AndAlso which does a logical AND.

SelectMany while Referencing Parent

Given
List<Foo> foos;
public class Foo
{
public string Name { get; set; }
public List<Bar> Bars { get; set; }
}
public class Bar
{
public double Score { get; set; }
}
I'm trying to output, for each object in foos, the property Foo.Name along with the maximum Bar.Score. I see how to get the maximum score:
foos.SelectMany(f => f.Bars).MaxBy(b => b.Score).Select(b => b.Score);
Is there a way to get the corresponding Name as well, without adding a reference from Bar back to Foo? If I made the following change
public class Bar
{
public Foo Foo { get; set; } // Can't really add this
public double Score { get; set; }
}
I could do
foos.SelectMany(f => f.Bars).MaxBy(b => b.Score)
.Select(b => new { Name = b.Foo.Name, Score = b.Score) });
However in the real case, I cannot add the back reference.
You don't need to use SelectMany, just use Select with Max:
var result = foos
.Select(f => new
{
Name = f.Name,
MaxScore = f.Bars.Max(b => b.Score)
});

Select multiple columns in LINQ

I've written a LINQ query shown below :
List<Actions> actions = resourceActions.Actions.Select(s => s.ActionName).ToList();
How do I give for selecting multiple columns here ? ie I want to add columns s.ActionId and s.IsActive. I'm unable to apply it.
Make a class to represent the data you want:
public class ResourceAction
{
public int Id {get;set;}
public string Name {get; set; }
}
Select a list of those instead:
List<ResourceAction> actions = resourceActions.Actions
.Select(s => new ResourceAction() { Id = s.Id, Name = s.ActionName}).ToList();
I believe this is what your looking for. However you need to change the output to an anonymous type.
var actions = resourceActions.Actions.Select(s => new { s.ActionName, s.ActionId, s.IsActive } ).ToList();
You can use a anonymous type for this, for example
var actions = resourceActions.Actions.Select(s =>
new { Id = s.Id, Name = s.ActionName, Active = s.IsActive).ToList();
but a better way would be to create a class like
public class ActionWithId
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
}
List<ActionWithId> actions = resourceActions.Actions.Select(s =>
new ActionWithId() { Id = s.Id, Name = s.ActionName, Active = s.IsActive }).ToList();

Entity Framework, MVC 3, OrderBy in LINQ To Entities

I've got the following query:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series
.Select(c => c.Comics
.Select(col => col.Collection)))
.SingleOrDefault();
This works great, although I now need to order the Comics by a property called 'ReadingOrder'.
I've tried:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series.Select(c => c.Comics.OrderBy(o => o.ReadingOrder)
.Select(col => col.Collection)))
.SingleOrDefault();
But this results in the following error:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path
Any ideas what this error means?
Thanks in advance
EDIT:
My models:
public class Page
{
public int PageId { get; set; }
public string Title { get; set; }
public ICollection<Series> Series { get; set; }
}
public class Series
{
public int SeriesId { get; set; }
public int PageId { get; set; }
public string Title { get; set; }
public Page Page { get; set; }
public ICollection<Comic> Comics { get; set; }
}
public class Comic
{
public int ComicId { get; set; }
public string Title { get; set; }
public int ReadingOrder { get; set; }
public string Subtitle { get; set; }
public int CollectionId { get; set; }
public Collection Collection { get; set; }
}
public class Collection
{
public int CollectionId { get; set; }
public string Title { get; set; }
public ICollection<Comic> Comics { get; set; }
}
The exception "...Include path expression must refer to a navigation property..." basically complains that c.Comics.OrderBy is not a navigation property. (It's a legitimate complaint, I think.)
Actually it's not supported by EF to apply sorting (and also filtering) in eager loading statements (Include).
So, what can you do?
Option 1:
Sort in memory after you have loaded the entity:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series.Select(c => c.Comics
.Select(col => col.Collection)))
.SingleOrDefault();
if (model.Page != null)
{
foreach (var series in model.Page.Series)
series.Comics = series.Comics.OrderBy(c => c.ReadingOrder).ToList();
}
Ugly, but because you are loading apparently only a single Page object by id it's possibly faster (LINQ to Objects in memory) than the following options (if Series and Comics collections are not extraordinarily long).
Option 2:
Break down the query in parts which mix eager and explicite loading:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series) // only Series collection is included
.SingleOrDefault();
if (model.Page != null)
{
foreach (var series in model.Page.Series)
db.Entry(series).Collection(s => s.Comics).Query()
.Include(c => c.Collection)
.OrderBy(c => c.ReadingOrder)
.Load(); // one new DB query for each series in loop
}
Option 3:
Projection?
Here and here is by the way something about the dangers of complex Include chains of multiple navigation properties. It can load huge amounts of duplicated data. Include ensures that you only have one DB roundtrip but possibly at the cost of much more transfered data. Explicite loading has multiple roundtrips but with possibly less data in total.
(I know, I gave you this Include...Select...Select...Select... chain, but how could I know that you would take me serious :). Well, depending on the size of your nested collections it can still be the best option.)
Off the top of my head, untested:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series
.Select(c => c.Comics
.Select(col => col.Collection)
.OrderBy(o => o.ReadingOrder)))
.SingleOrDefault();

Resources