Linq query returns unwanted results as well - c#

I'm new to linq and C#, trying to query complex objects by a certain property. The scenario is I have a list of currentPerson objects, which include PersonAddressesDisplay property, which is a string. I'm looping over the list of trying to find saved person objects in the DB by their address (PersonAddressesDisplay). Right now for some odd reason I get unwanted results as well (different strings also get in the matchAddresses list). The query is as follows:
foreach(var currentPerson in PersonsListToSave) {
.
.
.
var matchAddresses = db.PersonAddresses.Include(p => p.Persons).AsEnumerable().
Where(add => currentPerson.Addresses
.Any(personAddress => personAddress.PersonAddressesDisplay == add.PersonAddressesDisplay)).ToList();
// matchAddresses includes unwanted results
.
.
.
}

If you want to extract PersonAddress+Person objects which share at least address with the currentPerson, then you can construct a query for that purpose directly:
foreach(var currentPerson in PersonsListToSave)
{
// ...
IEnumerable<string> currentAddresses =
currentPerson.Addresses.Select(personAddr => personAddr.PersonAddressesDisplay);
var matchAddresses = db.PersonAddresses.Include(p => p.Persons)
.Where(addr => currentAddresses.Contains(addr.PersonAddressesDisplay))
.ToList();
// ...
}
I cannot try this code, but I think that it should work correctly by constructing the WHERE - IN SQL filter under the hood. Please try it out and send more info if this is not solving the issue.

Related

Linq challenge: converting this piece of code from method chain to standard Linq

The challenge is about converting from method chain to standard linq a piece of code full of group by.
The context
To fully understand the topic here you can read the original question (with class definitions, sample data and so on): Linq: rebuild hierarchical data from the flattened list
Thanks to #Akash Kava, I've found the solution to my problem.
Chain method formulation
var macroTabs = flattenedList
.GroupBy(x => x.IDMacroTab)
.Select((x) => new MacroTab
{
IDMacroTab = x.Key,
Tabs = x.GroupBy(t => t.IDTab)
.Select(tx => new Tab {
IDTab = tx.Key,
Slots = tx.Select(s => new Slot {
IDSlot = s.IDSlot
}).ToList()
}).ToList()
}).ToList();
But, for sake of knowledge, I've tried to convert the method chain to the standard Linq formulation but something is wrong.
What happens is similar to this..
My attempt to convert it to Linq standard syntax
var antiflatten = flattenedList
.GroupBy(x => x.IDMacroTab)
.Select(grouping => new MacroTab
{
IDMacroTab = grouping.Key,
Tabs = (from t in grouping
group grouping by t.IDTab
into group_tx
select new Tab
{
IDTab = group_tx.Key,
Slots = (from s in group_tx
from s1 in s
select new Slot
{
IDSlot = s1.IDSlot
}).ToList()
}).ToList()
});
The result in LinqPad
The classes and the sample data on NetFiddle:
https://dotnetfiddle.net/8mF1qI
This challenge helped me to understand what exactly returns a Linq Group By (and how prolix is the Linq syntax with Group By).
As LinqPad clearly shows a Group By returns a List of Groups. Group is a very simple class which has just one property: a Key
As this answer states, from definition of IGrouping (IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable) the only way to access to the content of the subgroups is to iterate through elements (a foreach, another group by, a select, ecc).
Here is shown the Linq syntax formulation of the method chain.
And here is the source code on Fiddle
But let's go on trying to see another solution:
What we usually do in SQL when we do a Group By is to list all the columns but the one which have been grouped. With Linq is different.. it still returns ALL the columns.
In this example we started with a dataset with 3 'columns' {IDMacroTab, IDTab, IDSlot}. We grouped for the first column, but Linq would return the whole dataset, unless we explicitly tell him..

How to use First Method in querying data with Entity Datasource?

