How to join results from two different sets in LINQ? - linq

I get some data about customers in my database with this method:
public List<KlientViewModel> GetListOfKlientViewModel()
{
List<KlientViewModel> list = _klientRepository.List().Select(k => new KlientViewModel
{
Id = k.Id,
Imie = k.Imie,
Nazwisko = k.Nazwisko,
Nazwa = k.Nazwa,
SposobPlatnosci = k.SposobPlatnosci,
}).ToList();
return list;
}
but also I have another method which counts value for extra field in KlientViewModel - field called 'Naleznosci'.
I have another method which counts value for this field based on customers ids, it looks like this:
public Dictionary<int, decimal> GetNaleznosc(List<int> klientIds)
{
return klientIds.ToDictionary(klientId => klientId, klientId => (from z in _zdarzenieRepository.List()
from c in z.Klient.Cennik
where z.TypZdarzenia == (int) TypyZdarzen.Sprzedaz && z.IdTowar == c.IdTowar && z.Sprzedaz.Data >= c.Od && (z.Sprzedaz.Data < c.Do || c.Do == null) && z.Klient.Id == klientId
select z.Ilosc*(z.Kwota > 0 ? z.Kwota : c.Cena)).Sum() ?? 0);
}
So what I want to do is to join data from method GetNaleznosc with data generated in method GetListOfKlientViewModel. I call GetNaleznosc like this:
GetNaleznosc(list.Select(k => k.Id).ToList())
but don't know what to do next.

Having obtained the dictionary:
var dict = GetNaleznosc(list.Select(k => k.Id).ToList());
You can now efficiently look up the decimal value of Naleznosci for a given client:
foreach (var k in list)
k.Naleznosci = dict[k.Id];
Now you have merged the values into the main list. Is this what you mean?
By the way, in your function that builds the dictionary, you make it accept a List<int>, but then all you do is call ToDictionary on it, which only requires IEnumerable<int>. So change the parameter type to that, and then you can call it:
var dict = GetNaleznosc(list.Select(k => k.Id));
This removes the call to ToList, which avoids making an unnecessary intermediate copy of the whole list of Ids. Probably won't make much difference in this case if you're hitting a database and then building up a large set of results in memory, but maybe worth bearing in mind for other uses of these operations.
Also, looking again at the helper function, there is no apparent advantage in building up the set of results in a dictionary for a list of ids, because each one is handled independently. You could simply put:
public decimal GetNaleznosc(int klientId)
{
return (from z in _zdarzenieRepository.List()
from c in z.Klient.Cennik
where z.TypZdarzenia == (int) TypyZdarzen.Sprzedaz && z.IdTowar == c.IdTowar && z.Sprzedaz.Data >= c.Od && (z.Sprzedaz.Data < c.Do || c.Do == null) && z.Klient.Id == klientId
select z.Ilosc*(z.Kwota > 0 ? z.Kwota : c.Cena)).Sum() ?? 0);
}
That is, provide a function that discovers just one value. Now you can directly build the right list:
public List<KlientViewModel> GetListOfKlientViewModel()
{
return _klientRepository.List().AsEnumerable().Select(k => new KlientViewModel
{
Id = k.Id,
Imie = k.Imie,
Nazwisko = k.Nazwisko,
Nazwa = k.Nazwa,
SposobPlatnosci = k.SposobPlatnosci,
Naleznosci = GetNaleznosc(k.Id)
}).ToList();
}

Related

How to compare IEnumerable<string> for null in Linq query

