Can this be done entirely via linq? - c#

I have a process where I identity rows in a list (unmatchedClient) then call a separate method to delete them (pingtree.RemoveNodes). This seems a little long winded and I could acheive the same thing by merely setting the value of the property "DeleteFlag" to true. But how do I set the value using linq?
var unmatchedClient = pingtree.Nodes.Where(x =>
_application.LoanAmount < x.Lender.MinLoanAmount ||
_application.LoanAmount > x.Lender.MaxLoanAmount ||
_application.LoanTerm < x.Lender.MinLoanTerm ||
_application.LoanTerm > x.Lender.MaxLoanTerm)
.Select(x => x.TreeNode)
.ToList();
pingtree.RemoveNodes(unmatchedClient);
Thanks in advance.

Like this?
pingtree.Nodes.Where(x =>
_application.LoanAmount < x.Lender.MinLoanAmount ||
_application.LoanAmount > x.Lender.MaxLoanAmount ||
_application.LoanTerm < x.Lender.MinLoanTerm ||
_application.LoanTerm > x.Lender.MaxLoanTerm)
.Select(x => x.TreeNode)
.ToList()
.ForEach(n=> n.DeleteFlag = true);

But how do I set the value using linq
You don't. Period.
Linq is a query language and querying is reading.
There is a back door that some people abuse to set values. In your case it would look like:
pingtree.Nodes.Where(...)
.Select(n => { n.DeleteFlag = true; return n; }
but this really is not done. Firstly because it is unexpected. Linq methods, including Select, are supposed to leave the source collection unchanged. Secondly because the statement itself does not do anything because of deferred execution. You'd have to force execution (e.g. by ToList()) to make it effective.
Maybe this looks like nitpicking, but when queries get a bit more complex it becomes a major point. It is not uncommon to do a projection (Select) followed by a filter (Where). You could have decided to do a filtering (where n.Deleted == false) after the projection for instance.
So, you query the objects using linq and then loop through them to do whatever needs to be done. ToList().ForEach() is one of the methods you can use to do that.
Side node: the back door that I showed would throw exceptions when using linq-to-sql or entity framework.

Related

Need to format complex foreach and IF statements to better formatted LINQ expression

I have a Foreach Statement as shown below
foreach (var fieldMappingOption in collectionHelper.FieldMappingOptions
.Where(fmo => fmo.IsRequired && !fmo.IsCalculated
&& !fmo.FieldDefinition.Equals( MMPConstants.FieldDefinitions.FieldValue)
&& (implicitParents || anyParentMappings
|| fmo.ContainerType == collectionHelper.SelectedOption.ContainerType)))
{
if (!collectionHelper.FieldMappingHelpers
.Any(fmh => fmh.SelectedOption.Equals(fieldMappingOption)))
{
requiredMissing = true;
var message = String.Format(
"The MMP column {0} is required and therefore must be mapped to a {1} column.",
fieldMappingOption.Label, session.ImportSource.CollectionLabel);
session.ErrorMessages.Add(message);
}
}
Can I break the above complex foreach and IF statements into better formatted LINQ expression. Also, performance wise which will be better. Please suggest.
Re : Change the Foreach to a Linq statement
Well, you could convert the two for loops into a LINQ Select, and since inside the loop, you've only one branch with an additional predicate, you could combine the predicate into the outer loop, something like so:
var missingFieldMappingOptions = collectionHelper.FieldMappingOptions
.Where(fmo => fmo.IsRequired && !fmo.IsCalculated
&& !fmo.FieldDefinition.Equals( MMPConstants.FieldDefinitions.FieldValue)
&& (implicitParents || anyParentMappings
|| fmo.ContainerType == collectionHelper.SelectedOption.ContainerType))
&& !collectionHelper.FieldMappingHelpers
.Any(fmh => fmh.SelectedOption.Equals(fmo)))
.Select(fmo =>
$"The MMP column {fmo.Label} is required and therefore" +
$" must be mapped to a {session.ImportSource.CollectionLabel} column.");
var requiredMissing = missingFieldMappingOptions.Any();
session.ErrorMessages.AddRange(missingFieldMappingOptions)
However, even LINQ can't make the filter clauses in the .Where disappear, so the LINQ Select is hardly more readable than the for loop, and isn't really any more performant either (there may be some marginal benefit to setting the requiredMissing flag and adding to session.ErrorMessages in one bulk chunk.
Performance
From a performance perspective, the below is problematic as it will be O(N log N) when combined in the outer for loop (fortunately .Any() will return early if a match is found, otherwise it would be as bad as N^2):
if (!collectionHelper
.FieldMappingHelpers.Any(fmh => fmh.SelectedOption.Equals(fieldMappingOption)))
Does FieldMappingOption have a unique key? If so, then suggest add a Dictionary<Key, FieldMappingOption> to collectionHelper and then use .ContainsKey(key) which approaches O(1), e.g.
!collectionHelper
.SelectedFieldMappingOptions.ContainsKey(fieldMappingOption.SomeKey)
Even if there isn't a unique key, you could use a decent HashCode on FieldMappingOption and key by that to get a similar effect, although you'll need to consider what happens in the event of a hash collision.
Readability
The Where predicate in the outer for loop is arguably messy and could use some refactoring (for readability, if not for performance).
IMO most of the where clauses could be moved into FieldMappingOption as a meta property, e.g. wrap up
fmo.IsRequired
&& !fmo.IsCalculated
&& !fmo.FieldDefinition.Equals(MMPConstants.FieldDefinitions.FieldValue)
into a property, e.g. fmo.MustBeValidated etc.
You could squeeze minor performance by ensuring the predicate returns false as soon as possible, by rearranging the && clauses which are most likely to fail first, but wouldn't do so if it impacts the flow of readability of the code.

Filter in linq with ID's in a List<int>

I need do a filter that request data with a parameter included in a list.
if (filter.Sc.Count > 0)
socios.Where(s => filter.Sc.Contains(s.ScID));
I try on this way but this not work, I tried also...
socios.Where( s => filter.Sc.All(f => f == s.ScID));
How I can do a filter like this?
socios.Where(s => filter.Sc.Contains(s.ScID));
returns a filtered query. It does not modify the query. You are ignoring the returned value. You need something like:
socios = socios.Where(s => filter.Sc.Contains(s.ScID));
but depending on the type of socios the exact syntax may be different.
In addition to needing to use the return value of your LINQ .Where(), you have a potential logic error in your second statement. The equivalent logic for a .Contains() is checking if Any of the elements pass the match criteria. In your case, the second statement would be
var filteredSocios = socios.Where( s => filter.Sc.Any(f => f == s.ScID));
Of course if you can compare object-to-object directly, the .Contains() is still adequate as long as you remember to use the return value.

trying to optimize if/else condition slows down program

I am currently trying to optimize a .net application with the help of the VS-Profiling tools.
One function, which gets called quite often, contains the following code:
if (someObjectContext.someObjectSet.Where(i => i.PNT_ATT_ID == tmp_ATT_ID).OrderByDescending(i => i.Position).Select(i => i.Position).Count() == 0)
{
lastPosition = 0;
}
else
{
lastPosition = someObjectContext.someObjectSet.Where(i => i.PNT_ATT_ID == tmp_ATT_ID).OrderByDescending(i => i.Position).Select(i => i.Position).Cast<int>().First();
}
Which I changed to something like this:
var relevantEntities = someObjectContext.someObjectSet.Where(i => i.PNT_ATT_ID == tmp_ATT_ID).OrderByDescending(i => i.Position).Select(i => i.Position);
if (relevantEntities.Count() == 0)
{
lastPosition = 0;
}
else
{
lastPosition = relevantEntities.Cast<int>().First();
}
I was hoping that the change would speed up the method a bit, as I was unsure wether the compiler would notice that the query is done twice and cache the results.
To my surprise the execution time (the number of inklusive samplings) of the method has not decreased, but even increased by 9% (according to the profiler)
Can someone explain why this is happening?
I was hoping that the change would speed up the method a bit, as I was unsure whether the compiler would notice that the query is done twice and cache the results.
It will not. In fact it cannot. The database might not return the same results for the two queries. It's entirely possible for a result to be added or removed after the first query and before the second. (Making this code not only inefficient, but potentially broken if that were to happen.) Since it's entirely possible that you want two queries to be executed, knowing that the results could differ, it's important that the results of the query not be re-used.
The important point here is the idea of deferred execution. relevantEntities is not the results of a query, it's the query itself. It's not until the IQueryable is iterated (by a method such as Count, First, a foreach loop, etc.) that the database will be queried, and each time you iterate the query it will perform another query against the database.
In your case you can just do this:
var lastPosition = someObjectContext.someObjectSet
.Where(i => i.PNT_ATT_ID == tmp_ATT_ID)
.OrderByDescending(i => i.Position)
.Select(i => i.Position)
.Cast<int>()
.FirstOrDefault();
This leverages the fact that the default value of an int is 0, which is what you were setting the value to in the event that there was not match before.
Note that this is a query that is functionally the same as yours, it just avoids executing it twice. An even better query would be the one suggested by lazyberezovsky in which you leveraged Max rather than ordering and taking the first. If there is an index on that column there wouldn't be much of a difference, but if there's not a an index ordering would be a lot more expensive.
You can use Max() to get maximum position instead of ordering and taking first item, and DefaultIfEmpty() to provide default value (zero for int) if there are no entities matching your condition. Btw you can provide custom default value to return if sequence is empty.
lastPosition = someObjectContext.someObjectSet
.Where(i => i.PNT_ATT_ID == tmp_ATT_ID)
.Select(i => i.Position)
.Cast<int>()
.DefaultIfEmpty()
.Max();
Thus you will avoid executing two queries - one for defining if there is any positions, and another for getting latest position.

How to use two conditions in Linq lambda which has different where clause

I want to query my item in table Items, where the last update of each item must be less than 91 days old (from last update till now) and the quantity > 0.
This is my code in the Model:
public IList<Item> GetAllProducts()
{
var ien_item = from i in this.DataContext.Items
orderby i.LastUpdated descending
select i;
return ien_item.ToList().Where(
s =>
HelperClasses.HelperClass.IsLastUpdate(s.LastUpdated.Value) == true
&&
(s => s.Quantity) > 0
)
.ToList();
}
Anyone can solve it? Thanks.
We don't really know what's not working here. EDIT: Merlyn spotted it; your lambda syntax is messed up. There's more to do here though.
However, I'd have thought you'd want this:
public IList<Item> GetAllProducts()
{
var lastUpdateLimit = DateTime.UtcNow.Date.AddDays(-91);
var query = from item in DataContext.Items
where item.Quantity > 0 && item.LastUpdated >= lastUpdateLimit
orderby item.LastUpdated descending
select item;
return query.ToList();
}
Note that this is able to do all the querying at the database side instead of fetching all the items and filtering at the client side. It does assume that HelperClasses.HelperClass.IsLastUpdate is simple though, and basically equivalent to the filter I've got above.
(One additional point to note is that by evaluating UtcNow.Date once, the result will be consistent for all items - whereas if your code evaluates "today" on every call to IsLastUpdate, some values in the query may end up being filtered against a different date to other values, due to time progressing while the query is evaluating.)
EDIT: If you really need to use HelperClasses.HelperClass.IsLastUpdate then I'd suggest:
public IList<Item> GetAllProducts()
{
var query = from item in DataContext.Items
where item.Quantity > 0
orderby item.LastUpdated descending
select item;
return query.AsEnumerable()
.Where(s => HelperClass.IsLastUpdate(s.LastUpdated.Value))
.ToList();
}
... then at least the quantity filter is performed at the database side, and you're not creating a complete buffered list before you need to (note the single call to ToList).
The problem is your lambda syntax. You're trying to define a second lambda while in the middle of defining a first lambda. While this is possible to do, and useful in some contexts, it is sort of an advanced scenario, and probably won't be useful to you until you know you need it.
Right now, you don't need it. Unless you know you need it, you don't need it :)
So -
Instead of what you've written:
.Where(
s =>
HelperClasses.HelperClass.IsLastUpdate(s.LastUpdated.Value) == true
&& (s => s.Quantity) > 0
)
Write this instead:
.Where(
s =>
HelperClasses.HelperClass.IsLastUpdate(s.LastUpdated.Value) == true
&& s.Quantity > 0 // Notice I got rid of the extra lambda here
)
If you're morbidly curious:
The compile error you got is because you didn't define your second lambda correctly. It redefined a variable you'd already used (s), and you were trying to check if a lambda was greater than zero. That makes no sense. You can only compare the result of a lambda to some value. It's like calling a function. You don't compare functions to numbers - you compare the result you get when calling a function to a number.
Easy ...
public IList<Item> GetAllProducts()
{
var ien_item =
from i in DataContext.Items
where
HelperClasses.HelperClass.IsLastUpdate(i.LastUpdated.Value)
&& s.Quantity > 0
orderby i.LastUpdated descending
select i;
return ien_item.ToList();
}
Linq to SQL: Methods are not allowed (linq is not magic and can not convert C# methods to TSQL)
http://msdn.microsoft.com/en-us/library/bb425822.aspx
Linq to Object: while looking the same, it is much more powerful than linq to SQL... but can not query SQL databases :)
http://msdn.microsoft.com/en-us/library/bb397919.aspx
Linq to XML: same as linq to Object, with xml object
http://msdn.microsoft.com/en-us/library/bb387098.aspx
Linq to Dataset: not the same as Linq to SQL !
http://msdn.microsoft.com/en-us/library/bb386977.aspx
Other linq providers:
http://en.wikipedia.org/wiki/Language_Integrated_Query

