How can I make this LINQ full-external-join function correctly?

I am creating a WPF application that tracks a directory on a user's computer. The application downloads files from a controlled directory and then saves some information in SQLite db. Part of the business process is not to re-process files that have already been downloaded, and to re-upload files that have been downloaded but changed since the last upload.

I have two helper methods that build and return the List<FileMetaData> that I used LINQ - Full Outer Join to join. My problem is that the code does not work when I use my FileMetaData object. Everything seems to work, but I don’t understand why it doesn’t work. Usually I try to post a comment in another thread, but currently I don't have "Rep".

Below is a sample that I created that shows my problem if you run it on LINQpad . Before you press the start button, make sure that you set the language as "C # Program". What should I do differently so that the sample works with objects? Thanks a ton!

  void Main() { var dbItems = new List<FileMetaData>() { new FileMetaData {FilePath = "C:\\Foo.txt", DbTimestamp = "1" }, new FileMetaData {FilePath = "C:\\FooBar.txt", DbTimestamp = "3" }, }; var fsItems = new List<FileMetaData>() { new FileMetaData {FilePath = "C:\\Bar.txt", FsTimestamp = "2" }, new FileMetaData {FilePath = "C:\\FooBar.txt", FsTimestamp = "3" }, }; var leftOuter = from d in dbItems join f in fsItems on d.FilePath equals f.FilePath into temp from o in temp.DefaultIfEmpty(new FileMetaData(){}) select new FileMetaData { FilePath = d.FilePath, DbTimestamp = d.DbTimestamp, FsTimestamp = o.FsTimestamp, }; var rightOuter = from f in fsItems join d in dbItems on f.FilePath equals d.FilePath into temp from o in temp.DefaultIfEmpty(new FileMetaData(){}) select new FileMetaData { FilePath = f.FilePath, DbTimestamp = o.DbTimestamp, FsTimestamp = f.FsTimestamp, }; var full = leftOuter.AsEnumerable().Union(rightOuter.AsEnumerable()); leftOuter.Dump("Left Results"); rightOuter.Dump("Right Results"); full.Dump("Full Results"); } // Define other methods and classes here public class FileMetaData { public string FilePath; public string DbTimestamp; public string FsTimestamp; } 

EDIT:

The answer below was exactly what I was looking for. I implemented IEqualityComparer as defined below and changed my call to var full = leftOuter.Union(rightOuter, new FileMetaDataCompare()) ...

  public class FileMetaDataCompare : IEqualityComparer<FileMetaData> { public bool Equals(FileMetaData x, FileMetaData y) { var areEqual = x.FilePath == y.FilePath; areEqual = areEqual && x.DbTimestamp == y.DbTimestamp; areEqual = areEqual && x.FsTimestamp == y.FsTimestamp; return areEqual; } public int GetHashCode(FileMetaData obj) { var hCode = string.Concat(obj.FilePath, obj.DbTimestamp, obj.FsTimestamp); return hCode.GetHashCode(); } } 
+3
source share
1 answer

The problem is that Union will give you duplicate elimination results by checking equality. When you use anonymous types, the definition of the equality "all fields have equal value". when you declare a type, it will use the Equals method. Since you did not redefine Equals , it defaults to ReferenceEquals , which means that two separate instances are not equal regardless of their field values.

Three ways to solve this problem:

1) Use anonymous types in your queries and convert them to specific types after combining:

 var full = leftOuter.Union(rightOuter).Select( i=> new FileMetaData { FilePath = i.FilePath, DbTimestamp = i.DbTimestamp, FsTimestamp = i.FsTimestamp }); 

2) Define the IEqualityComparer<FileMetaData> class that defines the equality you want (just FilePath? All fields?) And pass it an instance of Union()

3) Override Equals() (and GetHashCode() ) in FileMetaData .

2) and 3) will be very similar, but overriding Equals() can (and will) be used whenever you check for equality, and not just in this situation.

+4
source

Source: https://habr.com/ru/post/921419/


All Articles