For the following query:
var result = from sch in schemeDashboard
join exp in Expenditure on sch.schemeId equals exp.SchemeCode
into SchExpGroup
where sch.SectorDepartmentId == selectedDepartmentId &&
sch.YearCode == StateManager.CurrentYear
orderby sch.ADPId
select new
{
ModifiedAmounts = SchExpGroup.Select(a => a.ModifiedAmounts),
ProjectName = sch.schemeName,
ADPNo = sch.ADPId,
Allocation = sch.CurrentAllocation,
Expenditures = from expend in SchExpGroup
where expend.YearCode == StateManager.CurrentYear &&
expend.DepartmentId == selectedDepartmentId &&
InvStatus.Contains(expend.Status)
orderby expend.ADPId
group expend by expend.InvoiceId
};
I want to filter the above query on a condition so that result gives only those records where "ModifiedAmounts" are not null. I have tried as follow:
if (rbList2.SelectedIndex == 6)
{
result = result.Where(a => a.ModifiedAmounts != null));
}
but this gives error as:
Cannot compare elements of type
'System.Collections.Generic.IEnumerable`1'. Only primitive types,
enumeration types and entity types are supported.
Any suggestions as I am lost as how to rephrase the filtered query.
I think the problem is that ModifiedAmounts will never be null. Select will return an empty list. Unless SchExpGroup is null in which case you will get a null reference exception.
Try changing your code to
result = result.Where(a => a.ModifiedAmounts.Any());
if (rbList2.SelectedIndex == 6)
{
result = result.Where(a => a.!ModifiedAmounts.Any());
}

How can i optimize my entity framework query for better performance and more readability

There is a heavy page in my site called HotelDetail, that several things related to hotel will be load in this page. hotel, hotel's services,destinations of places to hotel,user's comments,hotel's room with their Services, prices and capacity.
All of this will be load in one time with below code, now I want to optimize this code, what's solution to optimize this for better performance and more readability ? is there a way to not load all of then in one time?
public PlaceViewModel GetPlaceDetail(string languageKey,string catKey , string placeKey, SearchViewModel searchOptions)
{
DateTime startDate = searchOptions.CheckIn.ToMiladiDateTime();
DateTime endDate = searchOptions.CheckOut.ToMiladiDateTime();
int nights = (int)endDate.Subtract(startDate).TotalDays;
placeViewModel.NightCount = nights;
var query = (from r in _unitOfWork.RoomServiceRepository.Get()
join
a in _unitOfWork.InventoryRepository.Get()
on r equals a.RoomService
where r.Room.Place.Id == place.Id && !r.SoftDelete && !r.Room.SoftDelete &&
a.Date >= startDate && a.Date < endDate && (a.CertainAvailability + a.FloatAvailability) > 0
select new { a, MaxCapacity = r.Room.Capacity + r.ExtraCapacity, r.RoomId });
placeViewModel.HasAvailability = query.ToList().Count != 0;
var grp = query.GroupBy(y => y.a.RoomServiceId)
.Select(z => new {
key = z.Key,
count = z.Count(),
minAvail = z.Min(ax => ax.a.CertainAvailability + ax.a.FloatAvailability),
minDate = z.Min(y => y.a.Date),
minPrice = z.Min(ax=>ax.a.Price),
price = z.Average(ax=>ax.a.Price) });
var tmp = query.Where(x => grp.Any(q =>
q.key == x.a.RoomServiceId &&
q.count == nights &&
q.minDate == x.a.Date)).ToList();
foreach (var item in tmp)
{
var roomViewModel = placeViewModel.Rooms.FirstOrDefault(x => x.Id == item.RoomId);
var avail = new AvailabilityViewModel(item.a, item.MaxCapacity);
avail.Availability = grp.FirstOrDefault(x => x.key == item.a.RoomServiceId).minAvail;
var otherInfos = grp.SingleOrDefault(x => x.key == item.a.RoomServiceId);
avail.JabamaPrice = otherInfos.price;
avail.JabamaMinPriceInPeriod = otherInfos.minPrice;
roomViewModel.Availabilites.Add(avail);
if (maxDiscount == null || (maxDiscount != null &&(maxDiscount.BoardPrice - maxDiscount.JabamaPrice) < (avail.BoardPrice - avail.JabamaPrice)))
maxDiscount = avail;
if (minPrice == null || (minPrice != null && avail.JabamaPrice > 0 && minPrice.JabamaPrice > avail.JabamaPrice))
minPrice = avail;
}
var discountQuery = tmp.Where(x => x.a.RoomService.ExtraCapacity == 0 && x.a.Date == minPrice.Date);
try
{
if (discountQuery.Any())
maxDiscountOfMinPercentageDay = tmp != null && minPrice != null ? discountQuery.Max(x => (x.a.BoardPrice - x.a.Price) / x.a.BoardPrice * 100) : 0;
else
maxDiscountOfMinPercentageDay = 0;
}
catch (Exception)
{
maxDiscountOfMinPercentageDay = 0;
}
foreach (var roomVM in placeViewModel.Rooms)
{
if (roomVM.Availabilites.Count() == 0)
roomVM.Availabilites.Add(new AvailabilityViewModel(-1));
}
}
I'm far from familiar with Linq but I do know some of the pitfalls of SQL. One of the things I notice is that you (and many other people!) use .Count != 0 to figure out if there might or might not be a matching record. In SQL this is a very inefficient approach as the system will effectively go over the entire table to find and count the records that match the prerequisite. In such cases I'd advice to use a WHERE EXISTS() construction; I believe Linq has something along the lines of .Any() that works similarly.
This is also what you do in real life: when someone asks you if there is any cheese left in the fridge, you will not start counting all types of cheese you can find in there but rather stop after the first type of cheese you see and answer 'Yes there is'; there is no added value to going through the entire fridge, it would only cost extra time.
PS: This probably also is true for roomVM.Availabilites.Count() == 0 which might be better handled with inversed logic...
Disclaimer: it could be that the EntityFwk is smart enough to optimize this for you in the background... I don't know; I very much doubt it but like I said, I'm no specialist. I'd suggest to start by first figuring out how long each part of your current code takes and then optimize the slowest ones first. Having a baseline also gives you a better idea if your changes are having any effect, be it positive or negative.

