Linq/lambda toDictionary() throwing exception - c#

I have a query that looks something like this
return (from item in context.MERCHANDISE_ITEMS
where item.ORGID == orgId
select new MerchandiseModel()
{
Description = item.DESCRIPTION,
...,
Images = context.MERCHANDISE_IMAGES.Where(i => i.ITEMID == item.ID).ToDictionary(i => i.ID, i => i.IMAGENAME)
}).FirstOrDefault();
Images is a Dictionary and MERCHANDISE_IMAGES.ID is int and MERCHANDISE_IMAGES.IMAGENAME is string.
This is throwing System.NotSupportedException, I've tried doing linq (no lambda), same exception.
Here is the exception:
LINQ to Entities does not recognize the method
'System.Collections.Generic.Dictionary`2[System.Int32,System.String] ToDictionary[MERCHANDISE_IMAGES,Int32,String](System.Collections.Generic.IEnumerable`1[Hylton.Infrastructure.EntityFrameworkORM.MERCHANDISE_IMAGES],
System.Func`2[Hylton.Infrastructure.EntityFrameworkORM.MERCHANDISE_IMAGES,
System.Int32], System.Func`2[Hylton.Infrastructure.EntityFrameworkORM.MERCHANDISE_IMAGES,System.String])' method,
and this method cannot be translated into a store expression.
I have no idea what I'm doing wrong, I've looked through every post on this issue and everything I've seen is doing it the same way. Can anyone point out what I'm missing?

Short version: EF can't convert ToDictionary to SQL.
In my experience, the best way to go is to simplify the Select to an anonymous type with all the stuff from the DBOs you'll need, call ToList (or something else to force the evaluation) and then do the ToDictionary and other massaging of the data.
The added benefit of this is by forcing it to evaluate, you'll avoid leaving the using your context is in and closing it, which will cause an error when you try and use the return.

Related

Linq select object while changing some variables

I'm having an issue in Linq and have an object like this one:
{"Id": "",
"tDate": "2021-02-08T13:15:59.5419262Z",
"innerObject": [
{
"innerObjectId": "",
"tDate": "2021-02-08T13:15:59.528643Z"
}
],
...};
This is connected to an api, and when it's called I want to query this object and return all the attributes in ObjectDTO while changing the tDate in case there's a date in the innerObject (I know that condition works, so no need to worry with that):
this.query = cosmosDbProvider.GetContainer().GetItemLinqQueryable<ObjectDTO>().AsQueryable()
.Where(t => someconditions)
.Select(o => { o.tDate = o.innerOject.Where(t => t.innerObjectId == input.innerObjectId).Select(t => t.tDate).ToArray()[0] ?? o.tDate;
return o; });
However, I can't seem to make this work. The errors I'm getting are 'A lambda expression with a statement body cannot be converted to an expression tree' and 'An expression tree may not contain an assignment operator'.
The other option I've tried is manually assigning each variable of ObjectDTO, but that looks awful.
Does anyone have any ideas?
I would try to use a "foreach" in that case with the "where" results.
Also if you want to use LINQ you may check this answer.
Is nice to remember that if you are working with an IQueryable you should know that the "ToList" method would trigger the Entity Framework query in the database before going to your "ForEach". In that case, try to have a look at this LINQ to SQL syntax
There are two enumerations you need to be aware of...one is the IEnumerable which is a concreate representation of the data and is in memory for access. There other is IQueryable which is not a hard representation, but a set of possibilities for what the data will look like and is sent to the database and not in memory.
But the method Where returns an IQueryable which is fine and can be chained with other IQueryable returns, but the Select is asking to change the shape of existing data (as IEnumerable).
Put in a ToList() (which will make the IQueryable(s) to be executed and return a concrete List<T> of objects) after the Where( ... ); and that will provide a valid data set to the Select operate on.

Populate navigation properties when used in method passed to Where clause

