LinQ - Error when trying to order by child property - linq

I'm trying to get a List of object that includes another list of childs with LinQ.
I need to sort this object by parent order and then by childs order.
This is my code:
public async Task<IEnumerable<Menus>> GetMenus(string modulo)
{
var result = this.context.Set<Menus>()
.Include(det => det.MenusSub)
.Where(e => e.Modulo.Equals(modulo))
.OrderBy(s => s.Orden).ThenBy(s => s.MenusSub.OrderBy(p => p.Orden));
return await result.ToListAsync();
}
The problem comes from the thenBy, because when executed this query return this error:
"Message": "Failed to compare two elements in the array.",
"StackTrace": " at System.Collections.Generic.GenericArraySortHelper1.Sort(T[] keys, Int32 index, Int32 length, IComparer1 comparer)\r\n at System.Array.Sort[T](T[] array, Int32 index, Int32 length, IComparer1 comparer)\r\n at System.Linq.EnumerableSorter1.Sort(TElement[] elements, Int32 count)\r\n at System.Linq.OrderedEnumerable1.GetEnumerator()+MoveNext()\r\n at System.Linq.OrderedAsyncEnumerable2.MoveNextCore(CancellationToken cancellationToken)\r\n at System.Linq.AsyncEnumerable.AsyncIterator1.MoveNext(CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.AsyncSelectEnumerable2.AsyncSelectEnumerator.MoveNext(CancellationToken cancellationToken)\r\n at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator2.MoveNextCore(CancellationToken cancellationToken)\r\n at System.Linq.AsyncEnumerable.AsyncIterator1.MoveNext(CancellationToken cancellationToken)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)\r\n at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable1 source, TAccumulate seed, Func3 accumulator, Func2 resultSelector, CancellationToken cancellationToken)\r\n at Ohmio.Data.MenusRepository.GetMenus(String modulo) in C:\Users\ericp\Documents\Mis Proyectos\Ohmio WEB\ohmio-web-server\OhmioData\Repositorios\MenusRepository.cs:line 42\r\n at Ohmio.Servicios.MenusService.GetMenus(String modulo) in C:\Users\ericp\Documents\Mis Proyectos\Ohmio WEB\ohmio-web-server\OhmioServicios\MenusService.cs:line 26\r\n at Ohmio.Api.Controladores.MenusController.GetMenus(String modulo) in C:\Users\ericp\Documents\Mis Proyectos\Ohmio WEB\ohmio-web-server\OhmioWEBAPINetCore\Controladores\MenusController.cs:line 26\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()"
If I remove the thenBy statement, everything works fine.
Any ideas? Thanks!

I believe the problem is that s.MenusSub.OrderBy(p => p.Orden) is returning a collection of objects. Since your custom object probably doesn't implement IComparable and IComparable<T> it's throwing the exception you see, which makes sense because it doesn't know how to compare two instances of your Menus type. None of the collection types in .Net implement the IComparable interface either.
Instead, you'll need to remove the ThenBy and instead use a projection in your return statement. Like this:
return await result.Select(r =>
new Menus
{
// Assign Menus properties
MenusSub = r.MenusSub.OrderBy(p => p.Orden)
}).ToListAsync();
Or you can load the collection into memory with the parent sorted, then return the result with the children sorted:
foreach (var menu in result)
{
menu.MenusSub = menu.MenusSub.OrderBy(p => p.Orden);
}

Related

Linq query where there's a certain desired relationship between items in the result

