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.
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);
}
More a general question, but how can I write LINQ Lambda expressions such that they will return a default string or simply an empty string if the LINQ expression fails or returns nothing. In XSLT XPath if a match fails then one just got nothing, and the application did not crash whereas in LINQ one seems to get exceptions.
I use First() and have tried FirstOrDefault().
So example queries may be:
Customers.First(c=>c.id==CustId).Tasks.ToList();
or
Customers.Where(c=>c.id==CustId).ToList();
or
Model.myCustomers.Where(c=>c.id==CustId);
etc.
Whatever the query, if it returns no records or null, then is there a general approach to ensure the query fails gracefully?
Thanks.
There isn't anything elegant built into C# for propagating nulls when you access properties. You could create your own extension methods:
public static class Extensions
{
public static TValue SafeGet<TObject, TValue>(
this TObject obj,
Func<TObject, TValue> propertyAccessor)
{
return obj == null ? default(TValue) : propertyAccessor(obj);
}
public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> collection)
{
return collection ?? Enumerable.Empty<T>();
}
}
Used like this:
Customers.FirstOrDefault(c => c.id==CustId).SafeGet(c => c.Tasks).OrEmpty().ToList();
Customers.First(c=>c.id==CustId) will crash if there is no matching record.
There are few ways you can try to find it, if you use FirstOrDefault that'll return NULL if no match is found and you can check for NULL.
Or, you can use the .Any syntax which checks if you have any record and returns boolean.
The only query I would expect to throw an exception would be the first one (assuming that Customers is a valid collection and not null itself):
Customers.First(c=>c.id==CustId).Tasks.ToList();
This will throw an exception if there is no customer with an id of CustId (you have some casing issues with your property and variable names).
If you don't wish to throw an exception on no match, then use FirstOrDefault as you mention, and do a null check, e.g:
var customer = Customers.FirstOrDefault(c => c.id == CustId);
if (customer == null)
{
// deal with no match
return;
}
var taskList = customer.Tasks.ToList();
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 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)
)));
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.