Converting foreach into single pass Linq Group By - c#

I have a collection of items that may have one or more properties.
I want to use a Linq statement (query syntax) to filter and group that list in a single pass such that it checks for a mandatory property first, and then looks for an optional property.
I can't figure out how to do this without first filtering the list and then going over it again to look for the optional property.
Here's how I could do it in a foreach statement. (Not efficient, just illustrating what I need.)
var usefulBoxes = new Dictionary<Box, int>;
foreach (box in cart)
{
bool boxNeeded = false;
int someCounter = 0;
foreach (prop in box.Properties)
{
if (prop == neededProp)
boxNeeded = true;
else if (boxNeeded && prop == optionalProp)
someCounter += 1;
}
if (boxNeeded)
usefulBoxes.Add(box, someCounter)
}

var usefulBoxes= box.where(b=>b.boxProperties.prop==neededProp).ToList();

this your demo linq:
var usefulBoxes = new Dictionary<List<int>, int>();
foreach (var boxNeeded in from box in cart let boxNeeded = false let someCounter = 0 from prop in box.Properties.Where(prop => prop == neededProp) select boxNeeded)
{
if (prop == neededProp)
boxNeeded = true;
else if (boxNeeded && prop == optionalProp)
someCounter += 1;
if (boxNeeded)
usefulBoxes.Add(box, someCounter);
}

Related

Change loop to Linq C#

One of my function in C# required me to filter some value.So, I try it by using a lot of loop in it. It works but doesn't look like effecient.Any idea on how to convert code below to LinQ?
Any help is appreciated.
var object1 = JsonConvert.DeserializeObject<List<string>>(object1json);
foreach (var item1 in table1)
{
if (item1.Code == InputCode)
{
for (int i = 0; i < object1.Count(); i++)
{
tempData temp = new tempData();
foreach (var item2 in item1.List)
{
if (item2.Code == object1[i])
{
temp.Code = item2.Code;
temp.Description = item2.Description;
}
}
if(temp.Code != null || temp.Description != null)
final.Add(temp);
}
}
}
If you want your code to be more efficient, as pointed out in the comments, converting it to Linq isn't really going to help. It's still the same logic, just written in a different way. If you're going for readability, it can be improved with just a few changes:
foreach (var item1 in table1.Where(i => i.Code == InputCode))
{
foreach (var code in object)
{
// This could be SingleOrDefault, I don't know if you have duplicates in the list or not
var item2 = item1.List.LastOrDefault(i => i.Code == code);
if(item2 != null)
{
final.Add(new tempData
{
Code = item2.Code,
Description = item2.Description,
});
}
}
}
If you convert the whole thing to Linq:
var final = table1.Where(i => i.Code == InputCode)
.SelectMany(item1 => object.Select(code => item1.List.LastOrDefault(i => i.Code == code))
.Where(item2 => item2 != null)
.Select(item2 => new tempData
{
Code = item2.Code,
Description = item2.Description,
})
.ToList();
Personally, I prefer the first option, as it's a bit easier to read.
I guess what you post is sample code instead of actual code otherwise it would be better to avoid keyword like object in C#. Anyway,
var final = table1.Where(item1 => item1.Code == InputCode)
.SelectMany(item1 => item1.List)
.Where(item2 => #object.Contains(item2.Code))
.Where(temp => temp.Code != null || temp.Description != null)
.Select(item2 => new tempData()
{
Code = item2.Code,
Description = item2.Description
});

Reduce execution time in two foreach loop

//Get linked claim for children list from ClaimLink
foreach (var claim in processedClaims)
{
if (claim.Children == null)
{
claim.Children = new List<Claim>();
}
var claimRelationList = newClaimLink.Where(k=> k.ClaimLinkId == claim.Id).ToList();
if (claimRelationList.Any())
{
//Get the claim for all selected ClaimLink
foreach (var claimLink in claimRelationList)
{
var newChildren = claims.Where(p => p.Id == claimLink.ClaimId).ToList();
claim.Children = claim.Children != null && claim.Children.Any() ? newChildren.Concat(claim.Children) : newChildren;
}
}
}
I want to reduce the execution time in above two foreach loop. Is there have better way than this?
You can remove the if (claimRelationList.Any()) condition as the following foreach will not iterate over an empty list.
You already know that claim.Children is not null within that for each because you initialized it above. So I would replace
claim.Children = claim.Children != null && claim.Children.Any() ? newChildren.Concat(claim.Children) : newChildren;
With
claim.Children.AddRange (newChildren).
Multiple options i see at first sight:
don't call ToList(), not necessary here unless you have some sort of query executing in the background, see comment of #Shelby115
newClaimLink.Where(k=> k.ClaimLinkId == claim.Id) could be replaced by a dictionary created beforehand like newClaimLink.ToDictionary(k=> k.ClaimLinkId)
same goes for claims.Where(p => p.Id == claimLink.ClaimId)
Here is my best answer for above question.
//Get linked claim for children list from ClaimLink
foreach (var claim in processedClaims)
{
if (claim.Children == null)
{
claim.Children = new List<Claim>();
}
var claimRelationList = newClaimLink.Where(k=> k.ClaimLinkId == claim.Id).ToList();
if (claimRelationList.Any())
{
//Get the claim for all selected ClaimLink
var newChildren = claims.Where(o => claimRelationList.Select(p => p.ClaimId).Contains(o.Id));
if (newChildren.Any())
{
claim.Children = claim.Children != null && claim.Children.Any() ? newChildren.Concat(claim.Children) : newChildren;
}
}
}
I resolved my problem with lambda expression for the inner foreach loop then I could have to reduce the execution time more than 250ms.
Thank you for the helping me

LINQ query to update property of a list

I am trying to convert the following code to linq:
for (int i = 0; i < List.Count;i++ )
{
List[i].IsActive = false;
if(List[i].TestList != null)
{
for(int j = 0;j<List[i].TestList.Count;j++)
{
List[i].TestList[j].IsActive = false;
}
}
}
I tried the following query :
(from s in List select s).ToList().ForEach((s) =>
{
s.IsActive = false;
(from t in s.TestList where t != null select t).ToList().ForEach((t) =>
{
t.IsActive = false;
});
});
But i get an error when TestList is null in the list. I am not sure what I am doing wrong here.
If your original(no LINQ) code is worked.
Then you missed one line, which check for null of TestList before iterating items
(from s in List select s).ToList().ForEach((s) =>
{
s.IsActive = false;
if(s.TestList != null) //This check of TestList was missing
(from t in s.TestList where t != null select t).ToList().ForEach((t) =>
{
t.IsActive = false;
});
});
You are selecting lists that are null
where t == null
Should the condition be
where t != null
A simple approach. No need to check for null.
s.ForEach((x)=>
{
x.IsActive = false;
x.TestList.Foreach((t)=>{t.IsActive = false});
});
You don't necessarily need an inner loop since it looks like you're deactivating all nested TestList items. You can just have two separate loops:
foreach(var item in List)
item.IsActive = false;
foreach(var item in List.Where(x => x.TestList != null).SelectMany(x => x.TestList))
item.IsActive = false;
Note that SelectMany "flattens" the inner lists into a single IEnumerable<T>.

Linq to objects - Search collection with value from other collection

I've tried to search SO for solutions and questions that could be similar to my case.
I got 2 collections of objects:
public class BRSDocument
{
public string IdentifierValue { get; set;}
}
public class BRSMetadata
{
public string Value { get; set;}
}
I fill the list from my datalayer:
List<BRSDocument> colBRSDocuments = Common.Instance.GetBRSDocuments();
List<BRSMetadata> colBRSMetadata = Common.Instance.GetMessageBRSMetadata();
I now want to find that one object in colBRSDocuments where x.IdentifierValue is equal to the one object in colBRSMetadata y.Value. I just need to find the BRSDocument that matches a value from the BRSMetadata objects.
I used a ordinary foreach loop and a simple linq search to find the data and break when the value is found. I'm wondering if the search can be done completely with linq?
foreach (var item in colBRSMetadata)
{
BRSDocument res = colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value);
if (res != null)
{
//Do work
break;
}
}
Hope that some of you guys can push me in the right direction...
Why not do a join?
var docs = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select d;
If there's only meant to be one then you can do:
var doc = docs.Single(); // will throw if there is not exactly one element
If you want to return both objects, then you can do the following:
var docsAndData = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select new
{
Doc = d,
Data = m
};
then you can access like:
foreach (var dd in docsAndData)
{
// dd.Doc
// dd.Data
}
Use Linq ?
Something like this should do the job :
foreach (var res in colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).Where(res => res != null))
{
//Do work
break;
}
If you are just interested by the first item, then the code would be :
var brsDocument = colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).FirstOrDefault(res => res != null);
if (brsDocument != null)
//Do Stuff

