I currently have code which produces the following LINQ expression (taken from the WhoCanHelpMe showcase project). Its purpose is to bind together two expressions but I don't know if the following is actually a valid expression:
.Where(p => (p.PostCodes
.Any(pc =>(pc = value(PatchByPostCodeSpecification).postCode)) &&
Invoke(p => p.Teams
.Any(t => (Convert(t.TeamType) =
Convert(value(PatchByBookingTypeSpecification).bookingType))),
p
)
));
When the expression is evaluated I get an Object reference not set to an instance of an object exception with the following stack trace:
at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetEntityName(ICriteria subcriteria, String propertyName)
at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetColumns(String propertyName, ICriteria subcriteria)
at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetColumnsUsingProjection(ICriteria subcriteria, String propertyName)
at NHibernate.Criterion.CriterionUtil.GetColumnNamesUsingPropertyName(ICriteriaQuery criteriaQuery, ICriteria criteria, String propertyName, Object value, ICriterion critertion)
at NHibernate.Criterion.CriterionUtil.GetColumnNamesForSimpleExpression(String propertyName, IProjection projection, ICriteriaQuery criteriaQuery, ICriteria criteria, IDictionary2 enabledFilters, ICriterion criterion, Object value)
at NHibernate.Criterion.SimpleExpression.ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary2 enabledFilters)
at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetWhereCondition(IDictionary2 enabledFilters)
at NHibernate.Loader.Criteria.CriteriaJoinWalker..ctor(IOuterJoinLoadable persister, CriteriaQueryTranslator translator, ISessionFactoryImplementor factory, ICriteria criteria, String rootEntityName, IDictionary2 enabledFilters)
at NHibernate.Criterion.SubqueryExpression.ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary2 enabledFilters)
at NHibernate.Criterion.Junction.ToSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary2 enabledFilters)
at NHibernate.Loader.Criteria.CriteriaQueryTranslator.GetWhereCondition(IDictionary2 enabledFilters)
at NHibernate.Loader.Criteria.CriteriaJoinWalker..ctor(IOuterJoinLoadable persister, CriteriaQueryTranslator translator, ISessionFactoryImplementor factory, ICriteria criteria, String rootEntityName, IDictionary2 enabledFilters)
at NHibernate.Loader.Criteria.CriteriaLoader..ctor(IOuterJoinLoadable persister, ISessionFactoryImplementor factory, CriteriaImpl rootCriteria, String rootEntityName, IDictionary2 enabledFilters)
at NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results)
at NHibernate.Impl.CriteriaImpl.List(IList results)
at NHibernate.Impl.CriteriaImpl.List()
at NHibernate.Linq.CriteriaResultReader1.List()
at NHibernate.Linq.CriteriaResultReader1.<GetEnumerator>d__0.MoveNext()
at System.Collections.Generic.List1..ctor(IEnumerable1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
at Environment.Core.Specifications.QuerySpecification2.SatisfyingElementsFrom(IQueryable1 candidates) in C:\DEV\Environment\Environment\app\Environment.Core\Specifications\QuerySpecification.cs:line 30
at Environment.Data.NHibernate.LinqRepository1.FindAll(ILinqSpecification2 specification) in C:\DEV\Environment\Environment\app\Environment.Data\NHibernate\LinqRepository.cs:line 43
........
UPDATE
I've tried running the query without using a complex expression:
.Where(p => (p.PostCodes
.Any(pc =>
(pc = value(PatchByPostCodeSpecification).postCode)
)));
The same error still occurred.
You are not using linq to objects. NHibernate.Linq is a Linq provider.
That means - it needs to know how to translate Your predicate into valid SQL.
Now ask Yourself a question - how on earth all databases nhibernate supports could ever know about .net type conversion?
Sorry I think I'll need a bit more of a pointer than that. Would a more complete code sample help?
As I see it - You are trying to accomplish impossible. I can't provide code sample that would solve Your problem cause I got no ideas what Your actual aim is. all I can see is that You are using technology wrong.
NHibernate.Linq is able to translate into sql expressions like
orders.Any(o=>o.Customers.Any(c=>c.IsDead)).Where(o=>o.Price==10)
But it's not able to translate into sql expressions like
orders.Where(o=>{Console.WriteLine("foo"); MsgBox("bar"); return false;})
This was caused because the expression was comparing two PostCode objects rather than comparing on properties. I changed the expression so that the following is produced:
.Where(p => (p.PostCodes
.Any(pc =>
(pc.Name = value(PatchByPostCodeSpecification).postCode.Name)
)));
Related
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);
}
I am getting the following error when I ultimately try to run the query
Unknown LINQ expression of type 'IsFalse'
this is the code
private static IQueryable<T> QueryMethod<T>(
IQueryable<T> query,
QueryableRequestMessage.WhereClause.Rule rule,
Type type,
string methodName,
Expression property,
Expression value,
string op,
ParameterExpression parameter
) where T : class
{
var methodInfo = type.GetMethod(methodName, new[] { type });
var call = Expression.Call(property, methodInfo, value);
var expression = rule.Op.Equals(op)
? Expression.Lambda<Func<T, bool>>(call, parameter)
: Expression.Lambda<Func<T, bool>>(Expression.IsFalse(call), parameter);
query = query.Where(expression);
return query;
}
The important variables have the following values
query: an IQueryable that I am building up
type: String
methodName: "EndsWith"
rule.Op: "ne" //Not Ends With
op: "ew"
value: "somestring"
Basically, if op and rule.Op are equal, it just runs the methodName (EndsWith) and filters accordingly. However, If they are different, I want to negate the result.
It seems that you are not doing anything wrong; your LINQ provider simply does not know how to deal with the expression tree instance that Expression.IsFalse returns so it complains.
You can try to manually construct the "is false" expression tree yourself, which should work:
Expression.Lambda<Func<T, bool>>(
Expression.Equal(call, Expression.Constant(false)),
parameter)
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.
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.
I write this method:
public List<TResult2> SelectAndJoin<TOuter, TInner, TKey, TResult, TResult2>(IEnumerable<TInner> inner,
System.Linq.Expressions.Expression<Func<Regions, TKey>> outerKeySelector,
System.Linq.Expressions.Expression<Func<TInner, TKey>> innerKeySelector,
System.Linq.Expressions.Expression<Func<Regions, TInner, TResult>> resultSelector,
Func<Regions, TResult2> selector)
{
using (RepositoryDataContext = new DataClasses1DataContext())
{
return RepositoryDataContext.Regions.Join(inner, outerKeySelector, innerKeySelector, resultSelector).AsEnumerable<TResult2>().Select<Regions, TResult2>(selector).ToList<TResult2>();
}
}
but the expression follow return has this Error:
'System.Linq.IQueryable' does not contain a definition for 'AsEnumerable' and the best extension method overload 'System.Linq.Enumerable.AsEnumerable(System.Collections.Generic.IEnumerable)' has some invalid arguments
How I can get rid of this error?
is this code standard?
thanks
You're calling AsEnumerable<TResult2> on enumerable which will be of type IQueryable<TResult>. You should call AsEnumerable<TResult> or you can even omit generic parameter and call AsEnumerable()
Also your next select will not work for the same reason - you are providing wrong types for generics.
I might be missing the point... but:
IQueryable inherits from IEnumerable so I think that AsEnumerable() is unnecessary in this code.
If you really do need/want to execute the query before doing the Select, then you could use ToList() instead of AsEnumerable() - this would be clearer
I'm also wondering if you are including using System.Linq in your .cs file - as AsEnumerable() is an extension method within System.Linq