如何使这个LINQ全外连接正常运行?

3
我正在构建一个WPF应用程序,用于监视用户计算机上的目录。该应用程序从受监视的目录上传文件,然后将一些信息保存到SQLite数据库中。业务处理的一部分是不重新处理已经上传的文件,并重新上传自上次上传以来发生更改的文件。
我有两个辅助方法,构建并返回一个List<FileMetaData>,我使用 LINQ - Full Outer Join来连接它们。我的问题是当我使用我的FileMetaData对象时,代码似乎无法正常工作。看起来一切都应该正常工作,但我不知道为什么它不起作用。通常我会尝试在其他线程上发布评论,但我目前没有这样做的“Rep”。
下面是一个示例,如果您在LINQpad中运行它,请确保将语言设置为“C# Program”。我应该怎么做才能使样本与对象一起工作?非常感谢!
    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;
    }

编辑:

下面的答案正是我所需要的。我按照下面定义的IEqualityComparer实现了它,并将我的调用更改为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();
        }
    }
1个回答

4
问题在于Union会通过检查相等性来给你提供消除重复项后的结果。当你使用匿名类型时,相等性的定义是“所有字段的值都相等”。当你声明一个类型时,它将使用Equals方法。由于你没有重写Equals,它默认为ReferenceEquals,这意味着两个不同的实例,无论其字段值如何,它们都不相等。

解决方法有三种:

1)在查询中使用匿名类型,然后在Union之后转换为定义的类型:

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

2) 定义一个 IEqualityComparer<FileMetaData> 类,该类定义了您想要的相等性(仅 FilePath?所有字段?),并将其实例传递给 Union()

3) 在 FileMetaData 中重写 Equals()(和 GetHashCode())。

2) 和 3) 将非常相似,但重写 Equals() 可以(并且将)在任何检查相等性的情况下使用,而不仅仅是在此情况下。


非常感谢您的回答。我完全忘记了IEqualityComparer。 - Frito

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接