How to get LINQ top(1) when accessing related entities? .Take() and .FirstOrDefault() are not working?

Using LINQ to Entities with Entity Framework and C#:
I have a method that is called CanInactivateReason which checks that there are no active associated records before allowing the user to inactivate the reason. If the method returns false - then a 'DisabledDelete' image is displayed with a tooltip informing the user why they are unable to remove the entity in the grid instead of a 'Delete' image button. My problem is performance where the queries are returning all related objects instead of doing a top 1 for each navigation property.
.Take(1) extension method is not adding top(1) to my LINQ to Entities query - why??
Nor is .Count() > 0 or .Any() or .Take(1).ToArray().Any() or .FirstOrDefault() != null
Here is my method that returns bool so I'd rather the queries be top 1 - I tried each item below:
public bool CanInactivateReason(Reason reasonToInactivate)
{
bool canInactivate = true;
if (reasonToInactivate.ProductReasons.Select(pa => pa).Where(pa => pa.Inactive == false).Count() > 0)
{
canInactivate = false; // Still active products associated
}
if (reasonToInactivate.EnhancementReasons.Select(ea => ea).Where(ea => ea.Inactive == false).Any())
{
canInactivate = false; // Still active enhancements associated
}
if (reasonToInactivate.SuggestionReasons.Select(sa => sa).Where(sa => sa.Inactive == false).Take(1).ToArray().Any())
{
canInactivate = false; // still active suggestions associated
}
if ((reasonToInactivate.SessionProductReasons.Select(spr => spr).Where(spr => spr.Inactive == false).FirstOrDefault()) != null)
{
canInactivate = false; // Still active sessions associated
}
return canInactivate;
}
I am assuming this is due to accessing the related entities of my object, but what can I do to turn those queries into SQL generated top(1)?
Thanks in advance!
Working via navigation properties like this can cause issues. Depends on how you have LazyLoading configured, what your underlying model is (e.g. use of inheritance associations) and what the base query for reasonToInactivate is.
As a starting point I'd advise going back to basics with the query - just to prove the concept.
So something like (depending on your schema):
if(context.SuggestionReasonsEntitySet.Any(p => p.ReasonId == reasonToActivateId && p.IsActive))
{
canInactivate = false; // Still active enhancements associated
}
So side step the navigation property entirely. Navigation properties can be great, but they can also obscure what should be very simple queries. Personally I stick to them for select queries (i.e. to avoid having to do joins when creating a projection), but avoid them for filtering logic - just because I've been bitten a few times like this when using inheritance associations.
If that works then you can try working back towards your navigation property solution to find the point at which things go wrong.
If this isn't helpful - is the generated SQL from your queries above short enough to post (and be understood)? If so can you post it?

Categories

Resources