Linq : Comparing 1 Child Collection to (Aggregated) ChildCollection(s) - c#

I have a Linq question: (DotNet Framework 4.0)
I have the following classes:
public class Employee
{
public Guid? EmployeeUUID { get; set; }
public string SSN { get; set; }
}
public class JobTitle
{
public Guid? JobTitleSurrogateKey { get; set; }
public string JobTitleName { get; set; }
}
public class EmployeeToJobTitleMatchLink
{
public EmployeeToJobTitleMatchLink()
{
this.TheJobTitle = new JobTitle() { JobTitleSurrogateKey = Guid.NewGuid(), JobTitleName = "SomeJobTitle:" + Guid.NewGuid().ToString("N") };
}
public Guid LinkSurrogateKey { get; set; }
/* Related Objects */
public Employee TheEmployee { get; set; }
public JobTitle TheJobTitle { get; set; }
}
public class Organization
{
public Organization()
{
this.Links = new List<EmployeeToJobTitleMatchLink>();
}
public int OrganizationSurrogateKey { get; set; }
public ICollection<EmployeeToJobTitleMatchLink> Links { get; set; }
}
In my code below, I can compare 2 child-collections and get the results I need (in "matches1".
Here I am using the "SSN" string property to compare and find the overlaps. And the Console.Write for matches1 works as I expect.
What I don't know how to do is compare the first child collection (org10) to all the children in (allOtherOrgsExceptOrg10 (all the Organizations and all the Links of these Organizations )
The commented out code shows kinda what I'm trying to do, one of my many feeble attempts today.
But basically, match2 would be populated with all the SSN overlaps...but comparing org10 with allOtherOrgsExceptOrg10, all their "Links", and their Employee.SSN's.
org10 overlaps with org20 with "AAA", so match2 would contain "AAA". and org10 overlaps with org30 with "BBB" so match2 would contain "BBB".
Organization org10 = new Organization();
org10.OrganizationSurrogateKey = 10;
Employee e11 = new Employee() { SSN = "AAA", EmployeeUUID = new Guid("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA") };
EmployeeToJobTitleMatchLink link11 = new EmployeeToJobTitleMatchLink();
link11.TheEmployee = e11;
org10.Links.Add(link11);
Employee e12 = new Employee() { SSN = "BBB", EmployeeUUID = new Guid("BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB") };
EmployeeToJobTitleMatchLink link12 = new EmployeeToJobTitleMatchLink();
link12.TheEmployee = e12;
org10.Links.Add(link12);
Organization org20 = new Organization();
org20.OrganizationSurrogateKey = 20;
Employee e21 = new Employee() { SSN = "AAA", EmployeeUUID = new Guid("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA") };
EmployeeToJobTitleMatchLink link21 = new EmployeeToJobTitleMatchLink();
link21.TheEmployee = e21;
org20.Links.Add(link21);
Employee e22 = new Employee() { SSN = "CCC", EmployeeUUID = new Guid("CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC") };
EmployeeToJobTitleMatchLink link22 = new EmployeeToJobTitleMatchLink();
link22.TheEmployee = e22;
org20.Links.Add(link22);
Organization org30 = new Organization();
org30.OrganizationSurrogateKey = 30;
Employee e31 = new Employee() { SSN = "BBB", EmployeeUUID = new Guid("BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB") };
EmployeeToJobTitleMatchLink link31 = new EmployeeToJobTitleMatchLink();
link31.TheEmployee = e31;
org30.Links.Add(link31);
Employee e32 = new Employee();
e32.SSN = "ZZZ";
EmployeeToJobTitleMatchLink link32 = new EmployeeToJobTitleMatchLink();
link32.TheEmployee = e32;
org30.Links.Add(link32);
IList<Organization> allOtherOrgsExceptOrg10 = new List<Organization>();
/* Note, I did not add org10 here */
allOtherOrgsExceptOrg10.Add(org20);
allOtherOrgsExceptOrg10.Add(org30);
IEnumerable<EmployeeToJobTitleMatchLink> matches1 =
org10.Links.Where(org10Link => org20.Links.Any(org20Link => org20Link.TheEmployee.SSN.Equals(org10Link.TheEmployee.SSN, StringComparison.OrdinalIgnoreCase)));
IEnumerable<EmployeeToJobTitleMatchLink> matches2 = null;
//org10.Links.Where(org10Link => ( allOtherOrgs.Where ( anyOtherOrg => anyOtherOrg.Links.Any(dbSideChild => dbSideChild.TheEmployee.SSN == org10Link.TheEmployee.SSN)) );
if (null != matches1)
{
foreach (EmployeeToJobTitleMatchLink link in matches1)
{
Console.WriteLine(string.Format("matches1, SSN = {0}", link.TheEmployee.SSN));
}
}
if (null != matches2)
{
foreach (EmployeeToJobTitleMatchLink link in matches2)
{
Console.WriteLine(string.Format("matches2, SSN = {0}", link.TheEmployee.SSN));
}
}

matches2 =
allOtherOrgsExceptOrg10.SelectMany(x => x.Links)
.Where(x => org10.Links.Select(o => o.TheEmployee.SSN).Contains(x.TheEmployee.SSN));
You can use the SelectMany on the allOther collection to select all Links over all org's. Then check if any SSN is inside the org10 List.
See: http://msdn.microsoft.com/en-us/library/system.linq.enumerable.selectmany(v=vs.100).aspx

You can use SelectMany to flatten out the collection and then use it just like you have for matches1
IEnumerable<EmployeeToJobTitleMatchLink> matches2 =
org10.Links.Where(
org10Link =>
allOtherOrgsExceptOrg10.SelectMany(allOtherOrgs => allOtherOrgs.Links).Any(
anyOtherLink =>
anyOtherLink.TheEmployee.SSN.Equals(org10Link.TheEmployee.SSN, StringComparison.OrdinalIgnoreCase)));
The SelectMany will make it seem like one IEnumerable instead of and IEnumerable of an IEnumerable.

Related

Remove from a list that has a list within it based on integer list

I have a list that basically look like this...
public class Area
{
public int Id { get; set; }
public string Name { get; set; }
public List<ZipCodeAdresses> ListOfIncludedDestinations { get; set; }
}
public class ZipCodeAdresses
{
public int AreaId { get; set; }
public List<Person> AdressList { get; set; }
}
public class Person
{
public string MottagarNamn { get; set; }
public string Street { get; set; }
}
var intListToRemove = new List<int>(){2,3};
var list = new List<Area>();
var subList = new List<ZipCodeAdresses>();
var personList = new List<Person>
{
new Person() {MottagarNamn = "User 1"},
new Person() {MottagarNamn = "User 2"}
};
subList.Add(new ZipCodeAdresses(){AdressList = personList , AreaId = 1});
personList = new List<Person>
{
new Person() {MottagarNamn = "User 3"},
new Person() {MottagarNamn = "User 4"}
};
subList.Add(new ZipCodeAdresses() { AdressList = personList, AreaId = 2 });
list.Add(new Area(){Name = "List A", ListOfIncludedDestinations = subList});
subList = new List<ZipCodeAdresses>();
personList = new List<Person>
{
new Person() {MottagarNamn = "User 5"},
new Person() {MottagarNamn = "User 6"}
};
subList.Add(new ZipCodeAdresses() { AdressList = personList, AreaId = 3 });
personList = new List<Person>
{
new Person() {MottagarNamn = "User 7"},
new Person() {MottagarNamn = "User 8"}
};
subList.Add(new ZipCodeAdresses() { AdressList = personList, AreaId = 4 });
list.Add(new Area() { Name = "List B", ListOfIncludedDestinations = subList });
I need to be able to remove from the list ListOfIncludedDestinations where AreaId is equal to any integer in intListToRemove which in this example is 2 and 3?
List<T> contains a method RemoveAll, that removes all entries that fulfill a certain condition. In your case it is:
foreach(var entry in list)
{
entry.ListOfIncludedDestinations.RemoveAll(x => intListToRemove.Contains(x.AreaId));
}
This loops through your list, and for every entry it removes all entries in ListOfIncludedDestinations that have an AreadId which is in intListToRemove.
Online demo: https://dotnetfiddle.net/ialnPb
You should add this sample code to remove them from the list :
foreach (var i in list)
i.ListOfIncludedDestinations.RemoveAll(o => intListToRemove.Contains(o.AreaId));

need to group data based on `InstanceData` Name property

I have one Packet like below,
var dataPacket = new Packet
{
Id = new Guid("2e08bd98-68eb-4358-8efb-9f2adedfb034"),
Results = new Result
{
ResultName = "ResultName1",
Instances = new List<Instance>
{
new Instance
{
InstanceName = "InstanceName1",
InstanceDatas = new List<InstanceData>
{
new InstanceData{Name = "N1", Value = "V1"},
new InstanceData{Name = "N2", Value = "V2"}
}
},
new Instance
{
InstanceName = "InstanceName2",
InstanceDatas = new List<InstanceData>
{
new InstanceData{Name = "N1", Value = "V3"},
new InstanceData{Name = "N2", Value = "V4"}
}
}
}
}
};
Here are the class structures,
public class Packet
{
public Guid Id { get; set; }
public Result Results { get; set; }
}
public class Result
{
public string ResultName { get; set; }
public List<Instance> Instances { get; set; }
}
public class Instance
{
public string InstanceName { get; set; }
public List<InstanceData> InstanceDatas { get; set; }
}
public class InstanceData
{
public string Name { get; set; }
public string Value { get; set; }
}
For above Packet I want to spilt this into 2 Packets based on InstanceData common Name
All N1 from InstanceName1 and InstanceName2 into one packet
All N2 from InstanceName1 and InstanceName2 into one packet
Packet1 should be like this,
var packet1 = new Packet
{
Id = new Guid("2e08bd98-68eb-4358-8efb-9f2adedfb034"),
Results = new Result
{
ResultName = "ResultName1",
Instances = new List<Instance>
{
new Instance
{
InstanceName = "InstanceName1",
InstanceDatas = new List<InstanceData>
{
new InstanceData{Name = "N1", Value = "V1"},
}
},
new Instance
{
InstanceName = "InstanceName2",
InstanceDatas = new List<InstanceData>
{
new InstanceData{Name = "N1", Value = "V3"},
}
}
}
}
};
and similarly packet2.
I have tried below, but this will split on InstanceData as well and giving 4 packets.
var packets = dataPacket.Results
.Instances
.SelectMany(x =>
x.InstanceDatas.Select(y => new Packet()
{
Id = dataPacket.Id,
Results = new Result()
{
ResultName = dataPacket.Results.ResultName,
Instances = new List<Instance>()
{
new Instance()
{
InstanceDatas = new List<InstanceData>() {y},
InstanceName = x.InstanceName
}
}
}
}));
You can write a helper method which finds the possible names as keys and iterate over the keys. Then you build new object instances for each key you are checking. The source code can look like this:
private static IList<Packet> SplitByName(Packet packet) {
IList<string> names = packet.Results.Instances
.SelectMany(it => it.InstanceDatas)
.Select(it => it.Name)
.Distinct()
.ToList();
IList<Packet> result = new List<Packet>();
foreach (string name in names)
{
List<Instance> newInstances = packet.Results.Instances
.Select(it => new Instance {
InstanceName = it.InstanceName,
InstanceDatas = it.InstanceDatas
.Where(it => it.Name == name)
.ToList()
})
.Where(it => it.InstanceDatas.Any())
.ToList();
Result newResult = new Result {
ResultName = packet.Results.ResultName,
Instances = newInstances
};
result.Add(new Packet {
Id = packet.Id,
Results = newResult
});
}
return result;
}
For each name you are filtering the InstanceData instances for each Instance object. Depending on your needs you might want to add .Where(it => it.InstanceData.Any()) so you don't have any "empty" instances.

How Can I Achieve this Using LINQ?

The best way I can describe what I'm trying to do is "Nested DistinctBy".
Let's say I have a collection of objects. Each object contains a collection of nicknames.
class Person
{
public string Name { get; set; }
public int Priority { get; set; }
public string[] Nicknames { get; set; }
}
public class Program
{
public static void Main()
{
var People = new List<Person>
{
new Person { Name = "Steve", Priority = 4, Nicknames = new string[] { "Stevo", "Lefty", "Slim" }},
new Person { Name = "Karen", Priority = 6, Nicknames = new string[] { "Kary", "Birdie", "Snookie" }},
new Person { Name = "Molly", Priority = 3, Nicknames = new string[] { "Mol", "Lefty", "Dixie" }},
new Person { Name = "Greg", Priority = 5, Nicknames = new string[] { "G-man", "Chubs", "Skippy" }}
};
}
}
I want to select all Persons but make sure nobody selected shares a nickname with another. Molly and Steve both share the nickname 'Lefty' so I want to filter one of them out. Only the one with highest priority should be included. If there is a highest priority tie between 2 or more then just pick the first one of them. So in this example I would want an IEnumerable of all people except Steve.
EDIT: Here's another example using music album instead of person, might make more sense.
class Album
{
string Name {get; set;}
int Priority {get;set;}
string[] Aliases {get; set;}
{
class Program
{
var NeilYoungAlbums = new List<Album>
{
new Person{ Name = "Harvest (Remastered)", Priority = 4, Aliases = new string[] { "Harvest (1972)", "Harvest (2012)"}},
new Person{ Name = "On The Beach", Priority = 6, Aliases = new string[] { "The Beach Album", "On The Beach (1974)"}},
new Person{ Name = "Harvest", Priority = 3, Aliases = new string[] { "Harvest (1972)"}},
new Person{ Name = "Freedom", Priority = 5, Aliases = new string[] { "Freedom (1989)"}}
};
}
The idea here is we want to show his discography but we want to skip quasi-duplicates.
I would solve this using a custom IEqualityComparer<T>:
class Person
{
public string Name { get; set; }
public int Priority { get; set; }
public string[] Nicknames { get; set; }
}
class PersonEqualityComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null || y == null) return false;
return x.Nicknames.Any(i => y.Nicknames.Any(j => i == j));
}
// This is bad for performance, but if performance is not a
// concern, it allows for more readability of the LINQ below
// However you should check the Edit, if you want a truely
// LINQ only solution, without a wonky implementation of GetHashCode
public int GetHashCode(Person obj) => 0;
}
// ...
var people = new List<Person>
{
new Person { Name = "Steve", Priority = 4, Nicknames = new[] { "Stevo", "Lefty", "Slim" } },
new Person { Name = "Karen", Priority = 6, Nicknames = new[] { "Kary", "Birdie", "Snookie" } },
new Person { Name = "Molly", Priority = 3, Nicknames = new[] { "Mol", "Lefty", "Dixie" } },
new Person { Name = "Greg", Priority = 5, Nicknames = new[] { "G-man", "Chubs", "Skippy" } }
};
var distinctPeople = people.OrderBy(i => i.Priority).Distinct(new PersonEqualityComparer());
EDIT:
Just for completeness, this could be a possible LINQ only approach:
var personNicknames = people.SelectMany(person => person.Nicknames
.Select(nickname => new { person, nickname }));
var groupedPersonNicknames = personNicknames.GroupBy(i => i.nickname);
var duplicatePeople = groupedPersonNicknames.SelectMany(i =>
i.OrderBy(j => j.person.Priority)
.Skip(1).Select(j => j.person)
);
var distinctPeople = people.Except(duplicatePeople);
A LINQ-only solution
var dupeQuery = people
.SelectMany( p => p.Nicknames.Select( n => new { Nickname = n, Person = p } ) )
.ToLookup( e => e.Nickname, e => e.Person )
.SelectMany( e => e.OrderBy( p => p.Priority ).Skip( 1 ) );
var result = people.Except( dupeQuery ).ToList();
See .net fiddle sample
This works once, then you have to clear the set. Or store the results in a collection.
var uniqueNicknames = new HashSet<string>();
IEnumerable<Person> uniquePeople = people
.OrderBy(T => T.Priority) // ByDescending?
.Where(T => T.Nicknames.All(N => !uniqueNicknames.Contains(N)))
.Where(T => T.Nicknames.All(N => uniqueNicknames.Add(N)));