I have an endpoint which should list all Candidatures filtered by search term, so what I do is to create a method which accepts candidature entity and searchTerm as params. Then I pass that method to Where clause, but the problem is that I got NullReferenceException because navigation properties are nulls. If I put statement inside Where clause instead of the method then it doesn't throw Exception. The question is how to fix this, but I want to keep the external method because there will be a lot more logic, but I need to have access to all navigation properties i.e. they should be populated.
if (!string.IsNullOrEmpty(searchTerm))
{
query = query.Where(c => FilterBySearchTerm(c, searchTerm));
}
var result = await query.Select(c => new CandidaturesResponseModel()
{
Id = c.Id,
Name = c.PrimaryMember.FullName, // that's filled
}).ToListAsync();
private bool FilterBySearchTerm(Candidature c, string searchTerm)
{
return c.PrimaryMember.FirstName.Contains(searchTerm); // here is the exception because PrimaryMember navigation property is null. So I want this to be filled.
}
The issue is that you're materializing the query by using your FilterBySearchTerm method. EF cannot translate random methods to SQL, so it has to go ahead and run the query, get the results back and then apply your Where. EF would actually throw an exception in the past, but EF Core handles this silently.
Anyways, once the query is run, you're done. Your filtering is happening in-memory, and at that point, without an Include, your related entities are not there to work with. Long and short, you'll need to build your filter in place (rather than using a separate method) in order for EF to be able to translate that to SQL.
An alternative approach which may serve you better is pass a queryable to your FilterBySearchTerm method. For example, instead of doing:
query = query.Where(c => FilterBySearchTerm(c, searchTerm));
Do
query = FilterBySearchTerm(query, searchTerm);
Then, inside FilterBySearchTerm, you can directly apply Where clauses to the passed in query. That allows you to build an actual query that EF can understand, while also encapsulating the logic.
Just use Include method to add PrimaryMember
query = query.Include(x=> x.PrimaryMember).Where(c => FilterBySearchTerm(c, searchTerm));

Cannot implicitly convert type 'Program.Data.View' TO System.linq.iqueryable<Program.Data.View>.

Goal/Problem: I am trying to use the First or FirstOrDefault to only return 1 result from the Database. I'm getting the following error:
Cannot implicitly convert type 'Program.Data.view' to System.Linq.Iqueryable An explicit conversion exists (are you missing a cast)
What I've tried:
After looking through documentation and many SO articles, I tried different ways of casting, including the code below. Articles such as Cannot implicitly convert type 'System.Linq.IQueryable<>to . An explicit conversion exists (are you missing a cast?). Most of the articles are going from System.Linq.IQueryable<> to something else, not this direction. Either way casting should be relatively easy, what am I missing?:
IQueryable<Program.Data.view> someVariable = db.view.First(m => m.ID == Search_ID);
My method signature is:
public IQueryable<Program.Data.view> GetDataFromQuery()
First() and similar methods (like FirstOrDefault() or Count() or Average()) does not return an IQueryable<Program.Data.view> and does not use deferred execution.
First() is executed immediatly and returns only one (well, the first) Program.Data.view.
So the error message is correct. Change your signature to
public Program.Data.view GetDataFromQuery()
and the mentioned line to:
Program.Data.view someVariable = db.view.First(m => m.ID == Search_ID);
When you use of method like: First(), FirstOrDefault(), Single(), SingleOrDefault(), Count() and something like this, return type of data that you mapped in Entity-Framework.
When you work with Entity-Framework, Do you know, when EF get data from database?
Look this sample:
var result = context.Student.Where(x=>x.Id == model.Id);
In this sample, the result type is IQueryable<T> and didn't call database, this is just a query.
Now look this sample:
var result = context.Student.Where(x=>x.Id == model.Id).ToList();
In this sample, the result type is T and called database and we have data about Student Table.
Attention that ToList() Or ToListAsync() method always call database and execute the query.
If you can not change signature of your GetDataFromQuery or you would like to save deferred execution, you can use this approach:
db.view.Where(m => m.ID == Search_ID).Take(1);
Please note that this replacement is not strictly equivalent: it will not throw even if db.view has no data
What ended up working was the following:
var someVariable = db.view.Where(m => m.Some_ID == Search_SomeID).GroupBy(x => x.Some_ID).Select(g => g.OrderByDescending(p => p.Column_Two).FirstOrDefault()).AsQueryable().OrderBy(x => x.Column_Three);
AKA (format):
Where().GroupBy().Select(...OrderByDescending().FirstOrDefault()).AsQueryable().OrderBy();
Not sure why the others gave me errors but doing it in this order worked
Also, the OrderBy at the very end seems completely unecessary by logic, but I get a System.NotSupportedException error when I don't have it (The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip' ... this happens on .DataBind())

Why am I unable to acess properties of my object model?

