Net 6.0
Our company uses a metric layout that takes records that look like this
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
and transforms it into this
AAA|BLANK|BLANK|BLANK
AAA|0000A|BLANK|BLANK
AAA|0000B|BLANK|BLANK
AAA|0000A|000AA|BLANK
AAA|0000A|000AB|BLANK
AAA|0000B|000BA|BLANK
AAA|0000B|000BB|BLANK
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AA|0003A
AAA|0000A|000AA|0004A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
I have a custom object
public class HierarchyStructure
{
public string Employer { get; set; }
public string Level1 { get; set; }
public string Level2 { get; set; }
public string Level3 { get; set; }
public string Level4 { get; set; }
public bool isRxlvl3 { get; set; }
public bool isExpired { get; set; }
public string lvl4SubType { get; set; }
public HierarchyStructure(string employer,
string? level1 = null,
string? level2 = null,
string? level3 = null,
string? level4 = null,
bool isRxlvl3 = false,
bool isExpired = false,
string? lvl4SubType = null)
{
this.Employer = employer;
this.Level1 = string.IsNullOrEmpty(level1) ? string.Empty : level1;
this.Level2 = string.IsNullOrEmpty(level2) ? string.Empty : level2;
this.Level3 = string.IsNullOrEmpty(level3) ? string.Empty : level3;
this.Level4 = string.IsNullOrEmpty(level4) ? string.Empty : level4;
this.isRxlvl3 = isRxlvl3;
this.isExpired = isExpired;
this.lvl4SubType = string.IsNullOrEmpty(lvl4SubType) ? string.Empty : lvl4SubType;
}
}
I am trying to populate from a static level into a formatted output list
private List<HierarchyStructure> AllLevels => _allLevels;
private List<HierarchyStructure> LocationLevels => _LocationLevels;
I developed methods for each level call, but I can't figure out how to merge them into one method that either calls itself recursively or dynamically selects the right values. I do not think it is possible to combine these due to the different comparisons and return values.
public bool GetDistinctEmployers()
{
var selectedLevels = (from levels in AllLevels
select new HierarchyStructure(levels.Employer));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel1(string emp)
{
var selectedLevels = (from levels in AllLevels
where levels.Employer == emp
select new HierarchyStructure(levels.Employer,levels.Level1));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel2(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level1 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel3(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level2 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel4(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level3 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
When I try and combine all these together, it ends up being soooo nested and I feel like there must be a better way to accomplish this. I am fairly certain this level of nesting will error my quality gate on my pipeline.
public void ProcessLevels()
{
if(GetDistinctEmployers())
{
foreach(string employer in AllLevels.Select(x => x.Employer))
{
if(GetDistinctLevel1(employer))
{
foreach(string level1 in AllLevels.Select(x => x.Level1))
{
if(GetDistinctLevel2(level1))
{
foreach(string level2 in AllLevels.Select(x => x.Level2))
{
if(GetDistinctLevel3(level2))
{
foreach(string level3 in AllLevels.Select(x => x.Level3))
{
if(GetDistinctLevel4(level3))
{
SaveData();
}
}
}
}
}
}
}
}
}
}
I am also getting another inline quality flag that my expressions in this block can be shortened using LINQ. I dont understand how my one line can be simplified.
...AllLevels.Select(x => x.Employer)
...AllLevels.Select(x => x.Level1)
... etc
A view of the data output I am trying to get.
Update: Thank you for the help offered - using the answers provided in this thread, I have coded my solution like this eliminating a lot of loops.
public void ProcessLevels()
{
var employers = AllLevels.Where(x=> !x.isExpired ).Select(x => x.Employer).Distinct();
var lvl1 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1 }).Distinct();
var lvl2 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2 }).Distinct();
var lvl3 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2, x.Level3 }).Distinct();
var lvl4 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2, x.Level3, x.Level4 }).Distinct();
foreach(var emp in employers) { LocationLevels.Add(new LevelHierarchyStructure(emp)); }
foreach(var lvl in lvl1) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1)); }
foreach(var lvl in lvl2) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2)); }
foreach(var lvl in lvl3) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2, lvl.Level3)); }
foreach(var lvl in lvl4) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2, lvl.Level3, lvl.Level4)); }
SaveData();
}
I assume someone will flag me for posting my update, but im posting it so ppl understand what i did and see my final solution
Perhaps you need something that first expands each source row into all of the possible levels, and runs those results through a .Distinct() and an .OrderBy().
Given:
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
The expanded data would be:
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0002A
...
AAA
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
Then a .Distinct() and .OrderBy()
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
...
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
...
You would need a generating function to expand each source row into an enumerated list than can be fed into a .SelectMany(), and may also need to define custom comparators to be used by the .Distinct() and a .OrderBy() functions.
Something like:
private static IEnumerable<HierarchyStructure> HierarchyGenerator(this HierarchyStructure level)
{
yield return new HierarchyStructure(levels.Employer);
yield return new HierarchyStructure(levels.Employer,levels.Level1);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4);
}
...
LocationLevels = AllLevels
.SelectMany(levels => levels.HierarchyGenerator())
.Distinct(...custom HierarchyStructure IEqualityComparer...)
.OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
.ToList();
There are other ways, such as generating distinct values are each level, unioning them all together, and then feeding them to the sort.
Something like:
var level0 = AllLevels
.Select(levels => newHierarchyStructure(levels.Employer))
.Distinct(IEqualityComparer...);
...
var level4 = AllLevels
.Select(levels => new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4))
.Distinct(IEqualityComparer...);
LocationLevels = level0
.Union(level1)
.Union(level2)
.Union(level3)
.Union(level4)
.OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
.ToList();
There may be some performance tradeoffs in selecting where the distinct operations are applied. Using intermediate anonymous objects might also help, such as:
var level1 = AllLevels
.Select(levels => new {levels.Employer, levels.Level1})
.Distinct()
.Select(levels => new HierarchyStructure(levels.Employer,levels.Level1));
Here the .Distinct() uses the default comparator for the anonymous object, which compares each contained value.
The custom comparers can also be incorporated into the HierarchyStructure class by implementing the IComparable interface. The HierarchyGenerator() function could also be made a member function within the HierarchyStructure class.
(My apologies in advance for any syntax errors. This is untested. I'll update the above given any comments.)
This seems to be able to be solved quite easily with LINQ.
I'll start with this input:
string[] input = new[]
{
"AAA|0000A|000AA|0001A",
"AAA|0000A|000AA|0002A",
"AAA|0000A|000AB|0001B",
"AAA|0000A|000AB|0002B",
"AAA|0000B|000BA|0001A",
"AAA|0000B|000BB|0001B",
};
Now I can transform this into the output like this:
string[][] parts = input.Select(x => x.Split('|')).ToArray();
int max = parts.Max(p => p.Length);
string[][] expanded =
Enumerable
.Range(0, max)
.SelectMany(i => parts.Select(p => p.Take(i + 1).ToArray()).ToArray())
.DistinctBy(xs => String.Join("|", xs))
.OrderBy(xs => String.Join("|", xs))
.ToArray();
string[] output =
expanded
.Select(e => String.Join("|", e))
.ToArray();
That gives me:
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B
AAA|0000B|000BA
AAA|0000B|000BA|0001A
AAA|0000B|000BB
AAA|0000B|000BB|0001B
If you just want to stop to fill a grid, just use expanded.
With this approach it doesn't matter how many levels deep the source data is.
I would like to return a list of names that do not exist in report. However I am not sure how to loop properly through IEnumerable<string> names using a LINQ. Is there a way to loop through another array using LINQ ?
private class Report
{
public string UserName { get; set; }
public string city { get; set; }
public string image { get; set; }
}
List<Report>report = await _service(id).ConfigureAwait(false);
IEnumerable<string> names = await _names(id).ConfigureAwait(false);
// only want to get list of names that do not exist in report
var newList = reports.Where(x => x.UserName.Where(i => != names)); // doesn't work
You can use Contains method. Try like:
var newList = reports.Where(x => !names.Contains(x.UserName)));
only want to get list of names that do not exist in report
You want to query on names then, to get names back that don't exist.
var newList = names.Where(x => !reports.Any(xx => xx.UserName == x));
Try this:
var newList = names.Where(n => reports.Any(r => r.UserName != n));
Say I have the following class structures
public class EmailActivity {
public IEnumerable<MemberActivity> Activity { get; set; }
public string EmailAddress { get; set; }
}
public class MemberActivity {
public EmailAction? Action { get; set; }
public string Type { get; set; }
}
public enum EmailAction {
None = 0,
Open = 1,
Click = 2,
Bounce = 3
}
I wish to filter a list of EmailActivity objects based on the presence of a MemberActivity with a non-null EmailAction matching a provided list of EmailAction matches. I want to return just the EmailAddress property as a List<string>.
This is as far as I've got
List<EmailAction> activityTypes; // [ EmailAction.Open, EmailAction.Bounce ]
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Where(
activity => activityTypes.Contains(activity.Action)
)
)
.Select(member => member.EmailAddress)
.ToList();
However I get an error message "CS1503 Argument 1: cannot convert from 'EmailAction?' to 'EmailAction'"
If then modify activityTypes to allow null values List<EmailAction?> I get the following "CS1662 Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type".
The issue is the nested .Where it's returning a list, but the parent .Where requires a bool result. How would I tackle this problem?
I realise I could do with with nested loops however I'm trying to brush up my C# skills!
Using List.Contains is not ideal in terms of performance, HashSet is a better option, also if you want to select the email address as soon as it contains one of the searched actions, you can use Any:
var activityTypes = new HashSet<EmailAction>() { EmailAction.Open, EmailAction.Bounce };
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Any(
activity => activity.Action.HasValue &&
activityTypes.Contains(activity.Action.Value)
)
)
.Select(activity => activity.EmailAddress)
.ToList();
You want to use All or Any depends if you want each or at least one match...
HashSet<EmailAction> activityTypes = new HashSet<EmailAction> { EmailAction.None };
var emailActivity = new List<EmailActivity>
{
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.None } }, EmailAddress = "a" },
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.Click } }, EmailAddress = "b" }
};
// Example with Any but All can be used as well
var activityEmailAddresses = emailActivity
.Where(x => x.Activity.Any(_ => _.Action.HasValue && activityTypes.Contains(_.Action.Value)))
.Select(x => x.EmailAddress)
.ToArray();
// Result is [ "a" ]
I have a method where list cannot convert to ienumerable. How do i cast???
public ActionResult Attending()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
var gigs = _context.Attendances.Include(a => a.Gig.Artist).Include(a => a.Gig.Genre).Where(a => a.AttendeeId == userId).ToList();
var viewModel = new GigsViewModel()
{
UpcomingGigs = gigs,
ShowActions = User.Identity.IsAuthenticated,
Heading = "Gigs I'm Attending"
};
return View("Gigs", viewModel);
}
Here is my ViewModel:
public class GigsViewModel
{
public IEnumerable<Gig> UpcomingGigs { get; set; }
public bool ShowActions { get; set; }
public string Heading { get; set; }
}
Try using below code, AsEnumerable() will do your work
var gigs = _context.Attendances.Where(a => a.AttendeeId == userId).select(a=>
new Gig()
{
property = a.property //assuming property, you can repeat the same for other properties of Gig
}).AsEnumerable();
The result of a Linq database query is typically IQueryable which is derived from IEnumerable, IQueryable, and IEnumerable.
In your case,it's linq database query so you need to call AsEnumerable();
You can also first fetch all attendees, and select the gigs.
IEnumerable<Gig> gigs = _context.Attendances.Where(a => a.AttendeeId == userId).Select(a => a.Gig).ToList();
Be aware if there can be multiple similar gigs, then perhaps you need only distinct set of gigs using Distinct:
IEnumerable<Gig> gigs = _context.Attendances.Where(a => a.AttendeeId == userId).Select(a => a.Gig).Distinct().ToList();
See more at: https://msdn.microsoft.com/en-us/library/bb534803(v=vs.110).aspx
Edited: edited the old suggestion, Distinct and Select added.
I have a collection of documents that can contain criteria grouped into categories. The structure could look like this:
{
"Name": "MyDoc",
"Criteria" : [
{
"Category" : "Areas",
"Values" : ["Front", "Left"]
},
{
"Category" : "Severity",
"Values" : ["High"]
}
]
}
The class I'm using to create the embedded documents for the criteria looks like this:
public class CriteriaEntity
{
public string Category { get; set; }
public IEnumerable<string> Values { get; set; }
}
The user can choose criteria from each category to search (which comes into the function as IEnumerable<CriteriaEntity>) and the document must contain all the selected criteria in order to be returned. This was my first attempt:
var filterBuilder = Builders<T>.Filter;
var filters = new List<FilterDefinition<T>>();
filters.Add(filterBuilder.Exists(entity =>
userCriterias.All(userCriteria =>
entity.Criteria.Any(entityCriteria =>
entityCriteria.Category == userCriteria.Category
&& userCriteria.Values.All(userValue =>
entityCriteria.Values.Any(entityValue =>
entityValue == userValue))))));
However I get the error: "Unable to determine the serialization information for entity...". How can I get this to work?
MongoDB.Driver 2.0 doesn't support Linq.All. Anyway you task can be resolve next way:
var filterDefinitions = new List<FilterDefinition<DocumentEntity>>();
foreach (var criteria in searchCriterias)
{
filterDefinitions
.AddRange(criteria.Values
.Select(value => new ExpressionFilterDefinition<DocumentEntity>(doc => doc.Criterias
.Any(x => x.Category == criteria.Category && x.Values.Contains(value)))));
}
var filter = Builders<DocumentEntity>.Filter.And(filterDefinitions);
return await GetCollection<DocumentEntity>().Find(filter).ToListAsync();