Dynamic linq query with dynamic where conditions and from conditions

public GetApplicants(string Office,
int Id,
List<int> cfrparts,
List<int> expertiseAreaIds,
List<int> authIds,
List<int> specIds)
{
bool isAuthIdsNull = authIds == null;
authIds = authIds ?? new List<int>();
bool isSpecIdNull = specIds == null;
enter code here
var query =
from application in Apps
from cfr in application.cfr
from exp in cfr.Aoe
from auth in exp.Auth
from spec in exp.Special
where application.Design.Id == 14
where (iscfrpart || cfrPartIds.Contains(cfr.CfrP.Id))
where (isexp || expertiseAreaIds.Contains(exp.Aoe.Id))
where (isAuthIdsNull || authIds.Contains(auth.Auth.Id))
where (isSpecIdNull || specIds.Contains(spec.Special.Id))
where application.Office.Text.Contains(Office)
where application.D.Id == Id
select application.Id;
How can i make this query dynamic. If I have only Id and Office values It should still give me the resultset based on the avaliable values. Cureently its not giving me the result.
Instead of doing multiple calls to where, use &&
var query =
from Apps
where (iscfrpart || cfrPartIds.Contains(Apps.cfr.CfrP.Id))
&& (isexp || expertiseAreaIds.Contains(Apps.cfr.Aoe.Id))
&& (isAuthIdsNull || authIds.Contains(Apps.cfr.Aoe.Auth.Id))
&& (isSpecIdNull || specIds.Contains(Apps.cfr.Aoe.Special.Id))
&& Apps.Office.Text.Contains(Office)
&& Apps.D.Id == Id
select application.Id;
Additionally, this clause application.D.Id == 14 will cause 0 results when combined with this one: application.D.Id == Id if the passed in Id does not equal 14. You may want to delete that first clause.
Edit: updated your from clause, but I still don't think this will work because your table structure seems off.
Dynamic querying isn't required to solve this problem. You can write code that constructs the query.
Make a method that constructs your filter based on the information you have.
public Expression<Func<Application, bool>> GetFilterForCFR(List<int> cfrParts)
{
if (cfrParts == null || !cfrParts.Any())
{
return app => true;
}
else
{
return app => app.cfr.Any(cfr => cfrParts.Contains(cfr.cfrPId));
}
}
Then, you can construct the query using the expression.
var query = Apps;
var cfrFilter = GetFilterForCFR(cfrParts);
query = query.Where(cfrFilter);
//TODO apply the other filters
var finalquery = query.Select(app => app.Id);

EntityFramework/LINQ - get filtered content from associated table/type

In my Entityframework model, I have a type/table “ModelElement”, which is linked to a type/table “ElementToComponentMapping”. The navigation/foreign keys are "ModelID" and "ElementNo".
I need to write a method that returns an instance of ModelElement based on certain filtering conditions, where the content of the ElementToComponentMapping linked to the ModelElement instance is also included. The challenge is that I also need to filter on what I return from the ElementToComponentMapping, which means it doesn’t look like I can use .Include
So this doesn't work, as I can't use the included/navigation type in the where clause
public ModelElement GetModelElement(int modelID, int modelElementNo, int version)
{
return (from c in context.ModelElements.Include("ElementToComponentMapping")
where c.ModelID == modelID && c.ElementNo == modelElementNo
&& c.ElementToComponentMappings.Where(m => m.version == version)
select c).FirstOrDefault();
}
My second attempt was to query out the main "ModelElement" object first, then query out the associated "ElementToComponentMappings" separately, and set that as the property of the "ModelElement"
public ModelElement GetModelElement(int modelID, int modelElementNo, int version)
{
ModelElement newElement = (from c in context.ModelElements
where c.ModelID == modelID && c.ElementNo == modelElementNo
select c).FirstOrDefault();
newElement.ElementToComponentMappings =
(from m in context.ElementToComponentMappings
where m.ModelID == modelID
&& m.ElementNo == modelElementNo
&& m.version == version
select m).FirstOrDefault();
return newElement;
}
But this also doesn't work, as the type returned by directly querying for the "ElementToComponentMappings" object is different from the "ElementToComponentMappings" property on the "ModelElement" object.
This seems like a simple operation - get value of foreign key-linked tables, where you filter on what you get from the content of the FK tables, so hopefully I'm just missing something obvious here...?
The type is different because your newElement.ElementToComponentMappings is collection but your query returns only single instance.
You can try to use this:
context.ContextOptions.LazyLoadingEnabled = false;
var newElement = (from c in context.ModelElements
where c.ModelID == modelID && c.ElementNo == modelElementNo
select c).FirstOrDefault();
var mapping = (from m in context.ElementToComponentMappings
where m.ModelID == modelID
&& m.ElementNo == modelElementNo
&& m.version == version
select m).FirstOrDefault();
// now check if newElement.ElementToComponentMappings contains your single item
You can also try to use this:
context.ContextOptions.LazyLoadingEnabled = false;
var newElement = (from c in context.ModelElements
where c.ModelID == modelID && c.ElementNo == modelElementNo
select c).FirstOrDefault();
((EntityCollection<ElementToComponentMappings>)newElement.ElementToComponentMappings)
.CreateSourceQuery()
.FirstOrDefault(m.version == version); // You don't need to check FKs here
// now check if newElement.ElementToComponentMappings contains your single item
Neither of these methods works if your type is proxied and lazy loading is enabled because this expected automatic relation fixup will not mark navigation property as loaded (if you have lazy loading enabled next access to the property will trigger lazy loading and load all other entities).

Better way to check resultset from LINQ projection than List.Count?

Is there a better way to check if a LINQ projection query returns results:
IList<T> TList = db.Ts.Where(x => x.TId == 1).ToList(); // More canonical way for this?
if (TitleList.Count > 0)
{
// Result returned non-zero list!
string s = TList.Name;
}
You can use Any(), or perhaps more appropriately to your example, SingleOrDefault(). Note that if you are expecting more than one result and plan to use all of them, then it doesn't really save anything to use Any() instead of converting to a List and checking the length. If you don't plan to use all the results or you're building a larger query that might change how the query is performed then it can be a reasonable alternative.
var item = db.Ts.SingleOrDefault( x => x.TId == 1 );
if (item != null)
{
string s = item.Name;
...
}
or
var query = db.Ts.Where( x => x.Prop == "foo" );
if (query.Any())
{
var moreComplexQuery = query.Join( db.Xs, t => t.TId, x => x.TId );
...
}

Resources