Load collection except another collection using linq - c#

I have this search method:
public List<Employeees> AutoSuggestEmployeee(string keyword,
long employeeeTypeId, int count)
{
return context.Employeees.Where(
x => x.EmployeeeName.Contains(keyword)
&& x.EmployeeeTypeId == employeeeTypeId)
.Take(count).ToList();
}
I have another collection of Employeees, say "BadEmployeees", what I want is using the same previous method to return all Employeees except "BadEmployeees".
I tried to write it like this:
return context.Employeees.Where(
x => x.EmployeeeName.Contains(keyword)
&& x.EmployeeeTypeId == employeeeTypeId)
.Except(BadEmployeees).Take(count).ToList();
But it is giving an exception that Except can just work with data types such as Int, Guid,...

The Except method does a comparison, so it has to know how to compare the objects. For simple types there are standard comparisons, but for complex types you need to supply an equality comparer that compares the relevant data in the object.
Example:
class EmployeeComparer : IEqualityComparer<Employeees> {
public bool Equals(Employeees x, Employeees y) {
return x.Id == y.Id;
}
public int GetHashCode(Employeees employee) {
return employee.Id.GetHashCode();
}
}
Usage:
return
context.Employeees
.Where(x => x.EmployeeeName.Contains(keyword) && x.EmployeeeTypeId == employeeeTypeId)
.Except(BadEmployeees, new EmployeeComparer())
.Take(count)
.ToList();

If you're happy to retrieve all the data and then perform the "except", that's relatively easy:
return context.Employees
.Where(x => x.EmployeeName.Contains(keyword)
&& x.EmployeeTypeId == employeeeTypeId)
// Limit the data *somewhat*
.Take(count + BadEmployees.Count)
// Do the rest of the query in-process
.AsEnumerable()
.Except(BadEmployees)
.Take(count)
.ToList();
Alternatively:
// I'm making some assumptions about property names here...
var badEmployeeIds = badEmployees.Select(x => x.EmployeeId)
.ToList();
return context.Employees
.Where(x => x.EmployeeName.Contains(keyword)
&& x.EmployeeTypeId == employeeeTypeId)
&& !badEmployeeIds.Contains(x.EmployeeId))
.Take(count)
.ToList();

Related

Combining Where and OrderByDescending Using a Custom Method

I'm trying to use a custom method for ordering but I also want to use that same custom method to only return results that match a certain value. I realize that the code below works but I was hoping there was a way to combine both methods to hopefully speed up the process.
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var bestList = inputList.Where(x => x != null && CalculateAverage(x) > 0).
OrderByDescending(x => CalculateAverage(x)));
return bestList;
}
public decimal CalculateAverage(List<decimal> inputList)
{
return inputList.Average();
}
As far as I understand you want to prevent recalculation of average, so you can use Select to create a temporary tuple containing average and original list, for example like that:
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var bestList = inputList
.Where(x => x != null )
.Select(x => (x, Avg: CalculateAverage(x)))
.Where(x => x.Avg > 0)
.OrderByDescending(x => x.Avg)
.Select(x => x.x);
return bestList;
}
The way to avoid performing the potentially expensive computation multiple times is to project the sequence into a new value that includes the list and the computation. This is simpler and easier with query syntax than method syntax:
public IEnumerable<List<decimal>> GetBestList(List<List<decimal>> inputList)
{
var query = from list in inputList
where list != null
let average = CalculateAverage(list)
where average > 0
orderby average
select list;
}

Selecting in LINQ based on value in navigation property value

I have a list of vehicles that are in various categories..
public IList<Vehicle> GetAllByCat(int compId, short catId)
{
var _vehicles = Context.Vehicle
.Where(i =>
i.CompanyId == compId &&
i.VehicleCategories.Any(o => o.VehicleCategoryId == catId))
.ToList();
return _vehicles;
}
But it returns null (and I get an text in debug that _vehicles does not exists in current Context)
I have searched extensively for an answer but not found anything that helps.
Any idea?
You have to get the VehicleCategories first then you can use Any method on it. so one way would be using eager loading to get the related categories then search within them.
public IList<Vehicle> GetAllByCat(int compId, short catId)
{
var _vehicles = Context.Vehicle
.Include(x => x.VehicleCategories)
.Where(i =>
i.CompanyId == compId &&
i.VehicleCategories.Any(o => o.VehicleCategoryId == catId))
.ToList();
return _vehicles;
}

Conditionally Insert Where Clause in LINQ Method Syntax with Select Many

