How do I Take everything in a collection using Linq? - c#

I've got a method which can accept an optional int? value as a number of items to Take from a collection. I want to return all items if a null value is passed. Right now I have to duplicate my query to accomplish this
if(take == null)
{
x = db.WalkingDeadEps.Where(x => x.BicyclesCouldHaveSavedLives == true).ToList()
}
else
{
x = db.WalkingDeadEps.Where(x => x.BicyclesCouldHaveSavedLives == true).Take(take).ToList()
}
Is there a simpler way? Something like this?
.Take(take != null ? take : "all")

with Linq you have the option to store your query in variables. it will not be executed until you call ToList or equivalent methods on it.
var query = db.WalkingDeadEps.Where(x => x.BicyclesCouldHaveSavedLives == true);
x = take.HasValue ? query.Take(take.Value).ToList() : query.ToList();

Related

Ternary operator add or not where conditions in linq

I have this query:
if(!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(name))
{
return this.context.Table.Where(
x => EF.Functions.Contains(x.Code, $"\"{code}\"")
&& EF.Functions.Contains(x.Name, $"\"{name}\""));
}
else if(string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(name))
{
return this.context.Table.Where(x => EF.Functions.Contains(x.Name, $"\"{name}\""));
}
else
{
return this.context.Table.Where(x => EF.Functions.Contains(x.Code, $"\"{code}\""));
}
I tried to do it again using the ternary operators then check if the string is not null or empty and if so add the where clause or not
I tried such a thing but obviously expects that after the "?" there is the alternative of ":"
return this.context.Table.Where(
x => !string.IsNullOrEmpty(code)
? EF.Functions.Contains(x.Code, $"\"{code}\"")
&& !string.IsNullOrEmpty(name)
? EF.Functions.Contains(x.Name, $"\"{name}\""));
Since unlike the example in my case I have to check 8 different input parameters that if not passed must not be used in the where for the controls, I wanted to avoid filling the code of many if cases and rewriting the query n times for the different combinations, is there a way or should I resign myself?
You can just return true for any you don't want to check for like below
!string.IsNullOrEmpty(code) ? EF.Functions.Contains(x.Code, $"\"{code}\"") : true;
This means if the string is null or empty then it will return true which should provide the behaviour you're expecting.
Do not use ternary operators for combining query. EF will create non optimal SQL.
Usually such task is done in the following way:
var query = this.context.Table.AsQueryable();
if (!string.IsNullOrEmpty(code))
{
query = query.Where(
x => EF.Functions.Contains(x.Code, $"\"{code}\""));
}
if (!string.IsNullOrEmpty(name))
{
query = query.Where(
x => EF.Functions.Contains(x.Name, $"\"{name}\""));
}
var result = query.ToList();

Non-static method requires a target when checking paramater for null values

In the below code, if i uncomment the line starting with queItem.RequestedMap == null I get
Non-static method requires a target.
If I then instead rewrite it as it is now, with a .ToList() and then doing the same where query after that it works. This tells me that .net is not able to translate the null check of queItem.RequestedMap == null into something sql specific.
queItem is an object paramater passed to the method containing this code.
Is there a way I can write this without retrieving the data back to .net and then doing another where? The existing answers I found just said to remove such expressions from the lambda query, which I dont want to do.
var gameToJoin = db.Games
//.Where(x =>
// (queItem.RequestedMap == null || x.Map.Id == queItem.RequestedMap.Id) // no map preference or same map
//)
.ToList()
.Where(x =>
queItem.RequestedMap == null
|| x.Map.Id == queItem.RequestedMap.Id) // no map preference or same map)
.FirstOrDefault();
Edit: Also, in the real query expression there are multiple other expressions in the first .Where that is commented here, they always need to be checked.
var gameToJoin = db.Games.AsQueryable();
// add the where's that always need to be checked.
if (queItem.RequestMap != null)
{
gameToJoin = gameToJoin.Where(x => x.Map.Id = queItem.RequestMap.Id);
}
var result = gameToJoin.ToList();
Or if you'd rather use FirstOrDefault()
var gameToJoin = db.Games.AsQueryable();
// add the where's that always need to be checked.
if (queItem.RequestMap != null)
{
var result = new List<Game>();
var game = gameToJoin.FirstOrDefault(x => x.Map.Id = queItem.RequestMap.Id);
if (game != null)
{
result.Add(game);
}
return result;
}
return gameToJoin.ToList();
Wouldn't this produce what you want? I don't see a reason that queItem.RequestedMap check should be a part the LINQ, because it is not a part the database.
Game gameToJoin = null;
if(queItem.RequestedMap == null)
{
gameToJoin = db.Games
.Where(x => x.Map.Id == queItem.RequestedMap.Id)
.FirstOrDefault;
}

Remove list items where property = myValue