I have the following code:
var orders = context.Orders
.Include("Clients")
.GroupBy(i => i.Clients.ClientName)
.Select(i => i.OrderByDescending(it => it.OrderDate).FirstOrDefault());
I want to get only the last order made by each client. The basis for the code I got from here Remove duplicates in the list using linq, from Freddy's answer. (I'm including "Clients" because Orders has ClientId but not client name and the results are to be displayed in a grid including the client name which I'm getting from Clients).
This works properly.
MY QUESTION:
Is it possible to do this using an asp.net Entity Datasource control?
Is it possible to use FirstOrDefault in some way in the asp.net Entity Datasource control?
If you move the Include to the end of the query ...
var orders = context.Orders
.GroupBy(i => i.Clients.ClientName)
.Select(i => i.OrderByDescending(it => it.OrderDate).FirstOrDefault())
.Include("Clients");
... you'll get Orders with their clients included.
With the original Include the query shape changes after the Include was applied. This always makes Include ineffective. You can move it to the end of the query, because even after the grouping, the query still return Orders, so the Include is applicable.
Note however that this is a tremendous overkill. Entire Client records are queried from the database, entire Client objects are materialized and in the end you only display their names. It's much better to project the required data to a DTO that exactly contains the data you want to display. For example:
var orders = context.Orders
.GroupBy(i => i.Clients.ClientName)
.Select(i => i.OrderByDescending(it => it.OrderDate).FirstOrDefault())
.Select(o => new OrderDto
{
o.OrderNumber,
o. ... // More Order properties
Client = o.Clients.Name
});
The DTO should be a class containing these properties.
I don't know the Entity Datasource control. From what I see in the MSDN documentation it seems too restricted to even shape the query sufficiently to get the last orders of each client. And it expects an entity set, no DTOs.
Instead of calling OrderbyDescending try using the max operated as explained here
I found here that with the EntityDataSource you can use:
Select="top(1) it.[OrderDate]"
However if you want to Order by DESC the top will be executed before the Order by DESC.
If you want the Order by executed before the top, in other words to get the last Item, instead of top do this in Code behind:
protected void entityDataSource_Selecting(object sender,EntityDataSourceSelectingEventArgs e)
{
e.SelectArguments.MaximumRows = 1;
}
All of this I got from that link in the Qustion and Answer.
I found that I can use the EntityDataSource's QueryCreated event as demonstrated in Filter with EntityDatasource in ASP.NET webforms in the question and answers.
In the case of this question I wrote
protected void EntityDataSource1_QueryCreated(object sender, QueryCreatedEventArgs e)
{
var ordersQuery = e.Query.OfType<Orders>();
e.Query = ordersQuery.Include("Clients")
.GroupBy(i => i.Clients.ClientName)
.Select(i => i.OrderByDescending(it => it.OrderDate).FirstOrDefault());
}

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>());

Order by string compared to string list

I'm attempting to order some data using a certain pattern and need some help figuring out how to do so. I haven't seen any examples of using linq queries within an orderby clause and am not exactly sure how to order the data in this scenario:
public IList<UserEventModel> GetListData(IDictionary<string, IList<string>> criteria, IEnumerable<UserEventModel> subscriptions)
{
subscriptions = subscriptions.OrderBy(x => x.EntityType);
}
Looking for an extension to append to EntityType to compare against the contents of 'criteria' value pair here at key[2], e.g. x => x.EntityType.? (specified collection of values)
your are almost right. Append tolist and try
subscriptions = subscriptions
.OrderBy(x => x.EntityType).ToList();

Formatting LINQ Query Result Set

I am working on an ASP.NET MVC application. This application executes a query via JQuery. The result set is returned as JSON from my ASP.NET MVC controller. Before I return the serialized result set, i need to trim it down to only the properties I need. To do this, I'm using a LINQ Query. That LINQ Query looks like the following:
private IEnumerable RefineResults(ResultList<Result> results)
{
// results has three properties: Summary, QueryDuration, and List
var refined = results.Select(x => new
{
x.ID, x.FirstName, x.LastName
});
return refined;
}
When I execute this method, I've noticed that refined does not include the Summary and Duration properties from my original query. I'm want my result set to be structured like:
Summary
QueryDuration
Results
- Result 1
- Result 2
- Result 3
...
At this time, when I execute RefineResults, I get the result list I would expect. However, I can't figure out how to put those entries in a property called "Results". I also don't know how to add the "Summary" and "QueryDuration" properties.
Can someone please point me in the right direction?
Thanks!
private object RefineResults(ResultList<Result> results)
{
// results has three properties: Summary, QueryDuration, and List
var refined = results.Select(x => new
{
x.ID, x.FirstName, x.LastName
});
return new { Results = refined, Summary = results.Summary, QueryDuration = results.QueryDuration };
}
You will probably want to create a specific DTO class for the return value of this function.

Categories

Resources