A linq query Where clause can apply a func to an item in the original set and return a bool to include or not include the item based on the item's characteristics. Great stuff:
var q = myColl.Where(o => o.EffectiveDate = LastThursday);
But what if I want to find a set of items where each item is related to the last item in some way? Like:
var q = myColl.Where(o => o.EffectiveDate = thePreviousItem.ExpirationDate);
How do you make a Where (or other linq function) "jump out" of the current item?
Here's what I tried, trying to be clever. I made every item an array just so I can use the Aggregate function:
public IQueryable<T> CurrentVersions
{
get => AllVersions
.Select(vo => new T[] { vo })
.Aggregate((voa1, voa2) => voa1[0].BusinessExpirationDate.Value == voa2[0].BusinessEffectiveDate.Value ? voa1.Concat(voa2).ToArray() : voa1)
.SelectMany(vo => vo);
}
but that doesn't compile on the SelectMany:
The type arguments for method Enumerable.SelectMany<TSource,
TResult>(IEnumerable<TSource>, Func<TSource, IEnumerable<TResult>>)
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
EDIT (SOLUTION)
As it turns out, I was on the right track, but was just confused about what SelectMany does. I didn't need it. I also needed to change IQueryable to IEnumerable because I'm using EF and you can't query after you let go of the DbContext. So, here is the actual solution.
public IEnumerable<T> CurrentVersions
{
get => AllVersions
.Select(vo => new T[] { vo })
.Aggregate((voa1, voa2) => voa1[0].BusinessExpirationDate.Value == voa2[0].BusinessEffectiveDate.Value ? voa1.Concat(voa2).ToArray() : voa1);
}
Linq queries are most effective when each item is processed in isolation. It doesn't work well when trying to relate items within the same collection, without having to process the same collection multiple times and standard linq operators.
The MoreLINQ library helps provide additional operators to fill in some of those gaps. I'm not sure what operators it provides that could be used in this instance, but I know it has a Pairwise() method that combines the current and previous items in the iteration.
In general, for situations like this, if you needed to roll out your own, it would be far easier to write it using a generator to generate your sequence. Either as a general purpose extension method:
public static IEnumerable<TSource> WhereWithPrevious<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, bool> predicate)
{
using (var iter = source.GetEnumerator())
{
if (!iter.MoveNext())
yield break;
var previous = iter.Current;
while (iter.MoveNext())
{
var current = iter.Current;
if (predicate(current, previous))
yield return current;
}
}
}
or one specifically for the problem you're trying to solve.
public static IEnumerable<MyType> GetVersions(IEnumerable<MyType> source)
{
using (var iter = source.GetEnumerator())
{
if (!iter.MoveNext())
yield break;
var previous = iter.Current;
while (iter.MoveNext())
{
var current = iter.Current;
if (current.EffectiveDate == previous.ExpirationDate)
yield return current;
}
}
}
An alternative approach which while standard practice in other languages but terribly inefficient here would be to zip the collection with itself offset by one.
var query = Collection.Skip(1).Zip(Collection, (c, p) => (current:c,previous:p))
.Where(x => x.current.EffectiveDate == x.previous.ExpirationDate)
...;
And with all of that said, using any of these options will most likely make your query incompatible with query providers. It's not something you would want expressed as a single query anyway.

Convert Linq query to List without using generic parameter

If I have an unevaluated query:
var q = (my linq query);
That happens to return a type of DbSet<T> and I need it to resolve to a List<T> because I'm about to lose the context within which it was created.
Normally I would just say q.Cast<T>.ToList() but at this place in the code I don't have T so how do I do this?
I do have the System.Type of T however. I came up with a work around but I am having to load a new List<T> (created with reflection) with the results of q. I don't like it because it's awkward and I'm loading another list but maybe loading a list is what ToList() does anyway.
Is there a better way?
public static System.Collections.IEnumerable RootCollection(string collectionPropertyName)
{
using (var db = new Model1())
{
var col = GetRootCollection(db, collectionPropertyName);
System.Type generic = typeof(List<>);
System.Type constructed = generic.MakeGenericType(col.GetType().GetGenericArguments().First());
var list = constructed.GetConstructor(new Type[] {col.GetType() }).Invoke(new object[] { col });
return (System.Collections.IEnumerable)list;
}
}

NHibernate LINQ queries with nested .Any() conditions