Not sure if this possible using the LINQ method chain syntax or at all, but I would like to conditionally insert a where class in the chain if a parameter passed to the method is not null.
Here is the redundant code I would like to simplify:
public ICollection<Organization> getNetworkServiceRecipients(string serviceId = null)
{
ICollection<Organization> children = get_all_children();
if (serviceId != null)
{
return children.SelectMany(o => o.receives_these_services)
.Where(s => s.serviceId == serviceId)
.Select(o => o.serviceRecipient)
.Distinct()
.ToList();
}
else
{
return (children.SelectMany(o => o.receives_these_services)
.Select(o => o.serviceRecipient)
.Distinct()
.ToList());
}
}
I have been trying to insert the where clause programmatically based on whether serviceId is null or not. All of the answers I have found where based on the query syntax, but I couldn't translate. Any suggestions?
If you don't want to have it in the actual where query as dotnetom mentioned, you can do something like this:
public ICollection<Organization> getNetworkServiceRecipients(string serviceId = null)
{
var services = get_all_children().SelectMany(o => o.receives_these_services);
if (serviceId != null)
services = services.Where(s => s.serviceId == serviceId);
return services.Select(o => o.serviceRecipient)
.Distinct()
.ToList();
}
You can try this approach:
public ICollection<Organization> getNetworkServiceRecipients(string serviceId = null)
{
ICollection<Organization> children = get_all_children();
return children.SelectMany(o => o.receives_these_services)
.Where(s => serviceId == null || s.serviceId == serviceId)
.Select(o => o.serviceRecipient)
.Distinct()
.ToList();
}
In this case if your variable serviceId is null then only the first part of where condition would be executed, otherwise first part would be true and only second condition would matter.
Similar answer as dotnetom, but uses a ternary to determine which lambda to use
so that serviceId == null doesn't get executed on a per-item basis.
return children.SelectMany(o => o.receives_these_services)
.Where( serviceId == null ? (_ => true) : (s => s.serviceId == serviceId))
.Select(o => o.serviceRecipient)
.Distinct()
.ToList();

Linq result if null then zero

How do I write something like this:
int result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count)) ?? 0;
Where it will return the sum value unless linq does not find anything in which case it will return 0.
EDIT: The field is not null-able.
EDIT 2: I am using Entity Framework.
You were very close with your original query. You only needed to cast your Count variable:
int result = database
.Where(x => x.Name == "Criteria")
.Sum(x => (int?)x.Count) ?? 0;
Doing it this way would be a little more efficient and elegant than wrapping it in a Try/Catch.
I suspect you are using Entity Framework. If you were just using Linq-to-Objects, the solutions everybody else have provided would have worked.
This should work fine (no need for ?? 0):
var result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count))
Unless you want to check if x itself is null or not:
var result = database
.Where(x => x != null)
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count))
You can just write:
int result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count));
The Enumerable.Sum method already returns zero on no results. From the documentation:
returns zero if source contains no elements.
This should work just fine:
var result = database.Where(x => x.Name == "Criteria").Sum(x => x.Count));
If no elements are returned by the Where function then the Sum function will return 0.
All of the Linq functions that return an IEnumerable<T> will return an empty collection instead of null.
Use the Aggregate extension method where 0 is a seed value
int sum = database.Where(x=>x.Name == "Criteria")
.Aggregate(0, (total, next) => total +=next);
I did it in a way that no one is going to like but garrantee to work 100% of the time, behold!
int result = 0;
try{
result = database
.Where(x => x.Name == "Criteria")
.Sum(x => x.Count));
} catch (Exception e){ }

FindAll conditions with different relevance

About the homework:
There are casters(witch(0)/fairy(1)) and they have spellpower(int). I stored them in a list.
I'm to find the best of both types. (There can be multiple casters with the same spellpower)
I've come up with this code, but there is a problem. If the caster with the most spellpower is a 1, then the first FindAll won't return anything, because it tries to find the caster with type 0 AND with the most spellpower. How can I get a list containing type 0 caster(s) with the most spellpower, if the caster with the most overall spellpower is type 1?
private List<Caster> BestCasters()
{
List<Caster> temp = new List<Caster>();
temp = casters.FindAll(x => x.SpellPower == casters.Max(y => y.SpellPower) && (x.TypeOfCaster == 0));
temp.AddRange(casters.FindAll(x => x.SpellPower == casters.Max(y => y.SpellPower) && (x.TypeOfCaster == 1)));
temp.OrderBy(x => x.TypeOfCaster).ThenBy(y => y.CasterName);
return temp;
}
The LINQ GroupBy behavior is perfect for this:
var strongest_casters = casters.GroupBy(c => c.TypeOfCaster)
.Select(grp => grp.OrderByDescending(x => x.SpellPower)
.First()
);
Or to return more than one of each type:
var strongest_casters = casters.GroupBy(c => c.TypeOfCaster)
.SelectMany(grp => grp.Where(y.SpellPower == grp.Max(x => x.SpellPower))
);
private List<Caster> BestCasters()
{
var witches = casters.Where(x => x.TypeOfCaster == 0).ToList();
var fairies = casters.Where(x => x.TypeOfCaster == 1).ToList();
int witchesMax = witches.Max(x => x.SpellPower);
int fairiesMax = fairies.Max(x => x.SpellPower);
var temp = witches.Where(x => x.SpellPower == witchesMax).ToList();
temp.AddRange(fairies.Where(x => x.SpellPower == fairiesMax));
return temp.OrderBy(x => x.TypeOfCaster).ThenBy(y => y.CasterName).ToList();
}
If you have to use FindAll like this you should invoke the Max on a subset only containing the casters of the right kind. Of course it would make more sense to split the initial list first and then fetch the strongest caster of each kind.
Since you did not tell what exactly you have to do I can only hope that you are allowed to split :-)

Categories

Resources