EF Core ToDictionary throw expression could not be translated - linq

I have this query:
Project = await Context.Projects.Where(x => x.Id == project.Id)
.Select(x => new ProjectModel
{
Id = project.Id,
Name = project.Name,
TreeDataDict = x.Tasks.Select(y => new TreeItemModel()
{
NodeId = y.Id,
ParentId = SetTaskParentId(y, y.Type),
NodeTitle = y.Name,
Expanded = false,
Object = new TaskBaseModel
{
Milestone = y.Milestone,
StartDate = y.StartDate,
CurrentEndDate = y.CurrentEndDate,
EndDate = y.EndDate,
},
Icon = TaskHelper.GetTaskIcon(y.Type),
Children = new List<TreeItemModel>()
}).ToDictionary(y => y.NodeId, y => y)
}).SingleOrDefaultAsync();
and also tried like this:
Project = await Context.Projects.Where(x => x.Id == project.Id)
.Select(x => new ProjectModel
{
Id = project.Id,
Name = project.Name,
TreeDataDict = x.Tasks.ToDictionary(y => y.Id, y => new TreeItemModel(
{
NodeId = y.Id,
ParentId = SetTaskParentId(y, y.Type),
NodeTitle = y.Name,
Expanded = false,
Object = new TaskBaseModel
{
Milestone = y.Milestone,
StartDate = y.StartDate,
CurrentEndDate = y.CurrentEndDate,
EndDate = y.EndDate,
},
Icon = TaskHelper.GetTaskIcon(y.Type),
Children = new List<TreeItemModel>()
})
}).SingleOrDefaultAsync();
Both ways I got this exception:
What could be causing this? and is there a way I could make this work without fetching as a list and then covert it to dictionary? What could be the most efficient way to achieve this?
Regards

What could be causing this?
Translation of the nested ToDictionary call (none of the available overloads) is not supported. Consider it one of the (many) current (latest at the time of writing official v5.0.11) EF Core shortcomings.
Interestingly though, the Dictionary<TKey, TValue> constructor overload with IEnumerable<KeyValuePair<TKey, TValue>> argument as well as projection (Select) to KeyValuePair<TKey, TValue> is supported, which gives the workaround - replace the ToDictionary call with the aforementioned constructor and Select, e.g. (replace int with the type of the Id)
TreeDataDict = new Dictionary<int, TreeItemModel>(
x.Tasks.Select(y => new TreeItemModel()
{
NodeId = y.Id,
ParentId = SetTaskParentId(y, y.Type),
NodeTitle = y.Name,
Expanded = false,
Object = new TaskBaseModel
{
Milestone = y.Milestone,
StartDate = y.StartDate,
CurrentEndDate = y.CurrentEndDate,
EndDate = y.EndDate,
},
Icon = TaskHelper.GetTaskIcon(y.Type),
Children = new List<TreeItemModel>()
})
.Select(e => new KeyValuePair<int, TreeItemModel>(e.NodeId, e)));
This will fix the current error. But note that you are using other non-translatable constructs (custom method calls like SetTaskParentId(y, y.Type) and TaskHelper.GetTaskIcon(y.Type)) which are supported only in the final Select, so make sure to not add LINQ operator like Where, OrderBy etc. after the root query Select(x => new ProjectModel { ... }, otherwise you'll get other runtime errors. ToList, `First

Related

IQueryable.Union/Concat in .net core 3

I want to add a dummy member to an IQueryable and came up with this solution:
IQueryable<Geography> geographies = _unitOfWork.GeographyRepository.GetAll(); //DbSet<Geography>
var dummyGeographies = new Geography[] { new Geography { Id = -1, Name = "All" } }.AsQueryable();
var combinedGeographies = geographies.Union(dummyGeographies);
var test = combinedGeographies.ToList(); //throws runtime exc
But it throws the following exception:
Processing of the LINQ expression 'DbSet
.Union(EnumerableQuery { Geography, })' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
How could I make it work?!
you can only union on data structure which are the same
IQueryable is only applicable if the query expression not been been expressed (ToList) before its run against db and you want the expression modifiable . aka nothing which which is not going to db as a query needs to be IQueryable (simple explanation better to research and understand this yourself)
List<Geography> geographies = _unitOfWork.GeographyRepository
.GetAll() //DbSet<Geography>
.Select(o => new Geography { Id = o.Id, Name = o.Name })
.ToList();
List<Geography> dummyGeographies = new List<Geography>() {
new Geography[] { new Geography { Id = -1, Name = "All" } }
};
var combinedGeographies = geographies.Union(dummyGeographies);
var test = combinedGeographies.ToList();
I was able to achieve it with the following code:
IQueryable<Geography> geographies = _unitOfWork.GeographyRepository.GetAll().Select(o => new Geography { Id = o.Id, Name = o.Name });
IQueryable<Geography> dummyGeographies = _unitOfWork.GeographyRepository.GetAll().Select(o => new Geography { Id = -1, Name = "All" });
var combinedGeographies = geographies.Union(dummyGeographies);

Convert if and foreach statement to select and where in linq

How would I go about changing my if statement and foreach to something cleaner in linq using select and where.
I've tried to make the if statement into a where clause and then use the select query as a replacement for the Foreach loop but that seem to have type issues and wasn't working.
{
StripeConfiguration.ApiKey = _appSettings.StripeSecretKey;
var profile = await _userManager.FindByIdAsync(customerServiceID);
var stripeId = profile.StripeAccountId;
if (stripeId == null)
throw new ArgumentException("No associated Stripe account found.");
List<PaymentMethodDto> result = new List<PaymentMethodDto>();
var options = new PaymentMethodListOptions
{
Customer = stripeId,
Type = "card",
};
var service = new PaymentMethodService();
var payments = await service.ListAsync(options);
if (payments != null && payments.Data?.Count > 0)
{
payments.Data.ForEach((x) =>
{
result.Add(
new PaymentMethodDto
{
Brand = x.Card.Brand,
LastDigits = x.Card.Last4,
StripeToken = x.Id,
CustomerID = x.CustomerId
});
});
}
return result;
}
Just do a regular Select.
List<PaymentMethodDto> result = payments.Data.Select(x => new PaymentMethodDto
{
Brand = x.Card.Brand,
LastDigits = x.Card.Last4,
StripeToken = x.Id,
CustomerID = x.CustomerId
})
.ToList();
If payments.Data has nothing in it, this will give you an empty list, which is what you want.
If payments is null, you'll get an exception, which I think if you think about it really hard is probably what you really want in that case too. Why would .ListAsync() yield a null value?

order by lambda expression with expression trees in C#

My requirement is to order table according to a column in runtime. So I went through the path of parameter expression. I am able to achieve it when all the columns are present in single table. Here is my piece of code:
My requirement is to order table according to the buildStatusOrder during run time:
List<BuildStatusEnum> buildStatusOrder = new List<BuildStatusEnum>
{
BuildStatusEnum.h,
BuildStatusEnum.f,
BuildStatusEnum.a
};
ParameterExpression parameterExpression = Expression.Parameter(typeof(companyQueue));
MemberExpression memberExpression = Expression.PropertyOrField(parameterExpression, "buildStatusID");
MemberExpression valueExpression = Expression.Property(memberExpression, "Value");
Expression orderByExpression = Expression.Constant(buildStatusOrder.Count);
for (int statusIndex = buildStatusOrder.Count - 1; statusIndex >= 0; statusIndex--)
{
ConstantExpression constantExpression = Expression.Constant((int)buildStatusOrder[statusIndex]);
ConstantExpression indexConstantExpression = Expression.Constant(statusIndex);
BinaryExpression equalExpression = Expression.Equal(valueExpression, constantExpression);
orderByExpression = Expression.Condition(equalExpression, indexConstantExpression, orderByExpression);
}
MemberExpression hasValueExpression = Expression.Property(memberExpression, "HasValue");
ConditionalExpression nullCheckExpression = Expression.Condition(hasValueExpression, orderByExpression, Expression.Constant(buildStatusOrder.Count));
var orderByLambda = Expression.Lambda<Func<companyQueue, int>>(nullCheckExpression, parameterExpression);
using (KapowContext context = new KapowContext())
{
var queue = context.companyQueues
.Where(cq => cq.u.s.cq.userID == Utilities.Authentication.UserId)
.Where(cq => buildStatusOrder.Contains((BuildStatusEnum)cq.u.s.cq.buildStatusID))
.OrderBy(o => o.u.se.segmentId)
.ThenBy(orderByLambda)
This works fine. I have to display an additional column. It is from another table. So I joined tables. My lambda expression now looks like this:
var queue = context.companyQueues
.Join(context.abcd, cq => cq.robotID, r => r.robotId, (cq, r) => new { cq, r })
.Join(context.efgh, s => s.r.segmentId, se => se.segmentId, (s, se) => new { s, se })
.Join(context.hijk, u => u.s.cq.userID, us => us.userID, (u, us) => new { u, us })
.Where(cq => cq.u.s.cq.userID == Utilities.Authentication.UserId)
.Where(cq => buildStatusOrder.Contains((BuildStatusEnum)cq.u.s.cq.buildStatusID))
.OrderBy(orderByLambda)
Now my orderbyLambda is not working. I am getting the following error:
'System.Linq.Queryable.ThenBy<TSource,TKey>(System.Linq.IOrderedQueryable<TSource>, System.Linq.Expressions.Expression<System.Func<TSource,TKey>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Conversion of a LINQ query fom method syntax to query syntax

Hi I am changing career to computer programming. I am still in college. I have to change the following LINQ query from method syntax to query syntax. What gets me is the 2 steps process of the method query. First it gets a teamId and then it returns a list based on the context and using the teamId. I am confused about how to translate this to query method. Most of the questions are about going from query syntax to method.
Can someone Help?
public IEnumerable<TemplateView> GetTemplates(Guid userId, int languageId)
{
using (DigigateEntities context = new Models.DigigateEntities())
{
var teamId = context
.TeamMembers
.Include("Team")
.FirstOrDefault(c => c.UserId == userId)
.Team.Id;
return context
.TeamTemplates.Include("Template")
.Where(c => c.TeamId == teamId)
.Select(c => c.Template)
.Where(c => c.StatusId == 1/*Active*/)
.Select(k => new TemplateView
{
TemplateName = k.Name,
Id = k.Id,
IsCustom = k.Custom,
TypeId = k.TypeId,
TypeName = k.TemplateType.Description,
FileName = k.FileName,
TemplateImage = "test.png",
LanguageId = k.LanguageId,
LanguageName = k.Language.Name,
CreateDate = k.CreateDate
}).ToList();
}
}
The first one is pretty straight forward. I delayed the execution of the query until the end. Since you may get a null reference exception in your example accessing .FirstOrDefault().Team.Id;
var teamId = (from c in context.TeamMembers.Include("Team")
where c.UserId == userId
select c.Team.Id).FirstOrDefault();
This one you just need to use an into in order to continue your query statement
return (from c in context.TeamTemplates.Include("Template")
where c.TeamId == teamId
select c.Template into k
where k.StatusId == 1
select new TemplateView
{
TemplateName = k.Name,
Id = k.Id,
IsCustom = k.Custom,
TypeId = k.TypeId,
TypeName = k.TemplateType.Description,
FileName = k.FileName,
TemplateImage = "test.png",
LanguageId = k.LanguageId,
LanguageName = k.Language.Name,
CreateDate = k.CreateDate
}).ToList();
public IEnumerable<TemplateView> GetTemplates(Guid userId, int languageId)
{
using (DigigateEntities context = new Models.DigigateEntities())
{
var teamId = (from tm in context.TeamMembers.Include("Team")
where tm.UserId==userId
select tm.Id).FirstOrDefault();
IEnumerable<TemplateView> result = from k in (from tmp in context.TeamTemplates.Include("Template")
select tmp.Template)
where k.StatusId==1
select new
{
TemplateName = k.Name,
Id = k.Id,
IsCustom = k.Custom,
TypeId = k.TypeId,
TypeName = k.TemplateType.Description,
FileName = k.FileName,
TemplateImage = "test.png",
LanguageId = k.LanguageId,
LanguageName = k.Language.Name,
CreateDate = k.CreateDate
};
return result;
}

LINQ: Group By + Where in clause

I'm trying to implement a T-SQL equivalent of a where in (select ...) code in LINQ.
This is what I have now:
int contactID = GetContactID();
IEnumerable<string> threadList = (from s in pdc.Messages
where s.ContactID == contactID
group 1 by new { s.ThreadID } into d
select new { ThreadID = d.Key.ThreadID}).ToList<string>();
var result = from s in pdc.Messages
where threadList.Contains(s.ThreadID)
group new { s } by new { s.ThreadID } into d
let maxMsgID = d.Where(x => x.s.ContactID != contactID).Max(x => x.s.MessageID)
select new {
LastMessage = d.Where(x => x.s.MessageID == maxMsgID).SingleOrDefault().s
};
However, my code won't compile due to this error for the ToList():
cannot convert from
'System.Linq.IQueryable<AnonymousType#1>'
to
'System.Collections.Generic.IEnumerable<string>'
Anyone have any suggestions on how to implement this? Or any suggestions on how to simplify this code?
Your query returns a set of anonymous types; you cannot implicitly convert it to a List<string>.
Instead, you should select the string itself. You don't need any anonymous types.
Change it to
var threadList = pdc.Messages.Where(s => s.ContactID == contactID)
.Select(s => s.ThreadID)
.Distinct()
.ToList();
var result = from s in pdc.Messages
where threadList.Contains(s.ThreadID)
group s by s.ThreadID into d
let maxMsgID = d.Where(x => x.ContactID != contactID).Max(x => x.MessageID)
select new {
LastMessage = d.Where(x => x.MessageID == maxMsgID).SingleOrDefault()
};

Resources