Linq multiple fields from one list distinctly - c#

I am having issues with a single query, I am attempting to take a list of objects (opList) and extract 2 UID's per object to create a distinct list for another query.
DataContext.Where(x => opList.Select(y => y.UIDFirst)
.Union(opList.Select(o => o.UIDSecond)).ToList()
.Contains(x.uid)).ToList();
while each of the selects work by themselves, and the union works to join the lists to a unique list (tested in imediates window) an exception is thrown when processes the statement as a whole.
any ideas on what I did wrong;
{"Unable to create a constant value of type 'DataContext.Class.Operation'. Only primitive types or enumeration types are supported in this context."}

This should work:
var uids = opList.Select(o => o.UIDFirst)
.Concat(opList.Select(o => o.UIDSecond)
.ToList();
var result = DataContext.Where(dc => uids.Contains(x.uid)).ToList();

You have to call your statement on a certain DbSet contained in the DataContext, not on the DataContext directly.
DatContext.SomeDbSet.Where(x => /* ... */ );

Related

Cannot be inferred from the usage. Try specifying the type arguments explicitly

I want to make a projection as a performance wise but the select part returns an anonymous type and I can't to make required mapping.
var jobDegreesQuery = _context.JOBDEGREEs.AsQueryable().Select(d=> new {d.DEGREE_CODE,d.DEGREE_NAME });
if (!String.IsNullOrWhiteSpace(name))
jobDegreesQuery = jobDegreesQuery.Where(c => c.DEGREE_NAME.Contains(name));
var jobDegreeDTOs = jobDegreesQuery
.ToList()
.Select(Mapper.Map<JOBDEGREE, JobDegreeDTO>); //The error
The type arguments for method 'Enumerable.Select(IEnumerable, Func)' cannot be
inferred from the usage. Try specifying the type arguments explicitly.
How can I do the projection and map to DTO Successfully ?
As I understand you want to map JOBDEGREEs to JobDegreeDTO. You are first selecting it as anonymous type, so I think AutoMapper can not map because you are giving anon. type.
Change your code as below it will perform better:
IQueryable<JOBDEGREEs> jobDegreesQuery = _context.JOBDEGREEs; // it is already queryable
if (!String.IsNullOrWhiteSpace(name))
jobDegreesQuery = jobDegreesQuery.Where(c => c.DEGREE_NAME.Contains(name));
var jobDegreeDTOs = jobDegreesQuery
//.Select(d=> new {d.DEGREE_CODE,d.DEGREE_NAME }) // do you need this?
.Select(d => Mapper.Map<JOBDEGREE, JobDegreeDTO>(d)); // here you can give any expression
.ToList()
What is the result of your ToList()? It is a List of objects of some anonymous class, that contains data extracted from your sequence of JobDegrees
Whenever you want to use Enumerable.Select on a sequence of objects, you'll first have to name an identifier that represents one element of your sequence. This identifier is the part before the =>. After the => you'll write the code to return one object using this input identifier.
This is a difficult way to say something like:
IEnumerable<Person> myPersons = ...
var firstNames = myPersns.Select(person => person.FirstName);
Here the person before the => represents one item of your collection of Persons. Hence person seems a proper name for this identifier.
If you want you can use any identifier to identify a person, although not all identifiers will improve readability:
var firstNames = myPersns.Select(x => x.FirstName);
When using LINQ and entity framework it is good practice to identify collections with plural nouns and elements of collections with singular nouns.
After the => you write some code that uses this input person to return exactly one object. In this example the FirstName of the person.
Back to your question
The result of your ToList is a sequence of objects with a DegreeCode and a DegreeName.
If you want to convert every object in your sequence into one other object (this is called projection), you'll have to identify one object of your sequence before the '=>'.
For example
...ToList()
.Select(extractedDegreeData => ...)
Here, every extractedDegreeData corresponds with one element of your list.
Now what do you want to do with one such extractedDegreeData? You want to return the return value of Mapper.Map<JOBDEGREE, JobDegreeDTO>(extractedDegreeData).
Therefore your code should be like:
...ToList()
.Select(extractedDegreeData => Mapper.Map<JOBDEGREE, JobDegreeDTO>(extractedDegreeData));
Advice:
While constructing your LINQ query, don't use functions like ToList, or any other functions that does not return IEnumerable<TResult>, it is a waste of processing power. What if after your Select you would have put Take(2)? What a waste to create the complete list of 1000 elements if you only wanted the first two!
Therefore functions like ToList, FirstOrDefault, Max, Count should always be the last in your linq query.
Finally: dbContext.JobDegrees is a DbSet<JobDegree>, which implements IQueryable<JobDegree>, hence there is no need to use AsQueryable.

Entityframework error: "The type '......' appears in two structurally incompatible initializations within a single LINQ to Entities query

I am having trouble generating an IQuerable result from a group by clause in linq to Entities for range of values.
IQueryable<Model.MyEntity> query = MyContext.GetDbSet()
IQueryable<MyObject> query2 = null;
query2 = query.Select(x => new MyObject()
{
GroupingColumn = SqlFunctions.StringConvert(arrayMin.AsQueryable().FirstOrDefault(s => x.Amount > s)) +
"-" + SqlFunctions.StringConvert(arrayMax.AsQueryable().FirstOrDefault(s => x.Amount < s)) ,
CountOfAmountRange = 1,
SumOfAmount = (decimal)x.Amount,
});
query2 = query2.GroupBy(cm => new {cm.GroupingColumn }).Select(y => new MyObject()
{
GroupingColumn =y.Key.GroupingColumn ,
CountOfAmountRange = y.Count(),
SumOfAmount = (decimal)y.Sum(p => p.SumOfAmount)
});
A bit of context:
I am working on a highly structured application which has a layer responsible for generating queries which will be later applied to retrieve data from context in another layer. I have successfully used this to generate many reports but this specific one throws the error below.
The type 'MyObject' appears in two structurally incompatible
initializations within a single LINQ to Entities query. A type can be
initialized in two places in the same query, but only if the same
properties are set in both places and those properties are set in the same order.
The arrayMax and arrayMin are arrays of decimal values containing the maximum and minimum values respectively for comparison and generating of string values for the GroupingColumn.
I have seen a couple of questions relating this error on StackOverflow but none of the answers seem to show me the solution to my problem.
For future readers, this SO duplicate (added one year later) was key to solving my problems:
The type appear in two structurally incompatible initializations within a single LINQ to Entities query
When you look at it, the error message is abundantly clear. Don't mess up the initialization order if you are instantiating an object more than once in the same Linq expression. For me, I instantiated a type conditionally, but with different initialization orders.

Group by Linq setting properties

I'm working on a groupby query using Linq, but I want to set the value for a new property in combination with another list. This is my code:
var result = list1.GroupBy(f => f.Name)
.ToList()
.Select(b => new Obj
{
ClientName = b.Name,
Status = (AnotherClass.List().Where(a=>a.state_id=b.????).First()).Status
})
I know I'm using a group by, but I'm not sure of how to access the value inside my bcollection to compare it with a.state_id.
This snippet:
Status = (AnotherClass.List().Where(a=>a.state_id=b.????).First()).Status
I've done that before but months ago I don't remember the syntax, when I put a dot behind b I have acces only to Key and the Linq Methods... What should be the syntax?`
Issue in your code is happening here:
a=>a.state_id=b.????
Why ?
Check type of b here, it would be IGrouping<TKey,TValue>, which is because, post GroupBy on an IEnumerable, you get result as IEnumerable<IGrouping<TKey,TValue>>
What does that mean?
Think of Grouping operation in the database, where when you GroupBy on a given Key, then remaining columns that are selected need an aggregation operation,since there could be more than one record per key and that needs to be represented
How it is represented in your code
Let's assume list1 has Type T objects
You grouped the data by Name property, which is part of Type T
There's no data projection so for a given key, it will aggregate the remaining data as IEnumerable<T>, as grouped values
Result is in the format IEnumerable<IGrouping<TK, TV>>, where TK is Name and TV represent IEnumerable<T>
Let's check out some code, break your original code in following parts
var result = list1.GroupBy(f => f.Name) - result will be of type IEnumerable<IGrouping<string,T>>, where list1 is IEnumerable<T>
On doing result.Select(b => ...), b is of type IGrouping<string,T>
Further you can run Linq queries on b, as follows:
b.Key, will give access to Name Key, there's no b.Value, for that your options could be following or any other relevant Linq operations:
a=>b.Any(x => a.state_id == x.state_id) or // Suuggests if an Id match in the Collection
a=>a.state_id == b.FirstOrDefault(x => x.state_id) //Selects First or default Value
Thus you can create a final result, from the IGrouping<string,T>, as per the logical requirement / use case

How to select all objects sharing a property value with a property value in list of objects?

I have a list of objects that I want to reload their data.
Like always, I have several options. I wanted just to select these items but encountered this "Additional information": Unable to create a constant value of type 'Item'. Only primitive types or enumeration types are supported in this context.
// (System.Collections.Generic.List<Item> selectedItems)
System.Collections.Generic.List<Item> items;
var q = from i in db.Items
where selectedItems.Any(s => s.Id == i.Id)
select i;
items = q.ToList()
the following yields the same, as expected...
var q = db.Items.Where(i => selectedItems.Any(si => i.Id == si.Id));
items = q.ToList();
I could have reattached each of the objects and call the reload, but then I would have(or not, but I don't know how) to run across the db lot of times to reload their Navigation Properties.
The only "fine" solution I've found until now is selecting the Id's of selectedItems and then running with it like follows:
int[] itemIds = selectedItems.Select(i => i.Id).ToArray();
var q = db.Items.Where(i => itemIds.Any(iId => i.Id == iId)); //Of course `Contains` could be used instead of `Any` here, since `itemIds` is a simple array of integers
items = q.ToList();
But is it a necessity or there is a more straight forward, neat or proper way to accomplish this?
But is it a necessity or there is a more straight forward, neat or proper way to accomplish this?
Not that I can think of. EF will try and turn your where clause into SQL (which is not as easy as you'd think). When it parses the expression and encounters a call to Any on a collection of non-primitive types, it does not know how to generically convert that list into a list of values to put in an into an in clause and gives you the error you quoted.
When the source collection is a collection of primitive or enumeration types, it can turn the source collection into a list of values and create an in clause. Contains does the same thing (it is also shorter is closer to the intent IMHO):
var q = db.Items.Where(i => itemIds.Contains(i.Id));

Merging two iqueryables that are projected to one entity from different tables

I have tried This answer, This one and this one to merge two iqueryables. But I always receive the following error:
The type 'Estudio' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
I'm mapping from two different but similar Entity Framework Entities (EXAMEN and EXPLORACION) to my domain entity Estudio, with the following code.
IQueryable<Estudio> listExamen = context.Set<EXAMEN>().Project().To<Estudio>();
IQueryable<Estudio> listExploracion = context.Set<EXPLORACION>().Project().To<Estudio>();
var listCombined = listExamen.Concat(listExploracion);
Is there anyway of generate a IQueryable (not enumerable) with the merging of both list? If AsEnumerable() is used, then the following filters (Order, Take, etc) are executed on memory. So I need to merge the list but still be able to apply filter to the merged list wihtout execute the queries.
//This will force the next condition is executed on memory
var listCombined = listExamen.AsEnumerable().Concat(listExploracion);
Is that possible?
I would try to select your data into an anonymous type in your linq query, perform the union, and add your criteria.
var listExamen = context.Examen
.Select(x => new { x.Prop1, x.Prop2, ... }); // Add properties
var listExploracion = context.Exploraction
.Select(x => new { x.Prop1, x.Prop2, ... }); // Add identical properties
var listCombined = listExamen.Concat(listExploracion);
var whereAdded = listCombines
.Where(x => x.Prop1 == someValue);
var result = whereAdded
.Skip(skipCount)
.Take(takeCount)
.ToList();
Note: I have no idea if you can use Common Table Expressions (the SQL necessity for skip/take) in combination with a Union-query
Note: I've changed the methods used to create the expressions, since I do not know your methods (Project, To)
So I think the solution is not to cast to a specific type, but to an anonymous type, since that probably can be translated to SQL.
Warning: didn't test it
My solution was to revise my mapping code. Instead of using individual property-based mappers, I had to project the entire entity at once, making sure that all of the properties were given in the same order.
So, instead of the ForMember syntax:
Mapper.CreateMap<Client, PersonResult>()
.ForMember(p => p.Name, cfg => cfg.MapFrom(c => c.Person.FirstName + " " + c.Person.LastName))
...
I used the ProjectUsing syntax:
Mapper.CreateMap<Client, PersonResult>()
.ProjectUsing(c => new PersonResult()
{
Name = c.Person.FirstName + " " + c.Person.LastName
...
});
This must be because of the way AutoMapper constructs its projections.
One way to work around this is to add dummy types:
class Estudio<T> : Estudio { }
And new mapping:
Mapper.CreateMap<Estudio , Estudio>();
Mapper.CreateMap<EXAMEN , Estudio<EXAMEN>>();
Mapper.CreateMap<EXPLORACION, Estudio<EXPLORACION>>();
One caveat is that all fields in Estudio need some value in mapping.
You can't use ignore. Returning 0 or "" is fine.
Now we can do:
var a = context.Set<EXAMEN>().ProjectTo<Estudio<EXAMEN>>();
var b = context.Set<EXPLORACION>().ProjectTo<Estudio<EXPLORACION>>();
return a.ProjectTo<Estudio>().Concat(b.ProjectTo<Estudio>());

Categories

Resources