Linq to Object referencing a list - c#

I have two disparate datasources, one has details about Clients the other the Site that only has a ClientID but due to partial migration of a system I can't join them at a database level (yet, that will eventually happen!):
var clients = _clientService.GetClientSummary(true);
var results = context.Sites.AsNoTracking().OrderByDescending(s => s.Id).
Skip((pageIndex - 1) * pageSize).Take(pageSize);
result.Content = pageResult.Select(a => new QuoteSearch
{
Accepted = a.Accepted,
Created = a.Created,
Id = a.Id,
Customer = clients.Find(b => b.Id == a.ClientId).Name
}).ToList();
Running the code above returns an error
"LINQ to Entities does not recognize the method
'CertsAssured.Model.Client.ClientSummary
Find(System.Predicate`1[CertsAssured.Model.Client.ClientSummary])' "
I can write the code after this step to perform the task but would then have to save the ClientId in to my object to then iterate through. Is there a way of getting the info from the client list during the Select method?
Thanks

After the database filtering/paging is set up, you can use AsEnumerable to convert the IQueryable result to an in memory IEnumerable where you can do lookups against clients;
result.Content = pageResult
.AsEnumerable()
.Select(a => new QuoteSearch
{
Accepted = a.Accepted,
Created = a.Created,
Id = a.Id,
Customer = clients.Find(b => b.Id == a.ClientId).Name
}).ToList();
If there are many database fields and you don't want all of the fetched from the database, you can filter the fields on the IQueryable first, something like;
result.Content = pageResult
.Select(a => new // This filters in the database
{ // to an anonymous type
Accepted = a.Accepted,
Created = a.Created,
Id = a.Id,
ClientId = a.ClientId
})
.AsEnumerable() // Convert to an IEnumerable
.Select(a => new QuoteSearch // This is done in-memory
{ // generating the real type
Accepted = a.Accepted,
Created = a.Created,
Id = a.Id,
Customer = clients.Find(b => b.Id == a.ClientId).Name
}).ToList();

Related

EF Core Reuse subquery in different queries

I have a problem trying to reuse some subqueries. I have the following situation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId);
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
This works fine, and generates the exact queries as wanted. The problem is that I want to have this subquery dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId to be taken from a method and not repeat it every time I need it.
I've tested it with the database, where I have values in the corresponding tables, and I've tested the query with the database without any data in the corresponding tables. It works fine with no problems or exceptions.
But when I try to extract the repeating subquery in a separate method and execute it against empty database tables (no data) the .First() statement throws error. Here is the code:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId;
}
and the query generation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == GetClimaticZoneId()).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == GetClimaticZoneId());
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
After execution I get InvalidOperationException "Sequence do not contain any elements" exception in the GetClimaticZoneId method:
I'm sure that I'm not doing something right.
Please help!
Regards,
Julian
As #Gert Arnold suggested, I used the GetClimaticZoneId() method to make a separate call to the database, get the Id and use it in the other queries. I gust modified the query to not generate exception when there is no data in the corresponding table:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).FirstOrDefault()?.ClimaticZoneId??0;
}

Query Source could not be identified

I am trying to generate a list of type Transaction from a list of type Posting by using group by:
var transactions = postings
.GroupBy(p => p.TransactionId, (key, g) => new { TransactionId = key, Postings = g })
.Select(x => new Transaction
{
TransactionId = x.TransactionId,
PostingCount = x.Postings.Count(),
TransactionState = x.Postings.First().PostingState,
CreateDate = x.Postings.First().CreateDate,
CreateUser = x.Postings.First().CreateUser
});
But I encounter this error:
Query Source could not be identified: ItemName = _3, ItemType = Crm.Library.Model.Posting, Expression = from Posting _3 in Convert([p])
This error happens when I perform any operation on x.Postings [First() for example].
Any Ideas on how to get the desired result?
I solved the issue by converting the list of Posting to IEnumerable before using GroupBy
The problem with using IQueryable it will be translated to SQL which will not understand my .net methods. Projections to custom types are part of the query and must be hardcoded.
My code is now:
var transactions = postings.AsEnumerable().GroupBy(p => p.TransactionId, (key, g) => new { TransactionId = key, Postings = g })
.Select(x => new Transaction
{
TransactionId = x.TransactionId,
PostingCount = x.Postings.Count(),
TransactionState = x.Postings.First().PostingState,
CreateDate = x.Postings.First().CreateDate,
CreateUser = x.Postings.First().CreateUser
});

LINQ: How to Select specific columns using IQueryable()

I need to select only two columns from Hospital table, HospitalId and Name.
i tried the below code it selects all columns from Hospital table which lead to slow performance. Please help me to select only two columns from Hospital table
public HttpResponseMessage GetAvailableHospitalsByAjax(System.Guid? DirectorateOfHealthID = null, System.Guid? UnitTypeID = null, string DeviceTypeIDs = null)
{
Context db = new Context();
var query = db.Hospitals.AsQueryable();
if (UnitTypeID != null)
{
query = query.Where(j => j.HospitalDepartments.Any(www => www.Units.Any(u => u.UnitTypeID == UnitTypeID)));
}
if (DirectorateOfHealthID != null)
{
query = query.Where(h => h.DirectorateHealthID == DirectorateOfHealthID);
}
query = query.Where(j => j.HospitalDepartments.Any(u => u.Units.Any(d => d.Devices.Any(s => s.Status == Enums.DeviceStatus.Free)))
&& j.HospitalDepartments.Any(hd => hd.Units.Any(u => u.Beds.Any(b => b.Status == Enums.BedStatus.Free))));
var list = query.ToList().Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
return Request.CreateResponse(HttpStatusCode.OK, list);
}
IQueryable<T> executes select query on server side with all filters. Hence does less work and becomes fast.
IEnumerable<T> executes select query on server side, load data in-memory on client side and then filter data. Hence does more work and becomes slow.
List<T> is just an output format, and while it implements IEnumerable<T>, is not directly related to querying.
So,
var list = query.ToList().Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
In your code you use query.ToList(). This means at first it pull all data into memory then apply Select query.If you want to retrieve HospitalID and Name then remove ToList() then your code like
var list = query.Select(w => new HospitalInfo
{
Id = w.ID,
Name = w.Name
}).ToList();
Remove the ToList call before the projection:
var list = query.Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();
With that ToList call you are materializing your query before do the projection
Because you do query.ToList() this materialises the entire query with all columns into memory. It's actually a bad habit to get into. Instead, remove that, you already have it at the end anyway. The Select projection you have will only retrieve the relevant columns though:
var list = query.Select(w => new HospitalInfo()
{
Id = w.ID,
Name = w.Name
}).ToList();

LINQ: join between results of two separate contexts

I know LINQ doesn't support two diff contexts in a standard 'join'.
Having said that, what I'm trying to do is, pull together a list of, shall we say, 'employees', from a 'user' and 'contact' contexts.
(These are edmx's that are from an old project, that I'm not about to mess with.)
Thing is, 'users' is WHO I want to get, but their demographics reside inside the 'contacts'. Here's the two current LINQ's:
var users = _pets_dc.Users
.Select(p => p)
.Where(x => x.Active)
.ToList();
var contacts = _poems_dc.Contacts
.Select(p => p)
.Where(x => x.Active)
.ToList();
I need contacts where 'user.Contact_GUID' equals 'contacts.Contact_GUID'.
I have tried:
var query = contacts
.Where(x => x.Contact_GUID == users
.Select(y => y.Contact_GUID)
.FirstOrDefault());
to no avail... this only brings back one contact, but won't work without .FirstOrDefault(). Any ideas?
If you are using Contact_GUID in both tables if you have FK in users table try using first query with include
var users = _pets_dc.Users.Include("Contacts")
.Where(x => x.Active)
.ToList();
you can try the following anyway:
var joined = from list1 in users
join list2 in contacts
on list1.Contact_GUID equals list2.Contact_GUID
select new { list1, list2 };
ref : https://stackoverflow.com/a/2724018/1166597
You can use below code:
var result = users.Select(e => contacts.Where(x => x.Contact_GUID == e.Contact_GUID));
Joining is one of the option that would work here, but you can modify your current solution as follows:
var query = contacts
.Where(x => users
.Select(y => y.Contact_GUID).Contains(x.Contact_GUID)
).FirstOrDefault());
Contains will check the Guid in a given list, in original solution you are comparing Guid with List<Guid>, which would fail
Option 1:
var query = from person in users
join contact in contacts on person.Contact_GUID equals contact.GUID into employees
from employee in employees.DefaultIfEmpty()
select new Employee() { User = person, Demographic = employee.Demographic };
var employees = query.ToList();
Option 2:
var query = from person in users
join contact in contacts on person.Contact_GUID equals contact.GUID into employees
from employee in employees.DefaultIfEmpty()
select new { person.FirstName, person.LastName, employee.Demographic };
var employees = query.ToList();

Entity framework use already selected value saved in new variable later in select sentance

I wrote some entity framework select:
var query = context.MyTable
.Select(a => new
{
count = a.OtherTable.Where(b => b.id == id).Sum(c => c.value),
total = a.OtherTable2.Where(d => d.id == id) * count ...
});
I have always select total:
var query = context.MyTable
.Select(a => new
{
count = a.OtherTable.Where(b => b.id == id).Sum(c => c.value),
total = a.OtherTable2.Where(d => d.id == id) * a.OtherTable.Where(b => b.id == id).Sum(c => c.value)
});
Is it possible to select it like in my first example, because I have already retrieved the value (and how to do that) or should I select it again?
One possible approach is to use two successive selects:
var query = context.MyTable
.Select(a => new
{
count = a.OtherTable.Where(b => b.id == id).Sum(c => c.value),
total = a.OtherTable2.Where(d => d.id == id)
})
.Select(x => new
{
count = x.count,
total = x.total * x.count
};
You would simple do
var listFromDatabase = context.MyTable;
var query1 = listFromDatabase.Select(a => // do something );
var query2 = listFromDatabase.Select(a => // do something );
Although to be fair, Select requires you to return some information, and you aren't, you're somewhere getting count & total and setting their values. If you want to do that, i would advise:
var listFromDatabase = context.MyTable.ToList();
listFromDatabase.ForEach(x =>
{
count = do_some_counting;
total = do_some_totalling;
});
Note, the ToList() function stops it from being IQueryable and transforms it to a solid list, also the List object allows the Linq ForEach.
If you're going to do complex stuff inside the Select I would always do:
context.MyTable.AsEnumerable()
Because that way you're not trying to still Query from the database.
So to recap: for the top part, my point is get all the table contents into variables, use ToList() to get actual results (do a workload). Second if trying to do it from a straight Query use AsEnumerable to allow more complex functions to be used inside the Select

Categories

Resources