Compare Two non related entity collection in Linq C# - c#

I want to compare two non related entities by using linq.
e.g -
Entity A
Id Name
1 A
2 B
3 C
4 D
Entity B
Id Name
1 B
2 C
Result I should get
A, D
From the above two collection I want to compare Entity B with Entity A by using the Name property and find out the records which are not available in Entity B.
Any help will be appreciated with some sample code.
Regards

You can use the Except extension method of LINQ. Quote from MSDN Documentation...
Produces the set difference of two sequences by using the default equality comparer to compare values.
Sample Code
int[] a = { 1, 2, 3, 4, 5 };
int[] b = { 4,5,6,7 };
var c = a.Except(b);
Result
1,2,3
Note
Because you are working with a custom object (a class) you will need to implement an equality comparer that compares items by the Name property. Example of custom equality comparer...
public class CustomComparer : IEqualityComparer<CustomObject>
{
public bool Equals(CustomObject x, CustomObject y)
{
return x.Name.Equals(y);
}
public int GetHashCode(CustomObject obj)
{
return obj.Name.GetHashCode();
}
}
Then you can use this custom equality comparer in an overload of the Except extension method, assumming a and b are of type CustomObject...
var c = a.Except(b, new CustomComparer());
The advantage is re-usability, especially if you are spreading this call to Except all over your project. Then,if you need to change your entity(custom object) you only have make changes in the custom equality comparer

var result = entityAs.Where(a => !entityBs.Any(b => b.Name == a.Name)).ToList();

Create a Class
class MyClass
{
public int Id {get; set;}
public string Name {get; set;}
}
Create List1
List<MyCLass> li1 = new List<MyCLass>();
MyCLass o1 = new MyClass();
o1.Id = 1;
o1.Name = "A";
li1.Add(o1);
o1 = new
o1.Id = 2;
o1.Name = "B";
li1.Add(o1);
o1 = new new MyClass();
o1.Id = 3;
o1.Name = "C";
li1.Add(o1);
o1 = new new MyClass();
o1.Id = 4;
o1.Name = "D";
li1.Add(o1);
Create List2
List<MyCLass> li2 = new List<MyCLass>();
o1 = new new MyClass();
o1.Id = 1;
o1.Name = "B";
li2.Add(o1);
o1 = new new MyClass();
o1.Id = 2;
o1.Name = "C";
li2.Add(o1);
o1 = new new MyClass();
o1.Id = 3;
o1.Name = "D";
li2.Add(o1);
Select only Selected items which you want to compare from List1
List<string> SelectedName = li1.Select(s => s.Name).ToList();
FinalList1 only Get those Item which are in List2
var FinalList = li2.Where(w => SelectedName.Contains(w.Name)).ToList();
/// or
FinalList2 only Get those Item which are not in List2
var FinalList2 = li2.Where(w => !SelectedName.Contains(w.Name)).ToList();

Related

How can I create a new List<T> based on two other List<T> and account for duplicates?