Query mongo document array

I have the next mongo document structure :
_id
-countryCode
-keywordID
-name
-displayName
-categories:[Array]
-_id
-name
-position
-canonical
I would like to get all the keywords that are in a specific category only knowing the category's ID. I am using the mongo C# driver but don't know how could I check what's inside that array.
I would like to send a list with the category ID's and get back all the keywords that have a category from that list.
public async Task<List<Keyword>> GetKeywords(List<long> keywordCatIds, string countryCode)
{
var mongoCollection = MongoDatabase.GetCollection<Keyword>("Keywords");
try
{
FilterDefinition<Keyword> mongoFilter = Builders<Keyword>.Filter.In(c=>c.Categories, keywordCatIds);
return await mongoCollection.Find(mongoFilter,null).ToListAsync<Keyword>();
}
catch (Exception ex)
{
Logger.Error(ex, "Multiple ids for Country Code: {0}, ids: {1}", countryCode, string.Join(',', keywordCatIds.Select(s => s)));
return null;
}
}
Your In function looks like a "categories._id" filter in normal mongoDB. Which transitions into an ElemMatch. I created a project which fills the db, than selects
all the keywords that are in a specific category only knowing the category's ID
public class CustomID
{
public string CountryCode { get; set; }
public long KeywordId { get; set; }
public string Name { get; set; }
}
public class Keyword
{
[BsonId]
public CustomID Id { get; set; }
public List<Category> Categories { get; set; }
}
public class Category
{
[BsonId]
public long Id { get; set; }
public string Name { get; set; }
public int Position { get; set; }
}
internal class Program
{
public static IMongoDatabase MongoDatabase { get; private set; }
public static async Task Main()
{
var conventionPack = new ConventionPack
{
new CamelCaseElementNameConvention()
};
ConventionRegistry.Register(
"CustomConventionPack",
conventionPack,
t => true);
var client = new MongoClient();
MongoDatabase = client.GetDatabase("SO");
var ret = await GetKeywords(new List<long> {1L, 2L}, "HU-hu");
// ret is A and B. C is filtered out because no category id of 1L or 2L, D is not HU-hu
}
public static async Task<List<Keyword>> GetKeywords(List<long> keywordCatIds, string countryCode)
{
var mongoCollection = MongoDatabase.GetCollection<Keyword>("keywords");
// be ware! removes all elements. For debug purposes uncomment>
//await mongoCollection.DeleteManyAsync(FilterDefinition<Keyword>.Empty);
await mongoCollection.InsertManyAsync(new[]
{
new Keyword
{
Categories = new List<Category>
{
new Category {Id = 1L, Name = "CatA", Position = 1},
new Category {Id = 3L, Name = "CatC", Position = 3}
},
Id = new CustomID
{
CountryCode = "HU-hu",
KeywordId = 1,
Name = "A"
}
},
new Keyword
{
Categories = new List<Category>
{
new Category {Id = 2L, Name = "CatB", Position = 2}
},
Id = new CustomID
{
CountryCode = "HU-hu",
KeywordId = 2,
Name = "B"
}
},
new Keyword
{
Categories = new List<Category>
{
new Category {Id = 3L, Name = "CatB", Position = 2}
},
Id = new CustomID
{
CountryCode = "HU-hu",
KeywordId = 3,
Name = "C"
}
},
new Keyword
{
Categories = new List<Category>
{
new Category {Id = 1L, Name = "CatA", Position = 1}
},
Id = new CustomID
{
CountryCode = "EN-en",
KeywordId = 1,
Name = "EN-A"
}
}
});
var keywordFilter = Builders<Keyword>.Filter;
var categoryFilter = Builders<Category>.Filter;
var mongoFilter =
keywordFilter.ElemMatch(k => k.Categories, categoryFilter.In(c => c.Id, keywordCatIds)) &
keywordFilter.Eq(k => k.Id.CountryCode, countryCode);
return await mongoCollection.Find(mongoFilter).ToListAsync();
}
}