Adapt function to included Field Names C# 2012.net 3.5

Is their anyway of changing the below to included all fields names only and values one thing i noticted that when testing this it also brought other information about the entitiy back im only wanting the fields that have been entered or changed by the user??
public static string ObjectToNotes(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj", "Value can not be null or Nothing!");
}
StringBuilder sb = new StringBuilder();
Type t = obj.GetType();
PropertyInfo[] pi = t.GetProperties();
for (int index = 0; index < pi.Length; index++)
{
sb.Append(pi[index].GetValue(obj, null));
if (index < pi.Length - 1)
{
sb.Append(Environment.NewLine);
}
}
return sb.ToString();
}
Right now this will save out the values for the entity only passed
As you can see from the image above the code is getting the values ok and fields but just not any drop downs related text
Help
Need more help with this how do i get the value of reference lookup values using the above method its only priting out the entity reference not the actual text value can this be done
Assuming by entered by the user you mean not having a string representation that is null or empty then try the following:
var properties = t.GetProperties();
var values = properties.Select(p => p.GetValue(obj, null)).Where(v => v != null && !string.IsNullOrEmpty(p.ToString());
var result = string.Join(Environment.NewLine, values);
In order to determine whcih fields have changed, you will need to pass two objects. One representing the entity in its pre-changed state and the other in its post-changed state and compare the properties:
var properties = t.GetProperties();
var before = properties.Select(p => new { property = p, value = p.GetValue(prechange, null) }).Where(v => v.value != null && !string.IsNullOrEmpty(p.value.ToString()).ToDictionary(p => p.property.Name, p => p.value);
var after = properties.Select(p => new { property = p, value = p.GetValue(postchange, null) }).Where(v => v.value != null && !string.IsNullOrEmpty(p.value.ToString()).ToDictionary(p => p.property.Name, p => p.value);
// You can then compare the keys / values of before and after dictionaries here

Categories

Resources