My first post. Humbled by this community. Thank you.
The goal: Create a new List<PropertyB> based on two other lists:
List<PropertyA> and another List<PropertyB>.
For each PropertyA in the List<PropertyA>, create a new PropertyB(), assigning the DisplayName to the new PropertyB's Name property. For each property in 'List', if the name from PropertyA matches PropertyB, assign the value to the new list's value property.
The problem: Accounting for Duplicate values. No data loss can occur between the lists.
The new list should include: Every PropertyA and every Value of the PropertyB list where there is a Name match.
The types:
My thoughts: My gut says the inner loop should check whether something has already been added to the collection. Or perhaps an accounting of duplicate values (ie: the index of duplicates?)
Any assistance is appreciated!
public class PropertyA{
private string DisplayName{get; set;}
private string Name {get; set;}
private string Value {get; set;}
}
public class PropertyB{
private string Name{get; set;}
private string Value{get; set;}
}
Initialization:
List<PropertyA> listA = new List<PropertyA>()
{
new PropertyA(){ DisplayName="LOB", Name="lineofbusiness", Value="test"},
new PropertyA(){ DisplayName="ABC", Name="alpha", Value="test2"},
new PropertyA(){ DisplayName="DEF", Name="beta", Value="test3"},
new PropertyA(){ DisplayName="GHI", Name="zeta", Value="test4"},
new PropertyA(){ DisplayName"Line of Business", Name="lineofbusiness", Value="test5"
};
List<PropertyB> listB = new List<PropertyB>()
{
new PropertyB(){ Name="lineofbusiness", Value="test789"},
new PropertyB(){ Name="alpha", Value="test234"},
new PropertyB(){ Name="lineofbusiness", Value="test456"},
new PropertyB(){ Name="beta", Value="test123"},
};
In Main:
List<PropertyB> newList = new List<PropertyB>();
foreach(PropertyA propA in listA){
PropertyB newProp = new PropertyB();
newProp.Name = propA.DisplayName;
foreach(PropertyB propB in listB){
if(propA.Name == propB.Name){
newProp.Value = propB.Value;
break;
}
}
newList.Add(newProp);
}
UPDATE:
The console output (if you choose) should be as follows:
LOB test789
ABC test234
DEF test123
GHI null
Line of Business test456
if you simply remove the break; you end up with:
LOB test456
ABC test234
DEF test123
GHI null
Line of Business test456
The inner loop will always assign the LAST name match value. That's a problem.
you can just fix your code, add a check for duplicates
List<PropertyB> newList = new List<PropertyB>();
foreach(PropertyA propA in listA)
{
PropertyB newProp = new PropertyB();
newProp.Name = propA.DisplayName;
foreach (var propB in listB)
{
if (propA.Name == propB.Name)
{
if( newList.Any(l =>l.Value==propB.Value )) continue;
newProp.Value = propB.Value;
break;
}
}
newList.Add(newProp);
}
but to make it more reliable I would offer this
List<PropertyA> newList = new List<PropertyA>();
foreach (var propA in listA)
{
var newProp = new PropertyA();
newProp.Name = propA.DisplayName;
newProp.DisplayName = propA.Name;
foreach (var propB in listB)
{
if (propA.Name == propB.Name)
{
if (newList.Any(l => l.Value == propB.Value
&& l.DisplayName==propA.Name)) continue;
newProp.Value = propB.Value;
break;
}
}
newList.Add(newProp);
}
var result = newList.Select(l => new PropertyB {Name=l.Name, Value=l.Value} );
both algorithms show the same result during the test
LOB test789
ABC test234
DEF test123
GHI null
Line of Business test456
I understood the process:
list of A needs turning into a list of B
Some of the list of B items might have a Value copied from some other list of B
var d = bList.ToDictionary(b => b.Name, b => b.Value);
var newB = aList.Select(a => new B { Name = a.DisplayName, Value = d.GetValueOrDefault(a.Name) } ).ToList();
You said no data shall be lost but I think inherently you must have to throw something away because B has fewer properties than A and some properties from B are used to "overwrite"/take the place of those in A..
I note also you have duplicated Name in your sample data list B, which the ToDictionary won't tolerate. You didn't specify how to resolve this but you'll have to choose (if it truly does occur) what value to pick or if to take multiple. This, for example, would tolerate duplicate names
var d = bList.ToLookup(b => b.Name, b => b.Value);
var newB = aList.Select(a => new B { Name = a.DisplayName, Value = d[a.Name]?.First() } ).ToList();
Again, this throws stuff away.. if you want to keep all the values you'll have to encode the Value somehow
Value = string.Join(",", d[a.Name])
for example
So, it looks like you want to keep all the duplicates and dispense them in order. We could do that by grouping these things into a list that we pull the items out of as we enumerate
var d = bList.GroupBy(b => b.Name, b => b.Value).ToDictionary(g => g.Key, g => g.ToList());
var newB = new List<B>();
foreach(var a in aList){
var b = new B { Name = a.DisplayName };
if(d.TryGetValue(a.Name, out var lst)){
b.Value = lst[0];
lst.RemoveAt(0);
}
}

Auto sorted list c# with duplicate keys

I have a series of objects and a function
double P(Object a, Object b){...}
Now, for a fixed Object a, I would like to store inside a list L all the other objects in this way:
Objects a,b,c,d with P(a,b)=1, P(a,c)=2, P(a,d)=1 should have
L[0] = b or d, L[1] = b or d, L[2] = c
Note that I only need to access (not modify, delete ecc..) the items stored in L, if L could be a SortedList then IndexOfValue would be perfect but it doesn't support duplicate keys.
Is there an easy way to solve this problem?
From the c# interactive shell
// making up a class, since there aren't any details.
// make it have some kind of value, and a human friendly name
public class Thing { public int Val {get; set;} public string Name { get; set; } }
// since P isn't given, make something up. How about adding two numbers?
Func<Thing, Thing, double> P = (a, b) => { return a.Val + b.Val; };
// give starting values to match example function output
var a = new Thing() { Val = 0, Name = "a" };
var b = new Thing() { Val = 1, Name = "b" };
var c = new Thing() { Val = 2, Name = "c" };
var d = new Thing() { Val = 1, Name = "d" };
// others is the list of values, sorted by the output from the function "P",
// compared against the first Thing ("a" in this case")
var others = (new List<Thing>() { b,c,d }).OrderBy(x => P(a, x));
// interactive shell out gives:
. others.Select(x => x.Name)
Enumerable.WhereSelectEnumerableIterator<Submission#0.Thing, string> { "b", "d", "c" }

linq selectmany flatten multiple levels

I have the following relation (for example)
A contains one or more B's
Each B contains one or more C's and D's
I want to flatten everything using SelectMany along with some search conditions and get A,B,C and D's . This is what i have.
context.A.Where(a => (string.IsNullOrEmpty(name) || a.Name.Contains(name)))
.SelectMany(ab =>ab.b.Where(n=>n.bname.Contains(name) || string.IsNullOrEmpty(name)),
(aa, bb) => new { aa, bb }) //gets all a's and b's
.SelectMany(bc => bb.c.Where(w => w.KEYWORD.Contains(Keyword) || string.IsNullOrEmpty(Keyword)),
(bc,words) => new {bc,kwords}) //gets all b's and c's
Is what i am doing right? If so , then how to get B along with all D's adding to the above expression?
Data Selection using Lambda Syntax:
var flatData = context.A.SelectMany(a => a.B.SelectMany(b => b.Select(new {a,b,c = b.C,d = b.D})
Going further, following checks shall be done before applying the Where Clause, as they check the constant input supplied, name and keyword
string.IsNullOrEmpty(name)
string.IsNullOrEmpty(keyword)
Remaining checks would be simple:
if(!string.IsNullOrEmpty(name))
flatData = flatData.Where(data => data.a.Name.Contains(name))
.Where(data => data.b.Name.Contains(name));
if(!string.IsNullOrEmpty(keyword))
flatData = flatData.Where(data => data.c.Keyword.Contains(keyword));
Important points:
flatData above has a cascading filter, first on a.Name, b.Name and c.Keyword
Agreeing with what Ivan suggested you can flatten this 3 levels deep structure like this:
var query = (from a in A
from b in (List<dynamic>)a.b
from c in (List<dynamic>)b.c
from d in (List<dynamic>)b.d
select new { a, b, c, d });
if (!string.IsNullOrEmpty(name))
{
query = query.Where(record => record.b.bname.Contains(name));
}
if (!string.IsNullOrEmpty(keyword))
{
query = query.Where(record => record.c.keyword.Contains(keyword));
}
var result = query.ToList();
You can also add the where clauses in the query at the top but seeing that you are checking if you got any valid input at all I'd put it after
Tested it with this sample data:
List<dynamic> A = new List<dynamic>
{
new { b = new List<dynamic> { new { bname = "a", c = new List<dynamic> { new { keyword = "b" } }, d = new List<dynamic> { 1, 2, 3 } } } },
new { b = new List<dynamic> { new { bname = "a", c = new List<dynamic> { new { keyword = "d" } }, d = new List<dynamic> { 1, 2, 3 } } } }
};
string name = "a";
string keyword = "b";

Group by linq for nested objects

I am making a group by linq statement where i convert a single list of data into an list with a nested list. Here is my code so far:
[TestMethod]
public void LinqTestNestedSelect2()
{
// initialization
List<combi> listToLinq = new List<combi>() {
new combi{ id = 1, desc = "a", name = "A", count = 1 },
new combi{ id = 1, desc = "b", name = "A", count = 2 },
new combi{ id = 2, desc = "c", name = "B", count = 3 },
new combi{id = 2, desc = "d", name = "B", count = 4 },
};
// linq group by
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = (from r in obj select new B() { des = r.des, count = r.count }).ToList() }).ToList();
// validation of the results
Assert.AreEqual(2, result.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
}
public class A
{
public int id;
public string name;
public List<B> descriptions;
}
public class B
{
public int count;
public string des;
}
public class combi
{
public int id;
public string name;
public int count;
public string desc;
}
This is fine if the objects are small like the example. However I will implement this for objects with a lot more properties. How can I efficiently write this statement so I don't have to write field names twice in my linq statement?
I would like to return the objects in the statement and I want something like:
// not working wishfull thinking code
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new (A){ this = obj.key , descriptions = obj.ToList<B>()}).ToList();
Background: I am re writing a web api that retrieves objects with nested objects in a single database call for the sake of db performance. It's basically a big query with a join that retrieves a crap load of data which I need to sort out into objects.
probably important: the ID is unique.
EDIT:
based on the answers so far I have made a solution which sort of works for me, but is still a bit ugly, and I would want it to be better looking.
{
// start part
return (from row in reader.AsEnumerable()
group row by row.id into grouping
select CreateA(grouping)).ToList();
}
private static A CreateA(IGrouping<object, listToLinq> grouping)
{
A retVal = StaticCreateAFunction(grouping.First());
retVal.descriptions = grouping.Select(item => StaticCreateBFunction(item)).ToList();
return ret;
}
I hope the StaticCreateAFunction is obvious enough for what it does. In this scenario I only have to write out each property once, which is what I really wanted. But I hope there is a more clever or linq-ish way to write this.
var result = (from row in listToLinq
group new B { des = row.desc, count = row.count } by new A { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = obj.ToList() }).ToList();
You can add to each of the A and B classes a constructor that receives a combi and then it takes from it only what it needs. For example for a:
public class A
{
public A(combi c)
{
id = c.id;
name = c.name;
}
}
public class B
{
public B(combi c)
{
count = c.count;
des = c.desc;
}
}
Then your query can look like:
var result = (from row in listToLinq
group row by new { row.id, row.name } into grouping
select new A(grouping.First())
{
descriptions = grouping.Select(item => new B(item)).ToList()
}).ToList();
If you don't like the grouping.First() you can then override Equals and GetHashCode and then in the group by do by a new a with the relevant fields (which will be those in the Equals) and then add a copy constructor from a
Another way, in which you decouple the A/B classes from the combi is to extract the convert logic to a collection of static methods.

C# comparing 2 lists with string array property

I have a class with one property of List<String> to hold a dynamic list of one or more string ids.
public class FieldCompareItem
{
public List<string> Fields = new List<string>();
public FieldCompareItem(string[] fields)
{
for (int i = 0; i < fields.Count(); i++)
Fields.Add(fields[i]);
}
}
}
I'm trying to compare 2 lists to see if the string arrays match but it doesn't work. Basically, I want to do an A/B compare to get items that only exist in A, in B, and in both, something like this:
var listA = new List<FieldCompareItem>
{
new FieldCompareItem(new[] {"a1"}),
new FieldCompareItem(new[] {"a2"}),
new FieldCompareItem(new[] {"a3","001"})
};
var listB = new List<FieldCompareItem>
{
new FieldCompareItem(new[] {"a2"}),
new FieldCompareItem(new[] {"a3"}),
new FieldCompareItem(new[] {"a3","001"}),
new FieldCompareItem(new[] {"a4"}),
new FieldCompareItem(new[] {"a5"})
};
//exists in A only
var aOnly = listA.Except(listB).ToList();
//expect a1,a3
//exists in B only
var bOnly = listB.Except(listA).ToList();
//expect a4,a5
//exists in both - this may be used for update A>B or B>A
var inBoth = ?????
//expect a2
Because they are values within an array property it doesnt seem to find by criteria. any help appreciated
Create a comparer first:
public class FieldCompareItemComparer: IEqualityComparer<FieldCompareItem>
{
public bool Equals(FieldCompareItem x, FieldCompareItem y)
{
var result = x.Fields.SequenceEqual(y.Fields);
return result;
}
public int GetHashCode(FieldCompareItem obj)
{
return String.Concat(obj.Fields).GetHashCode();
}
}
then use it like the following:
var comparer = new FieldCompareItemComparer();
// exists in A only
var aOnly = listA.Except(listB, comparer).ToList();
// exists in B only
var bOnly = listB.Except(listA, comparer).ToList();
// exists in both
var inBoth = listA.Intersect(listB, comparer).ToList();

Categories

Resources