Unable to deserialize JSON array

Below is my class :
public class Employee : Base
{
public int Id { get; set; }
public string Fname { get; set; }
public DepartmentModel Department { get; set; }
}
public class DepartmentModel : Base
{
public int Id { get; set; }
public string DepartmentName { get; set; }
public List<Location> Locations { get; set; }
}
public class Locations
{
public string Area { get; set; }
public string StreetNo { get; set; }
public string Nearby { get; set; }
}
Response return from service:
var response = new
{
id = 100,
department = new
{
id = 200,
departmentName = "Abc",
locations = new[]
{
Employee.Department.Locations
.Select
(
lo => new
{
area = lo.Area,
streetNo = lo.streetNo,
nearby = lo.Nearby
}
).ToList()
}
}
};
return JsonConvert.SerializeObject(response);
Now when I try to deserialize this above JSON into my class Employee like below:
var deserialize = JsonConvert.DeserializeObject<Employee>(response.ToString());
Error:
How can I deserialize this above JSON?
The problem lies here:
locations = new[]
{
Employee.Department.Locations
.Select
(
lo => new
{
area = lo.Area,
streetNo = lo.streetNo,
nearby = lo.Nearby
}
).ToList()
}
The LINQ expression ends with .ToList() and thus is already returning a list of items. You are then wrapping that with new[] in an array. So, instead of being an array of Locations, the JSON is an array of an array of Locations.
Try removing the new[]. You don't want locations to be an array of lists
locations = Employee.Department.Locations
.Select(lo => new
{
area = lo.Area,
streetNo = lo.streetNo,
nearby = lo.Nearby
}
).ToList()
You need to instantiate a new Employee() and use the same casing as the classes:
var response = new Employee() // Instantiates Employee to ensure correct properties used.
{
Id = 100, // Has PascalCase.
Department = new DepartmentModel()
{
Id = 200,
DepartmentName = "Abc",
Locations = Employee.Department.Locations
.Select(lo => new Location
{
Area = lo.Area,
StreetNo = lo.StreetNo,
Nearby = lo.Nearby
}
).ToList()
}
};

Categories

Resources