Here are what my classes look like:
class User
{
IList<UserRedemptionCode> UserRedemptions;
}
class UserRedemptionCode
{
string Code;
}
class Course
{
IList<CourseRedemptionCode> CourseRedemptions;
}
class CourseRedemptionCode
{
string Code;
string SomeData;
}
I want to retrieve all of the courses that match a redemption code that is associated with a user. Here is the current IQuerable call I am making:
IQueryable<Course> query = Courses.Where(
course => course.CourseRedemptions.Any(
cr => user.UserRedemptions.Any(rc => cr.Code == rc.Code));
However, this doesn't seem to work with NHibernate. I get this exception:
System.NotSupportedExceptionSpecified method is not supported.
at NHibernate.Hql.Ast.ANTLR.PolymorphicQuerySourceDetector.GetClassName(IASTNode querySource)
at NHibernate.Hql.Ast.ANTLR.PolymorphicQuerySourceDetector.Process(IASTNode tree)
at NHibernate.Hql.Ast.ANTLR.AstPolymorphicProcessor.Process()
at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(IASTNode ast, String queryIdentifier, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
at NHibernate.Hql.Ast.ANTLR.ASTQueryTranslatorFactory.CreateQueryTranslators(String queryIdentifier, IQueryExpression queryExpression, String collectionRole, Boolean shallow, IDictionary`2 filters, ISessionFactoryImplementor factory)
at NHibernate.Engine.Query.QueryPlanCache.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow, IDictionary`2 enabledFilters)
at NHibernate.Impl.AbstractSessionImpl.GetHQLQueryPlan(IQueryExpression queryExpression, Boolean shallow)
at NHibernate.Impl.AbstractSessionImpl.CreateQuery(IQueryExpression queryExpression)
at NHibernate.Linq.DefaultQueryProvider.PrepareQuery(Expression expression, IQuery& query, NhLinqExpression& nhQuery)
at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression)
at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression)
at Remotion.Linq.QueryableBase`1.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Web.Http.Filters.EnumerableEvaluatorFilter.Convert[T](Object input)
at System.Web.Http.Filters.EnumerableEvaluatorFilter.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
at System.Web.Http.Filters.ActionFilterAttribute.CallOnActionExecuted(HttpActionContext actionContext, HttpResponseMessage response, Exception exception)
at System.Web.Http.Filters.ActionFilterAttribute.<>c__DisplayClass4.<System.Web.Http.Filters.IActionFilter.ExecuteActionFilterAsync>b__2(HttpResponseMessage response)
at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass2e`2.<Then>b__2d(Task`1 t)
at System.Threading.Tasks.TaskHelpersExtensions.ThenImpl[TTask,TOuterResult](TTask task, Func`2 continuation, CancellationToken cancellationToken)
There is also another issue, in this query I get back a list of courses, but I also need the CourseRedemptionCode associated with the user so I can get SomeData.
Any ideas? Is there another way to do this query?
There are several issues in NHibernate Jira matching "PolymorphicQuerySourceDetector" or "Any", some recently fixed, but you don't state which NHibernate version you are using.
As alternative, and probably better anyway, try something along these lines:
var codes = user.UserRedemptions.Select(ur => ur.Code).ToList();
var query = (from c in Courses
from cr in c.CourseRedemptions
where codes.Contains(cr.Code)
select new { c, cr });
Note that if multiple codes match, you will get the same course multiple times in the response. You could also remove "cr" from the select statement, and look for the matching code in memory afterwards.
Before you try Oskar's answer, try this because, depending on the mapping it may work in SQL:
Example - replace:
.Where(u => u.Businesses.Any(ub => trainee.Businesses.Any(tb => tb.Id == ub.Id)))
with
.Where(u => u.Businesses.Any(ub => trainee.Businesses.Contains(ub)))
I just tried this and it worked for me. YMMV!
If it does it should be preferred as it could create a more terse SQL statement and pull back less data.

Load parent and its child records in single LINQ statement

