LINQ error: System.NotSupportedException on GroupBy Selection - c#

When var items = q3.ToList(); executes from the code snippet below, it throws exception System.NotSupportedException. The aim is to get the list of items after the grouping.
Exception:
Unable to create a constant value of type 'AppDB.Stage.Rules'. Only primitive types or enumeration types are supported in this context.
var valuations = context.stage
.Where(q => q.stageID == stageID && !rules.ToList().Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
var q3 = valuations.Select(y => new StageType
{
TypeKey = y.Key,
TypeName= "UNKNOWN",
});
var items = q3.ToList(); //error here

Your database doesn't have any idea of what your in-memory rules actually is, and in-turn cant convert this statement to SQL
The simplest solution will be to leave it as an IQueryable and don't use ToList,
context.stage
.Where(q => q.stageID == stageID && !rules.Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
However, if it is already in memory, then you will have to send the values as a primitive list
var type1s = rules.Select(x => x.type1);
var type2s = rules.Select(x => x.type2);
context.stage
.Where(q => q.stageID == stageID && !type1s.Contains(q.type1) && !type2s.Contains(q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;

Because rules.ToList() makes results in memory, you can't use it inside an IQueryable that executes over SQL. You should first bring your data into memory and then narrow it by other in-memory object.
var valuations = context.stage.ToList()
.Where(q => q.stageID == stageID && !rules.ToList().Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
var q3 = valuations.Select(y => new StageType
{
TypeKey = y.Key,
TypeName= "UNKNOWN",
});
var items = q3.ToList();

Related

How to iterate over a list to build a Linq query

I have the following working query:
posts.Where(post =>
post.Fields
.Where(x =>
x.RegionId == "RecipeArticleDetails" &&
(x.FieldId == "RecipePrepTime" || x.FieldId == "RecipeCookTime")
)
.GroupBy(x => x.PostId)
.Select(x => new { ID = x.Key, Value = x.Sum(y => Convert.ToInt32(y.Value)) })
.Where(x => x.Value > 10 && x.Value < 40)
.Any()
)
List<string> suppliedTimes = new List<string>(){
"10-60","0-10"
};
I would like to replace Where(x => x.Value > 10 && x.Value < 40) so it looks up from a list of ranges:
List<string> suppliedTimes = new List<string>(){
"10-60","0-10"
};
My understanding is I can use select to iterate over the items:
posts.Where(post =>
suppliedTimes.Select(x => new {low = Convert.ToInt32(x.Split("-",StringSplitOptions.RemoveEmptyEntries)[0]), high = Convert.ToInt32(x.Split("-",StringSplitOptions.RemoveEmptyEntries)[1]) })
.Any( a =>
post.Fields
.Where(x =>
x.RegionId == "RecipeArticleDetails" &&
(x.FieldId == "RecipePrepTime" || x.FieldId == "RecipeCookTime")
)
.GroupBy(x => x.PostId)
.Select(x => new { ID = x.Key, Value = x.Sum(y => Convert.ToInt32(y.Value)) })
.Where(x => x.Value > a.low && x.Value < a.high)
.Any()
)
)
However this code results in the error:
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Please can someone explain how I can achieve this and why what I have isn't working.
To make it work with EF Core I would suggest my extnsion FilterByItems and change the way how to retrieve records.
List<string> suppliedTimes = new List<string>(){
"10-60","0-10"
};
var ranges = suppliedTimes
.Select(x => x.Split("-", StringSplitOptions.RemoveEmptyEntries))
.Select(x => new {
low = Convert.ToInt32(x[0]),
high = Convert.ToInt32(x[1])
});
var fields = context.Fields
.Where(x =>
x.RegionId == "RecipeArticleDetails" &&
(x.FieldId == "RecipePrepTime" || x.FieldId == "RecipeCookTime")
)
.GroupBy(x => x.PostId)
.Select(x => new { ID = x.Key, Value = x.Sum(y => Convert.ToInt32(y.Value)) })
.FilterByItems(ranges, (e, r) => e.Value > r.low && e.Value < r.high, true);
var posts = posts
.Join(fields, p => p.Id, f => f.ID, (p, f) => p);

Is there a way to set this entity/linq query to IEnumerable?

I'm trying to return an IEnumerable activities instead of "var"
var activities = ctx.Activities.Where(a => a.SiteID == propID)
.Where(a => a.ActivityTypeName == "Call")
.Select(x => new
{
x.DateTimeEntry,
x.Contact.OwnerContact.ParcelDatas
.FirstOrDefault(a => a.OwnerContactID == x.Contact.OwnerContact.OOwnerID)
.Parcel_LetterTracking.LMailDate,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID)
.FAQ_Library.FaqNum,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID)
.FAQ_Library.Question
});
edit: data type Object compiles but I'm not sure if that's right.
.Select already returns a an IEnumerable<TResult> also combine your ..where() clauses with && instead. https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=netframework-4.8 also one other thing you can do is use .AsEnuemerable()
var activities = ctx.Activities.Where(a => a.SiteID == propID && a.ActivityTypeName == "Call")
.Select(x => new
{
x.DateTimeEntry,
x.Contact.OwnerContact.ParcelDatas.FirstOrDefault(a => a.OwnerContactID == x.Contact.OwnerContact.OOwnerID).Parcel_LetterTracking.LMailDate,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID).FAQ_Library.FaqNum,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID).FAQ_Library.Question
}).AsEnumerable();

Two dbcontexts cause an error "value cannot be null. parameter name entitytype"

I have 2 DbContexts in my application, and need to do a join in 2 tables that are each one in a different DbContext; I get this error
value cannot be null. parameter name entitytype
When I try to join 2 tables of the same context, this error does not happen.
var VerificaExistenciaSinistro = sinistroContext.SnsAviso
.Join(
sinistroContext.SnsNumAviso,
sinistro => sinistro.NumApo,
aviso => aviso.NumApo,
(sinistro, aviso) => new {
sinistroV = sinistro,
avisoV = aviso })
.Where(c => c.sinistroV.CodItm == c.avisoV.CodItm &&
c.sinistroV.NumApo == c.avisoV.NumApo &&
c.sinistroV.NumAvs == c.avisoV.SeqNumAvs)
.Join(sgsContext.EmsEmissao,
sinistro1 => sinistro1.sinistroV.CodCtrtAvs,
emissao => emissao.CodCtrt,
(sinistro1, emissao) => new {
sinistroC = sinistro1,
emissaoC = emissao })
.Where(c => c.sinistroC.sinistroV.CodCtrtAvs == c.emissaoC.CodCtrt &&
c.emissaoC.CodEmis == c.sinistroC.avisoV.CodEms)
.Where(x => x.sinistroC.sinistroV.NumApo == apolice &&
x.emissaoC.StsEmis == emissao &&
x.emissaoC.NumEndosso ==endosso &&
x.sinistroC.sinistroV.CodItm == cod_itm &&
x.sinistroC.sinistroV.CodCbe == cbeCod)
.Select(x => x.sinistroC)
.ToList();
It generally occurs when u query data from two dbcontext using IQueryable
I encountered the same issue when I was using two dbContext. The solution which I found was. If u are using join between two tables then load any one table's data first to a variable/Object so that It becomes IEnumerable or IList and then do join with that variable/Object to the another table of another dbContext
Example
var FirstDbContextTable = sinistroContext.SnsAviso
.Join(
sinistroContext.SnsNumAviso,
sinistro => sinistro.NumApo,
aviso => aviso.NumApo,
(sinistro, aviso) => new {
sinistroV = sinistro,
avisoV = aviso })
.Where(c => c.sinistroV.CodItm == c.avisoV.CodItm &&
c.sinistroV.NumApo == c.avisoV.NumApo &&
c.sinistroV.NumAvs == c.avisoV.SeqNumAvs).ToList();
var result=FirstDbContextTable.Join(sgsContext.EmsEmissao,
sinistro1 => sinistro1.sinistroV.CodCtrtAvs,
emissao => emissao.CodCtrt,
(sinistro1, emissao) => new {
sinistroC = sinistro1,
emissaoC = emissao })
.Where(c => c.sinistroC.sinistroV.CodCtrtAvs == c.emissaoC.CodCtrt &&
c.emissaoC.CodEmis == c.sinistroC.avisoV.CodEms)
.Where(x => x.sinistroC.sinistroV.NumApo == apolice &&
x.emissaoC.StsEmis == emissao &&
x.emissaoC.NumEndosso ==endosso &&
x.sinistroC.sinistroV.CodItm == cod_itm &&
x.sinistroC.sinistroV.CodCbe == cbeCod)
.Select(x => x.sinistroC)
.ToList();

I'm getting an error when trying to join against multiple tables in a query

I getting this error when I join:
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The specified LINQ expression contains references to queries that are associated with different contexts.
var rightsList = RoleRightService.GetRoleRights<RoleRight>().Where(x => x.RoleCode == role && x.CompanyId == USER_OBJECT.CompanyId).AsEnumerable();
var securables = SecurableServices.GetSecurable<Securable>()
.GroupBy(a => new { a.RegistrationType_LookUpId })
.Select(r => new
{
id = r.Select(x => x.SecurableID),
registrationType = r.Key.RegistrationType_LookUpId,
RegistrationTypeName = r.Select(x => x.RegistrationType.LookUpDescription).Distinct().FirstOrDefault(),
IsChecked = false,
pageList = r.GroupBy(b => new { b.PageID })
.Select(p => new SecurableViewModel
{
Id = p.Where(x => x.PageID == p.Key.PageID && x.Type == 1).Select(x => x.SecurableID).FirstOrDefault(),
PageId = p.Where(x => x.PageID == p.Key.PageID && x.Type == 1).Select(x => x.PageID).FirstOrDefault(),
PageName = p.Where(x => x.PageID == p.Key.PageID && x.Type == 1).Select(x => x.PageDescription).FirstOrDefault(),// && rr.AccessRight !=0
IsChecked = rightsList.Where(rr => rr.SecurableID == (p.Where(x => x.PageID == p.Key.PageID && x.Type == 1).Select(x => x.SecurableID).FirstOrDefault())).Count() > 0,
operationList = r.Where(x => x.PageID == p.Key.PageID && x.Type == 2)
.Select(o => new RoleRightViewModel
{
Id = o.SecurableID,
OperationID = o.OperationID,
OperationName = o.OperationDescription,
IsChecked = rightsList.Where(rr => rr.SecurableID == o.SecurableID).Count() > 0,
})
.ToList()
}).ToList()
}).ToList();
I am getting error
The specified LINQ expression contains references to queries that are associated with different contexts.
For this line:
IsChecked = rightsList.Where(rr => rr.SecurableID == (p.Where(x => x.PageID == p.Key.PageID && x.Type == 1).Select(x => x.SecurableID).FirstOrDefault())).Count() > 0,
is there possibilty to right delegate for this
It looks like you are using multiple EF entity contexts, possibly to query more than one database. EF is not able to perform a linq to entities query across more than one EF context.
In order to execute this query without error is will be necessary to use linq to objects instead by projecting the data from each context into memory before combining them. Please note this may have a negative performance impact since all objects will need to be fetched into memory before being filtered down.
Try adding a .ToList() between your GroupBy and Select statements:
var securables = SecurableServices.GetSecurable<Securable>()
.GroupBy(a => new { a.RegistrationType_LookUpId })
.ToList()
.Select(r => new
...

Add correlated subqueries to Select statement using QueryOver

I'm attempting to convert a SQL statement to use QueryOver (in hopes of pre-fetching the entities part of the response) but I'm having trouble figuring out how to add a correlated subquery to the Select statement (all the examples I found have only shown using a subquery in the Where clause).
This is the query I'm trying to convert:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
var projectWhereClause = project != null ? "AND f1.project_id = " + project.Id : "";
var query = Session.CreateSQLQuery(string.Format(#"
SELECT
ft.id as FEEDBACK_TYPE_ID,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0}) as ALL_FEEDBACK_COUNT,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0} AND feedback_status_id = {1}) as PENDING_FEEDBACK_COUNT
FROM feedback f
RIGHT JOIN feedback_type ft on f.feedback_type_id = ft.id WHERE ft.RESTRICTED_IND = 0
GROUP BY ft.id, ft.sort_order
ORDER BY ft.sort_order",
projectWhereClause,
pendingFeedbackStatus.Id
))
.SetResultTransformer(Transformers.AliasToEntityMap);
var results = query.List<IDictionary>();
return results.Select(r =>
new FeedbackTypeSummary
{
Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])),
AllFeedbackCount = Convert.ToInt32(r["ALL_FEEDBACK_COUNT"]),
PendingFeedbackCount = Convert.ToInt32(r["PENDING_FEEDBACK_COUNT"])
}).ToList();
and here is what I have so far (which is mostly everything minus the correlated subqueries and some additional filtering added to the subqueries):
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
var foo = Session.QueryOver<Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
// TODO: Add correlated subqueries here?
.SelectGroup(() => feedbackTypeAlias.Id)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc;
var test = foo.List<object[]>();
I'd also like to find a way to return a full FeedbackType entity of from the statement, instead of returning feedbackTypeAlias.Id and then having to perform Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])) in a loop as I do in the original.
I felt like I looked for this 10 times, but I overlooked the .SelectSubQuery() method which provided the desired correlated subqueries. This answer tipped me off - https://stackoverflow.com/a/8143684/191902.
Here is the full QueryOvery version:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending").SingleOrDefault();
Domain.Feedback.Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
if (project != null)
{
allFeedback.Where(f => f.Project.Id == project.Id);
pendingFeedback.Where(f => f.Project.Id == project.Id);
}
FeedbackTypeSummary result = null;
var query = Session.QueryOver<Domain.Feedback.Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
.SelectSubQuery(allFeedback.ToRowCountQuery()).WithAlias(() => result.AllFeedbackCount)
.SelectSubQuery(pendingFeedback.ToRowCountQuery()).WithAlias(() => result.PendingFeedbackCount)
.SelectGroup(() => feedbackTypeAlias.Id).WithAlias(() => result.TypeId)
.SelectGroup(() => feedbackTypeAlias.Name).WithAlias(() => result.TypeName)
.SelectGroup(() => feedbackTypeAlias.NamePlural).WithAlias(() => result.TypeNamePlural)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc
.TransformUsing(Transformers.AliasToBean<FeedbackTypeSummary>());
var results = query.List<FeedbackTypeSummary>();
return results;
I also was able to populate my FeedbackTypeSummary DTO from a single query, although I couldn't find a way to alias an entity and ended up extracting a few of the needed properties from FeedbackType into FeedackTypeSummary (which is probably a better thing to do anyways).

Categories