For some odd reason, I am unable to access the properties of this object EVEN if I cast it as its model type. Does anyone have an idea as to why? (It may be obvious, but I'm pretty new to C# so please be patient! :o) )
Users currentUser = new Users();
currentUser = (from x in db_tc.Users where x.Id == Convert.ToInt32(User.Identity.Name) select x);
When I call currentUser, I am only able to access the CRUD methods and a List<Users> property called usrList. I didn't create the list definition, so I imagine this is some piece of the Entity framework that is automagically created.
I did try casting currentUser with (Users) prior to the entity query, it didn't help at all.
That's because you've only created the query, you haven't actually executed it. Add Single() (or First() etc.) to get the result:
var currentUser = (from x in db_tc.Users where x.Id == Convert.ToInt32(User.Identity.Name) select x).SingleOrDefault();
Single(): Gets the first element of the sequence, but will throw an exception if no element is found or if the sequence has more than one element.
First(): Gets the first element of the sequence, but will throw an exception if no element is found.
SingleOrDefault() and FirstOrDefault(): Same as above, but will return default(T) instead of throwing on the empty sequence.
This "deferred execution" aspect of LINQ is probably the most difficult part to understand. The basic idea is that you can build up a query using the query operations (Where(), Select(), etc.), and then you can execute that query to actually get its results, using one of the non-deferred execution operations (Single(), ToList(), etc.)
The operation you have here will return a list of matches the DB will return, it is a collection.
If you intend on it only returning a single record append .First(); to the end of your linq query.
Also remove this line Users currentUser = new Users();
and add this var currentUser =...
Some more tips from the "good LINQ practices" wagon...
LINQ should "usually" return var, then you convert to the data type you are expecting. Another good practice I have found is to immediately validate the return from LINQ, as any usage without validation is highly exception prone. For instance:
var qUser = (from x in db.Users select x);
if (qUser != null && currentUser.Count() > 0)
{
List<User> users = (List<User>)qUser.ToList();
... process users result ...
}
The not null and count greater than 0 check should be required after each LINQ query. ;)
And don't forget to wrap in try-catch for SqlException!

Select doesn't work on IQueryable but does on IList

I have two lines of code, one is
AllItems().Where(c => c.Id== id)
.Select(d => new Quality(d.QualityType)).ToList();
and the other one
AllItems().Where(c => c.Id== id).ToList()
.Select(d => new Quality(d.QualityType)).ToList();
The only difference is on the second statement ToList() is called after the Where statement. The second statment works just fine.
On the first statement, the default parameterless constructor is hit instead of the constructor with the parameter. so the list is created but the objects in the list are initialized with default values rather than with the d.QualityType.
you can see the full source of the file in question at (Method: GetBestQualityInHistory)
https://github.com/kayone/NzbDrone/blob/master/NzbDrone.Core/Providers/HistoryProvider.cs
**Edit: After further investigation, this seems to be a SubSonic bug, if the Last ToList is replaced by an OrderBy subsonic throws an The construtor 'Void .ctor(NzbDrone.Core.Repository.Quality.QualityTypes, Boolean)' is not supported.
If SubSonic works in the same way as Entity framework you cannot use constructors with parameters - you must use parameterless constructors and initializers. My very high level explanation of this is that the query is not executed as is - it is translated to SQL and because of that you must use property initializers so that expression tree knows which properties in the projected type should be filled by values. When using constructor with parameters, expression tree doesn't know where the passed parameter belongs to (it doesn't check content of the constructor). The real constructor (parameterless) is called once you execute Tolist and result set is materialized into QuantityType instances.
This isn't really an answer, and I was going to make it a comment, but needed more room for code snippet.
Judging by the SubSonic code, I'm pretty sure that in some cases where you get the "constructor not supported" errors, SS is for some reason trying to parse your new ...() statement into SQL. The offending method is part of the SQL Formatter, and looks like it only handles DateTime:
protected override NewExpression VisitNew(NewExpression nex)
{
if (nex.Constructor.DeclaringType == typeof(DateTime))
{
// ...omitted for brevity...
}
throw new NotSupportedException(string.Format("The construtor '{0}' is not supported", nex.Constructor));
}
I think that would normally be hit if you did something like:
someData
.Where(data => data.CreatedDate <= new DateTime(2011, 12, 01))
.Select(data => data)
Then that newDateTime would be translated to SQL.
So however you change the Linq to get that exception, I think that is what is happening.
I assume this is because if you add an .OrderBy() after the .Select() then you are no longer calling the OrderBy on the IQueryable of whatever AllItems() returns, but instead trying to order by what is returned by the .Select(), which is the enumerable of new Quality obejcts, so SS probably tries to turn all that into SQL.
I'm wondering if it would work right if you reversed it?
AllItems().Where(c => c.Id== id)
.OrderBy(d => d.QualityType)
.Select(d => new Quality(d.QualityType));

Categories

Resources