Linq Join multi key one side of key is null - c#

Trying to join two tables where part of the key on one side is nullable. Getting "Type arguments cannot be inferred from the usage", which I believe is related to the mismatched types in the key.
TableA.GroupJoin(TableB,
a => new {a.IntKeyA, a.StringKeyA},
b => new {b.NullIntKeyB, b.StringKeyB}
(tabA, tabB) => new {tabA, tabB});
Tried to cast the type of the key in TableA
a => new (int?, string) {a.IntKeyA, a.StringKeyA}
or
a => (int?, string)(new {a.IntKeyA, a.StringKeyA})
Tried to Coalesce the key in TableB, magic number 0 isn't great but would have worked in this scenario.
b => new {b.NullIntKeyB ?? 0, b.StringKeyB}
Tried GetValueOrDefault
b => new {b.NullIntKeyB.GetValueOrDefault(), b.StringKeyB}
I suppose I could probably define a class to hold the key but I don't really want to do that every time this issue comes up.

For the time being, this seems to have worked, but I'm not going to mark it as the answer yet in hopes there's an easier way.
class ReportKey
{
private int? IntKey { get; }
private string StringKey { get; } = string.Empty;
internal ReportKey(int? intKey, string stringKey)
{
IntKey = intKey;
StringKey = stringKey;
}
public override bool Equals(object obj)
{
var item = obj as ReportKey;
if (item == null) return false;
return this.IntKey == item.Intkey &&
StringKey == item.StringKey;
}
public override int GetHashCode()
{
return $"{IntKey}{StringKey}".GetHashCode();
}
}
...
TableA.GroupJoin(TableB,
a => new ReportKey(a.IntKeyA, a.StringKeyA),
b => new ReportKey(b.NullIntKeyB, b.StringKeyB),
(tabA, tabB) => new {tabA, tabB});

Related

Compare 2 not identical DTO but have common properties in Fluent Assertion