I've got a problem. I have a list of objects (vehicle).
List<Vehicle> vehicleList = Vehicle.GetVehiclesFromDatabase();
And now I want something like this:
vehicleList.Remove(where vehicleList.Brand == "Volkswagen");
I hope I could explain what my problem is.
Many thanks in advance!
You can use List<T>.RemoveAll:
int recordsRemoved = vehicleList.RemoveAll(v => v.Brand == "Volkswagen");
The method takes a Predicate<T> (= Func<T,bool>), which will remove all items for which the predicate returns true.
For you this is equal to the following method:
bool Filter(Vehicle vehicle)
{
return vehicle.Brand == "Volkswagen";
}
You can use linq to do this, like so
vehicleList = vehicleList.Where(v => v.Brand != "Volkswagen").ToList();
You can also do this with a RemoveAll
vehicleList.RemoveAll(v => v.Brand == "Volkswagen");
You can do like this
var itemToRemove = vehicleList.Single(r => r.Brand == "Volkswagen");
resultList.Remove(itemToRemove);
When you are not sure the item really exists you can use SingleOrDefault. SingleOrDefault will return null if there is no item (Single will throw an exception when it can't find the item). Both will throw when there is a duplicate value (two items with the same id).
var itemToRemove = vehicleList.SingleOrDefault(r => r.Brand == "Volkswagen");
if (itemToRemove != null)
resultList.Remove(itemToRemove);

Proper way to use LINQ for this type of query?

I was originally using a foreach loop and then for each element in the loop, I perform a LINQ query like so:
foreach (MyObject identifier in identifiers.Where(i => i.IsMarkedForDeletion == false))
{
if (this.MyEntities.Identifiers.Where(pi => identifier.Field1 == pi.Field1 && identifier.Field2 == pi.Field2 && identifier.Field3 == pi.Field3).Any())
{
return false;
}
}
return true;
Then I modified it like so:
if (identifiers.Any(i => !i.IsMarkedForDeletion && this.MyEntities.Identifiers.Where(pi => i.Field1 == pi.Field1 && i.Field2 == pi.Field2 && i.Field3 == pi.Field3).Any()))
{
return false;
}
return true;
My question is this still the wrong way to use LINQ? Basically, I want to eliminate the need for the foreach loop (which seems like I should be able to get rid of it) and also make the DB query faster by not performing separate DB queries for each element of a list. Instead, I want to perform one query for all elements. Thanks!
You can change your code in this way, and it will be converted to SQL statement as expected.
To prevent runtime errors during transformation, it will be better to save DBSet to the IQueryable variable; identifiers should be IQueryable too, so you should change your code into something like this (to be honest, Resharper converted your foreach in this short labda):
IQueryable<MyObject2> identifiers = MyEntities.Identifiers.Where(i => i.IsMarkedForDeletion == false);
IQueryable<MyObject2> ids = MyEntities.Identifiers.AsQueryable();
return identifiers.All(identifier => !ids.Any(pi => identifier.Field1 == pi.Field1 && identifier.Field2 == pi.Field2 && identifier.Field3 == pi.Field3));
If identifiers is in memory collection you can change code in this way (hope that fields are string):
IQueryable<MyObject2> ids = MyEntities.Identifiers.AsQueryable();
string[] values = identifiers.Where(i => i.IsMarkedForDeletion == false).Select(i => String.Concat(i.Field1, i.Field2, i.Field3)).ToArray();
return !ids.Any(i => values.Contains(i.Field1 + i.Field2 + i.Field3));
Unfortunately your modified version will be executed exactly the same way (i.e. multiple database queries) as in the original foreach approach because EF does not support database query with joins to in memory collection (except for primitive and enumeration type collections), so if you try the most logical way
bool result = this.MyEntities.Identifiers.Any(pi => identifiers.Any(i =>
!i.IsMarkedForDeletion &&
i.Field1 == pi.Field1 && i.Field2 == pi.Field2 && i.Field3 == pi.Field3));
you'll get
NotSupportedException: Unable to create a constant value of type 'YourType'. Only primitive types or enumeration types are supported in this context.
The only way to let EF execute a single database query is to manually build a LINQ query with Concat per each item from in memory collection, like this
IQueryable<Identifier> query = null;
foreach (var item in identifiers.Where(i => !i.IsMarkedForDeletion))
{
var i = item;
var subquery = this.MyEntities.Identifiers.Where(pi =>
pi.Field1 == i.Field1 && pi.Field2 == i.Field2 && pi.Field3 == i.Field3);
query = query != null ? query.Concat(subquery) : subquery;
}
bool result = query != null && query.Any();
See Logging and Intercepting Database Operations of how to monitor the EF actions.
I would use it as follows:
if (identifiers.Where(i => !i.IsMarkedForDeletion &&
this.MyEntities.Identifiers.Field1 == i.Field1 &&
this.MyEntities.Identifiers.Field2 == i.Field2 &&
this.MyEntities.Identifiers.Field3 == i.Field3).Any()))
{
return false;
}
return true;
I hope this helps. Even though it is more to type out, it is more understandable and readable then using multiple 'where' statements.

Using variables in the lambda

I am using Linq to filter some things and some times I need to use reflection to get the value. Here is the example:
//...
PropertyType[] properties = myType.GetProperties();
var filtered = properties.Where(p=>p.PropertyType==typeof(MyMetaData)
&& ((MyType)p.GetValue(obj)).Name=="name"
&& ((MyType)p.GetValue(obj)).Length==10
).ToList();
//...
In my example I am using GetValue() method more than one time. Is there way if I can use variable to store it? I think that will help with performance.
It looks like that to include some variable in a LINQ we have to use the expression query, not method query, like this:
var filtered = (from x in properties
let a = (x.PropertyType is MyType) ? (MyType) x.GetValue(obj) : null
where a != null && a.Name == "name" && a.Length == 10).ToList();
I think this also works for method query with some Select:
var filtered = properties.Select(p=> new {p, a = (p.PropertyType is MyType) ? (MyType) p.GetValue(obj) : null})
.Where(x=>x.a != null && x.a.Name == "name" && x.a.Length == 10)
.Select(x=>x.p).ToList();
Something like following should work with method expression(lambda syntax)
var filtered = properties.Where(p =>
{
if(!p.PropertyType is MyMetaData)
{
return false;
}
var item = (MyType) p.GetValue(obj);
return item.Name == "name"
&& item.Length == 10
}
).ToList();

Categories