I know this could easily be done in two hits to the database, but I've been experimenting with a single LINQ statement to load an order by ID and it's order items, translating them to ViewModel objects at the same time:
var query = from orderLine in db.PurchaseOrderLines
where orderLine.PurchaseOrderId == id
orderby orderLine.Id
group orderLine by orderLine.PurchaseOrder into grouped
select new PurchaseOrderViewModel
{
Id = grouped.Key.Id,
PlacedDateTime = grouped.Key.PlacedDateTime,
Reference = grouped.Key.OrderReference,
StatusId = grouped.Key.PurchaseOrderStatusId,
Status = grouped.Key.PurchaseOrderStatus.Description,
Supplier = grouped.Key.Supplier.Name,
SupplierId = grouped.Key.SupplierId,
OrderLines = grouped.Select(row => new PurchaseOrderLineViewModel
{
Id = row.Id,
PartNumber = row.Product.PartNumber,
ProductDescription = row.Product.Description,
Quantity = row.Quantity
})
};
However, the Type of query is System.Data.Objects.ObjectQuery<...PurchaseOrderViewModel>, and trying to iterate the results throws an exception:
System.InvalidOperationException was unhandled by user code
Message=Sequence contains no elements
Source=System.Core
StackTrace:
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at System.Data.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__3[TResult](IEnumerable`1 sequence)
at System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable`1 query, Expression queryRoot)
at System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[S](Expression expression)
at System.Linq.Queryable.Single[TSource](IQueryable`1 source)
at MyApp.Controllers.PurchaseOrderController.Details(Int32 id) in E:\Code\WCs\MyApp\MyApp\Controllers\PurchaseOrderController.cs:line 69
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
InnerException:
What am I doing wrong? Am I making a mistake to even attempt this?
Many thanks in advance.
You have a few options to force eager loading in LINQ-to-SQL. Without knowing how you're executing your query or whether you're implementing paging and so on, it's hard to say.
The first thing I'd try (still inside your existing query) is simply forcing the OrderLines to a new list when the query is executed, like so:
OrderLines = grouped.Select(row => new PurchaseOrderLineViewModel
{
Id = row.Id,
PartNumber = row.Product.PartNumber,
ProductDescription = row.Product.Description,
Quantity = row.Quantity
}).ToList()
If this works, it's simple.
The alternative is to use the DataLoadOptions class. This is the usual way of implementing eager loading in LINQ-to-SQL.
var loadOptions = new DataLoadOptions();
loadOptions.LoadWith<PurchaseOrder>(p => p.PurchaseOrderLines);
db.LoadOptions = loadOptions;
The only catch is that you need to set the LoadOptions property of your datacontext before using the context. This shouldn't be a problem if you're using the context in the recommended manner (short term, instance per operation), but that usage pattern doesn't seem to be as prevalent as intended. If you create a datacontext and pass it around or hang on to it, then this might cause you problems.

Generics and Database Access

I have the following method I can pass in a lambda expression to filter my result and then a callback method that will work on the list of results. This is just one particular table in my system, I will use this construct over and over. How can I build out a generic method, say DBget that takes a Table as a parameter(An ADO.NET dataservice entity to be fair) and pass in a filter (a lambda experssion).
public void getServiceDevelopmentPlan(Expression<Func<tblServiceDevelopmentPlan, bool>> filter, Action<List<tblServiceDevelopmentPlan>> callback)
{
var query = from employerSector in sdContext.tblServiceDevelopmentPlan.Where(filter)
select employerSector;
var DSQuery = (DataServiceQuery<tblServiceDevelopmentPlan>)query;
DSQuery.BeginExecute(result =>
{
callback(DSQuery.EndExecute(result).ToList<tblServiceDevelopmentPlan>());
}, null);
}
My first bash at this is:
public delegate Action<List<Table>> DBAccess<Table>(Expression<Func<Table, bool>> filter);
If you are using Linq to Ado.NET Dataservices or WCF Dataservices, your model will build you a lot of typed. Generally though you will be selecting and filtering. You need the following, then all your methods are just candy over the top of this:
Query Type 1 - One Filter, returns a list:
public void makeQuery<T>(string entity, Expression<Func<T, bool>> filter, Action<List<T>> callback)
{
IQueryable<T> query = plussContext.CreateQuery<T>(entity).Where(filter);
var DSQuery = (DataServiceQuery<T>)query;
DSQuery.BeginExecute(result =>
{
callback(DSQuery.EndExecute(result).ToList<T>());
}, null);
}
Query Type 2 - One Filter, returns a single entity:
public void makeQuery(string entity, Expression> filter, Action callback)
{
IQueryable<T> query = plussContext.CreateQuery<T>(entity).Where(filter);
var DSQuery = (DataServiceQuery<T>)query;
DSQuery.BeginExecute(result =>
{
callback(DSQuery.EndExecute(result).First<T>());
}, null);
}
What you need to do is overload these and swap out the filter for a simple array of filters
Expression<Func<T, bool>>[] filter
And repeat for single and list returns.
Bundle this into a singleton if you want one datacontext, or keep track of an array of contexts in some sort of hybrid factory/singleton and you are away. Let the constructor take a context or if non are supplied then use its own and you are away.
I then use this on a big line but all in one place:
GenericQuery.Instance.Create().makeQuery<tblAgencyBranches>("tblAgencyBranches", f => f.tblAgencies.agencyID == _agency.agencyID, res => { AgenciesBranch.ItemsSource = res; });
This may look complicated but it hides a lot of async magic, and in certain instances can be called straight from the button handlers. Not so much a 3 tier system, but a huge time saver.

Resources