I am writing a unit test for a manual mapper. It maps an object to two different classes but have common properties. how to compare if their properties are equal in fluent assertion?
This is what I tried
var domain = new Domain.ConsentDefinition()
{
SomeProperty = 1,
ListOfFirstDTO = new List<FirstDTO>()
{
new FirstDTO()
{
Name = "Label",
Age = 18,
}
},
SomeOtherProperty = "one"
}
ef = domain.ToEF();
domain.SomeProperty.Should().Be(ef.SomeProperty);
domain.SomeOtherProperty.Should().Be(ef.SomeOtherProperty);
domain.ListFirstDTO.Should().Equal(ef.ListOfSecondDTO); // This is NOT working
classes
public class FirstDTO
{
public string Name {get;set;}
public int Age {get;set;}
}
public class SecondDTO
{
public string Name {get;set;}
public int Age {get;set;}
public string Email {get;set;}
}
Override firstDTO's equals so you compare values instead of references:
public override bool Equals(object obj)
{
if (obj == null || !(obj is FirstDTO) || !(obj is SecondDTO))
{
return false;
}
if(obj is SecondDTO){
return (this.Name == ((SecondDTO)obj).Name)
&& (this.Age == ((SecondDTO)obj).Age)
}
// if obj is instance of FirstDTO check the rest of fields...
}
and run again
domain.ListFirstDTO.Should().Equal(ef.ListOfSecondDTO); // This is NOT working
Another more elegant solution with no need of overriding equals would be
domain.ListFirstDTO.Select(c => c.Name).Should().Equal(ef.ListOfSecondDTO.Select(c => c.Name);
domain.ListFirstDTO.Select(c => c.Age).Should().Equal(ef.ListOfSecondDTO.Select(c => c.Age);
fluentassertion/collections
domain.Should().BeEquivalentTo(new
{
SomeProperty = ef.SomeProperty,
SomeOtherProperty = ef.SomeOtherProperty,
ListFirstDTO = ef.ListOfSecondDTO
});
or
domain.Should().BeEquivalentTo(ef, options => options
.Including(x => x.SomeProperty)
.Including(x => x.SomeOtherProperty)
.Including(x => x.ListOfSecondDTO));
By default, FA will compare two collections by ignoring the order of the items in the collection. Use WithStrictOrdering to control that.
If the second DTO implements Equals, then FA will use that. You can override that by using the ComparingByMembers<T> option.

c# Linq - Check if composite key exists in another list

I have a list of errors defined as the following:
List<Errors> test1 = new List<Errors>();
public class Errors
{
public int ID {get; set;}
public int Occurrence {get; set;}
//.....
//.....
}
The errors are unique by the combination of the two fields above.
A second list keeps track of whose been assigned to the errors.
List<Tasks> test2 = new List<Tasks>();
public class Tasks
{
public int ID {get; set;}
public int Occurrence {get; set;}
public int EmployeeID {get; set;}
//.....
}
Also made unique by the same two fields. Essentially the tasks are a subset of the errors that have been assigned to someone.
I would like to use a LINQ query (or equivalent) to determine if the composite ID from the List<Errors> exists in List<Tasks>... To be clear it must use both IDS.
I have found the below solution but have not been able to adopt it to a composite key.
`var test2NotInTest1 = test2.Where(t2 => !test1.Any(t1 => t2.Contains(t1)));`
Just need to use and && operator and check both properties instead of one:
var test2NotInTest1 = test2.Where(t2 => !test1.Any(t1 => t1.ID == t2.ID && t1.Occurance == t2.Occurance);
There is a function for that... Except
var test2NotInTest1 = test1.Except(test2);
If you don't have it you will need to create the interface for equal -- something like this:
var test2NotInTest1 = test1.Except(test2, new ErrorsComparer());
class ErrorsComparer : IEqualityComparer<Errors>
{
public bool Equals(Errors x, Errors y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.ID == y.ID && x.Occurrence == y.Occurrence;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Errors e)
{
if (Object.ReferenceEquals(e, null)) return 0;
int hashID = e.ID == null ? 0 : e.ID.GetHashCode();
int hashO = e.Occurrence.GetHashCode();
//Calculate the hash code for the product.
return hashID ^ hashO;
}
}
You were almost there, just add a correct condition to the LINQ expression:
var test2NotInTest1 = listOfErrors.Where(e => !listOfTasks.Any(t => t.ID == e.Id && t.Occurrence == e.Occurrence)).ToList();
For: to determine if the composite ID from the Errors exists in Tasks...
Another approach is to use Enumerable.Join Method
var assignedErrors =
errors.Join(tasks,
error => new { Id = error.Id, Occurrence = error.Occurrence },
task => new { Id = task.Id, Occurrence = task.Occurrence },
(error, task) => error);
For: to determine if the composite ID from the Errors not exists in Tasks..., as in your sample:
var test2NotInTest1 = test2.Where(t2 => !test1.Any(t1 => t2.Contains(t1)));
You can use HashSet to "speed up" search for already assigned errors.
var assignedErrors = tasks.Select(task => (task.Id, task.Occurrence)).ToHashSet();
var notAssignedErrors =
errors.Where(error => assignedErrors.Contains((error.Id, error.Occurrence)) == false)
.ToList();
Or create your own domain specific extension method:
public static IEnumerable<Errors> NotAssignedIn(
this IEnumerable<Errors> errors,
IEnumerable<Tasks> tasks)
{
var assigned = new HashSet<(int Id, int Occurrence)>();
foreach (var task in tasks)
{
assigned.Add((task.Id, task.Occurrence));
}
foreach (var error in errors)
{
if (assigned.Contains((error.Id, error.Occurrence)) == false)
{
yield return error;
}
}
}
Usage:
var notAssignedErrors = errors.NotAssignedIn(tasks);

C# Linq .Distinct() method not working [duplicate]

This question already has answers here:
LINQ's Distinct() on a particular property
(23 answers)
Closed 20 days ago.
class Program
{
static void Main(string[] args)
{
List<Book> books = new List<Book>
{
new Book
{
Name="C# in Depth",
Authors = new List<Author>
{
new Author
{
FirstName = "Jon", LastName="Skeet"
},
new Author
{
FirstName = "Jon", LastName="Skeet"
},
}
},
new Book
{
Name="LINQ in Action",
Authors = new List<Author>
{
new Author
{
FirstName = "Fabrice", LastName="Marguerie"
},
new Author
{
FirstName = "Steve", LastName="Eichert"
},
new Author
{
FirstName = "Jim", LastName="Wooley"
},
}
},
};
var temp = books.SelectMany(book => book.Authors).Distinct();
foreach (var author in temp)
{
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Console.Read();
}
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
return true;
//if (obj.GetType() != typeof(Author)) return false;
//else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
}
}
This is based on an example in "LINQ in Action". Listing 4.16.
This prints Jon Skeet twice. Why? I have even tried overriding Equals method in Author class. Still Distinct does not seem to work. What am I missing?
Edit:
I have added == and != operator overload too. Still no help.
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
LINQ Distinct is not that smart when it comes to custom objects.
All it does is look at your list and see that it has two different objects (it doesn't care that they have the same values for the member fields).
One workaround is to implement the IEquatable interface as shown here.
If you modify your Author class like so it should work.
public class Author : IEquatable<Author>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
public override int GetHashCode()
{
int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
int hashLastName = LastName == null ? 0 : LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}
Try it as DotNetFiddle
The Distinct() method checks reference equality for reference types. This means it is looking for literally the same object duplicated, not different objects which contain the same values.
There is an overload which takes an IEqualityComparer, so you can specify different logic for determining whether a given object equals another.
If you want Author to normally behave like a normal object (i.e. only reference equality), but for the purposes of Distinct check equality by name values, use an IEqualityComparer. If you always want Author objects to be compared based on the name values, then override GetHashCode and Equals, or implement IEquatable.
The two members on the IEqualityComparer interface are Equals and GetHashCode. Your logic for determining whether two Author objects are equal appears to be if the First and Last name strings are the same.
public class AuthorEquals : IEqualityComparer<Author>
{
public bool Equals(Author left, Author right)
{
if((object)left == null && (object)right == null)
{
return true;
}
if((object)left == null || (object)right == null)
{
return false;
}
return left.FirstName == right.FirstName && left.LastName == right.LastName;
}
public int GetHashCode(Author author)
{
return (author.FirstName + author.LastName).GetHashCode();
}
}
Another solution without implementing IEquatable, Equals and GetHashCode is to use the LINQs GroupBy method and to select the first item from the IGrouping.
var temp = books.SelectMany(book => book.Authors)
.GroupBy (y => y.FirstName + y.LastName )
.Select (y => y.First ());
foreach (var author in temp){
Console.WriteLine(author.FirstName + " " + author.LastName);
}
There is one more way to get distinct values from list of user defined data type:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
Surely, it will give distinct set of data
Distinct() performs the default equality comparison on objects in the enumerable. If you have not overridden Equals() and GetHashCode(), then it uses the default implementation on object, which compares references.
The simple solution is to add a correct implementation of Equals() and GetHashCode() to all classes which participate in the object graph you are comparing (ie Book and Author).
The IEqualityComparer interface is a convenience that allows you to implement Equals() and GetHashCode() in a separate class when you don't have access to the internals of the classes you need to compare, or if you are using a different method of comparison.
You've overriden Equals(), but make sure you also override GetHashCode()
The Above answers are wrong!!!
Distinct as stated on MSDN returns the default Equator which as stated The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T
Which means as long as you overide Equals you are fine.
The reason you're code is not working is because you check firstname==lastname.
see https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx and https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx
You can achieve this several ways:
1. You may to implement the IEquatable interface as shown Enumerable.Distinct Method or you can see #skalb's answer at this post
2. If your object has not unique key, You can use GroupBy method for achive distinct object list, that you must group object's all properties and after select first object.
For example like as below and working for me:
var distinctList= list.GroupBy(x => new {
Name= x.Name,
Phone= x.Phone,
Email= x.Email,
Country= x.Country
}, y=> y)
.Select(x => x.First())
.ToList()
MyObject class is like as below:
public class MyClass{
public string Name{get;set;}
public string Phone{get;set;}
public string Email{get;set;}
public string Country{get;set;}
}
3. If your object's has unique key, you can only use the it in group by.
For example my object's unique key is Id.
var distinctList= list.GroupBy(x =>x.Id)
.Select(x => x.First())
.ToList()
You can use extension method on list which checks uniqueness based on computed Hash.
You can also change extension method to support IEnumerable.
Example:
public class Employee{
public string Name{get;set;}
public int Age{get;set;}
}
List<Employee> employees = new List<Employee>();
employees.Add(new Employee{Name="XYZ", Age=30});
employees.Add(new Employee{Name="XYZ", Age=30});
employees = employees.Unique(); //Gives list which contains unique objects.
Extension Method:
public static class LinqExtension
{
public static List<T> Unique<T>(this List<T> input)
{
HashSet<string> uniqueHashes = new HashSet<string>();
List<T> uniqueItems = new List<T>();
input.ForEach(x =>
{
string hashCode = ComputeHash(x);
if (uniqueHashes.Contains(hashCode))
{
return;
}
uniqueHashes.Add(hashCode);
uniqueItems.Add(x);
});
return uniqueItems;
}
private static string ComputeHash<T>(T entity)
{
System.Security.Cryptography.SHA1CryptoServiceProvider sh = new System.Security.Cryptography.SHA1CryptoServiceProvider();
string input = JsonConvert.SerializeObject(entity);
byte[] originalBytes = ASCIIEncoding.Default.GetBytes(input);
byte[] encodedBytes = sh.ComputeHash(originalBytes);
return BitConverter.ToString(encodedBytes).Replace("-", "");
}
The Equal operator in below code is incorrect.
Old
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
NEW
public override bool Equals(Object obj)
{
var other = obj as Author;
if (other is null)
{
return false;
}
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
Instead of
var temp = books.SelectMany(book => book.Authors).Distinct();
Do
var temp = books.SelectMany(book => book.Authors).DistinctBy(f => f.Property);

Anonymous type and intersection of 2 lists

public class thing
{
public int Id{get;set;}
public decimal shouldMatch1 {get;set;}
public int otherMatch2{get;set;}
public string doesntMatter{get;set;}
public int someotherdoesntMatter{get;set;}
}
List<thing> firstList = new List<thing>();
List<thing> secondList = new List<thing>();
firstList.Add( new thing{ Id=1,shouldMatch1 = 1.11M, otherMatch2=1000,doesntMatter="Some fancy string", someotherdoesntMatter=75868});
firstList.Add( new thing{ Id=2,shouldMatch1 = 2.22M, otherMatch2=2000,doesntMatter="Some fancy string", someotherdoesntMatter=65345});
firstList.Add( new thing{ Id=3,shouldMatch1 = 3.33M, otherMatch2=3000,doesntMatter="Some fancy string", someotherdoesntMatter=75998});
firstList.Add( new thing{ Id=4,shouldMatch1 = 4.44M, otherMatch2=4000,doesntMatter="Some fancy string", someotherdoesntMatter=12345});
secondList.Add( new thing{ Id=100,shouldMatch1 = 1.11M, otherMatch2=1000,doesntMatter="Some fancy string", someotherdoesntMatter=75868});
secondList.Add( new thing{ Id=200,shouldMatch1 = 2.22M, otherMatch2=200,doesntMatter="Some fancy string", someotherdoesntMatter=65345});
secondList.Add( new thing{ Id=300,shouldMatch1 = 3.33M, otherMatch2=300,doesntMatter="Some fancy string", someotherdoesntMatter=75998});
secondList.Add( new thing{ Id=400,shouldMatch1 = 4.44M, otherMatch2=4000,doesntMatter="Some fancy string", someotherdoesntMatter=12345});
//Select new firstList.Id,secondList.Id where firstList.shouldMatch1 ==secondList.shouldMatch1 && firstList.otherMatch2==secondList.otherMatch2
//SHould return
//1,100
//4,400
Is there a way to intersect the lists, or must I iterate them?
Pseudocode
firstList.Intersect(secondList).Where(firstList.shouldMatch1 == secondList.shouldMatch1 && firstList.otherMatch2 == secondList.otherMatch2)
Select new {Id1=firstList.Id,Id2=secondList.Id};
Regards
_Eric
You could use an approach other than intersecting and implementing an IEqualityComparer, as follows:
var query = from f in firstList
from s in secondList
where f.shouldMatch1 == s.shouldMatch1 &&
f.otherMatch2 == s.otherMatch2
select new { FirstId = f.Id, SecondId = s.Id };
foreach (var item in query)
Console.WriteLine("{0}, {1}", item.FirstId, item.SecondId);
This is essentially the Enumerable.SelectMany method in query format. A join would likely be quicker than this approach.
Consider using a multi-condition join to join your records. An intersect would cause you to lose ID's either on the left or the right.
Here is an example of a working multi-column join for this particular scenario. The appeal of this query is that it requires no equality comparer, and it allows you to retrieve the ID column while joining on the other specified columns.
var query = from first in firstList
join second in secondList on
new { first.shouldMatch1, first.otherMatch2 }
equals
new { second.shouldMatch1, second.otherMatch2 }
select new
{
FirstId = first.Id,
SecondId = second.Id
};
You need to make your thing type override Equals and GetHashCode to indicate its equality semantics:
public sealed class Thing : IEquatable<Thing>
{
public int Id{get;set;}
public decimal ShouldMatch1 {get;set;}
public int OtherMatch2{get;set;}
public string DoesntMatter{get;set;}
public int SomeOtherDoesntMatter{get;set;}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 31 + ShouldMatch1.GetHashCode() ;
hash = hash * 31 + OtherMatch2.GetHashCode() ;
return hash;
}
public override bool Equals(object other) {
return Equals(other as Thing);
}
public bool Equals(Thing other) {
if (other == null) {
return false;
}
return ShouldMatch1 == other.ShouldMatch1 &&
OtherMatch2 == other.OtherMatch2;
}
}
Note that sealing the class makes the equality test simpler. Also note that if you put one of these in a dictionary as a key but then change Id, ShouldMatch1 or OtherMatch2 you won't be able to find it again...
Now if you're using a real anonymous type, you don't get to do this... and it's tricky to implement an IEqualityComparer<T> to pass to Intersect when it's anonymous. You could write an IntersectBy method, a bit like MoreLINQ's DisinctBy method... that's probably the cleanest approach if you're really using an anonymous type.
You'd use it like this:
var query = first.Intersect(second);
You then end up with an IEnumerable<Thing> which you can get the right bits out of.
Another option is to use a join:
var query = from left in first
join right in second
on new { left.ShouldMatch1, left.OtherMatch2 } equals
new { right.ShouldMatch1, right.OtherMatch2 }
select new { left, right };
(EDIT: I've just noticed others have done a join too... ah well.)
Yet another option if you're only interested in the bits of the match is to project the sequences:
var query = first.Select(x => new { x.ShouldMatch1, x.OtherMatch2 })
.Intersect(second.Select(x => new { x.ShouldMatch1,
x.OtherMatch2 }));
You will need an equality comparer:
public class thingEqualityComparer : IEqualityComparer<thing>
{
#region IEqualityComparer<thing> Members
public bool Equals(thing x, thing y) {
return (x.shouldMatch1 == y.shouldMatch1 && x.otherMatch2 == y.otherMatch2)
public int GetHashCode(thing obj) {
// if this does not suffice provide a better implementation.
return obj.GetHashCode();
}
#endregion
}
Then you can intersect the collections with:
firstList.Intersect(secondList, new thingEqualityComparer());
Alternatively, you can override the Equal function (see John's solution).
Also please not that thing is not anonymous class - this would be for example new { prop = 1 }.

Distinct not working with LINQ to Objects [duplicate]

This question already has answers here:
LINQ's Distinct() on a particular property
(23 answers)
Closed 23 days ago.
class Program
{
static void Main(string[] args)
{
List<Book> books = new List<Book>
{
new Book
{
Name="C# in Depth",
Authors = new List<Author>
{
new Author
{
FirstName = "Jon", LastName="Skeet"
},
new Author
{
FirstName = "Jon", LastName="Skeet"
},
}
},
new Book
{
Name="LINQ in Action",
Authors = new List<Author>
{
new Author
{
FirstName = "Fabrice", LastName="Marguerie"
},
new Author
{
FirstName = "Steve", LastName="Eichert"
},
new Author
{
FirstName = "Jim", LastName="Wooley"
},
}
},
};
var temp = books.SelectMany(book => book.Authors).Distinct();
foreach (var author in temp)
{
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Console.Read();
}
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
return true;
//if (obj.GetType() != typeof(Author)) return false;
//else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
}
}
This is based on an example in "LINQ in Action". Listing 4.16.
This prints Jon Skeet twice. Why? I have even tried overriding Equals method in Author class. Still Distinct does not seem to work. What am I missing?
Edit:
I have added == and != operator overload too. Still no help.
public static bool operator ==(Author a, Author b)
{
return true;
}
public static bool operator !=(Author a, Author b)
{
return false;
}
LINQ Distinct is not that smart when it comes to custom objects.
All it does is look at your list and see that it has two different objects (it doesn't care that they have the same values for the member fields).
One workaround is to implement the IEquatable interface as shown here.
If you modify your Author class like so it should work.
public class Author : IEquatable<Author>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
public override int GetHashCode()
{
int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
int hashLastName = LastName == null ? 0 : LastName.GetHashCode();
return hashFirstName ^ hashLastName;
}
}
Try it as DotNetFiddle
The Distinct() method checks reference equality for reference types. This means it is looking for literally the same object duplicated, not different objects which contain the same values.
There is an overload which takes an IEqualityComparer, so you can specify different logic for determining whether a given object equals another.
If you want Author to normally behave like a normal object (i.e. only reference equality), but for the purposes of Distinct check equality by name values, use an IEqualityComparer. If you always want Author objects to be compared based on the name values, then override GetHashCode and Equals, or implement IEquatable.
The two members on the IEqualityComparer interface are Equals and GetHashCode. Your logic for determining whether two Author objects are equal appears to be if the First and Last name strings are the same.
public class AuthorEquals : IEqualityComparer<Author>
{
public bool Equals(Author left, Author right)
{
if((object)left == null && (object)right == null)
{
return true;
}
if((object)left == null || (object)right == null)
{
return false;
}
return left.FirstName == right.FirstName && left.LastName == right.LastName;
}
public int GetHashCode(Author author)
{
return (author.FirstName + author.LastName).GetHashCode();
}
}
Another solution without implementing IEquatable, Equals and GetHashCode is to use the LINQs GroupBy method and to select the first item from the IGrouping.
var temp = books.SelectMany(book => book.Authors)
.GroupBy (y => y.FirstName + y.LastName )
.Select (y => y.First ());
foreach (var author in temp){
Console.WriteLine(author.FirstName + " " + author.LastName);
}
There is one more way to get distinct values from list of user defined data type:
YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();
Surely, it will give distinct set of data
Distinct() performs the default equality comparison on objects in the enumerable. If you have not overridden Equals() and GetHashCode(), then it uses the default implementation on object, which compares references.
The simple solution is to add a correct implementation of Equals() and GetHashCode() to all classes which participate in the object graph you are comparing (ie Book and Author).
The IEqualityComparer interface is a convenience that allows you to implement Equals() and GetHashCode() in a separate class when you don't have access to the internals of the classes you need to compare, or if you are using a different method of comparison.
You've overriden Equals(), but make sure you also override GetHashCode()
The Above answers are wrong!!!
Distinct as stated on MSDN returns the default Equator which as stated The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T
Which means as long as you overide Equals you are fine.
The reason you're code is not working is because you check firstname==lastname.
see https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx and https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx
You can achieve this several ways:
1. You may to implement the IEquatable interface as shown Enumerable.Distinct Method or you can see #skalb's answer at this post
2. If your object has not unique key, You can use GroupBy method for achive distinct object list, that you must group object's all properties and after select first object.
For example like as below and working for me:
var distinctList= list.GroupBy(x => new {
Name= x.Name,
Phone= x.Phone,
Email= x.Email,
Country= x.Country
}, y=> y)
.Select(x => x.First())
.ToList()
MyObject class is like as below:
public class MyClass{
public string Name{get;set;}
public string Phone{get;set;}
public string Email{get;set;}
public string Country{get;set;}
}
3. If your object's has unique key, you can only use the it in group by.
For example my object's unique key is Id.
var distinctList= list.GroupBy(x =>x.Id)
.Select(x => x.First())
.ToList()
You can use extension method on list which checks uniqueness based on computed Hash.
You can also change extension method to support IEnumerable.
Example:
public class Employee{
public string Name{get;set;}
public int Age{get;set;}
}
List<Employee> employees = new List<Employee>();
employees.Add(new Employee{Name="XYZ", Age=30});
employees.Add(new Employee{Name="XYZ", Age=30});
employees = employees.Unique(); //Gives list which contains unique objects.
Extension Method:
public static class LinqExtension
{
public static List<T> Unique<T>(this List<T> input)
{
HashSet<string> uniqueHashes = new HashSet<string>();
List<T> uniqueItems = new List<T>();
input.ForEach(x =>
{
string hashCode = ComputeHash(x);
if (uniqueHashes.Contains(hashCode))
{
return;
}
uniqueHashes.Add(hashCode);
uniqueItems.Add(x);
});
return uniqueItems;
}
private static string ComputeHash<T>(T entity)
{
System.Security.Cryptography.SHA1CryptoServiceProvider sh = new System.Security.Cryptography.SHA1CryptoServiceProvider();
string input = JsonConvert.SerializeObject(entity);
byte[] originalBytes = ASCIIEncoding.Default.GetBytes(input);
byte[] encodedBytes = sh.ComputeHash(originalBytes);
return BitConverter.ToString(encodedBytes).Replace("-", "");
}
The Equal operator in below code is incorrect.
Old
public bool Equals(Author other)
{
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
NEW
public override bool Equals(Object obj)
{
var other = obj as Author;
if (other is null)
{
return false;
}
if (FirstName == other.FirstName && LastName == other.LastName)
return true;
return false;
}
Instead of
var temp = books.SelectMany(book => book.Authors).Distinct();
Do
var temp = books.SelectMany(book => book.Authors).DistinctBy(f => f.Property);

Categories

Resources