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

时间:2012-07-24 16:46:56

标签: c# .net linq full-outer-join

我正在构建一个监视用户计算机上的目录的WPF应用程序。应用程序从受监视目录上载文件,然后将一些信息保存到SQLite数据库中。部分业务处理是不重新处理已上载的文件,并重新上载已上载但自上次上载以来已更改的文件。

我有两个辅助方法可以构建并返回List<FileMetaData>,我使用LINQ - Full Outer Join来加入。我的问题是,当我使用FileMetaData对象时,代码似乎不起作用。看起来一切都应该都有效,但我不知道为什么它不起作用。我通常会尝试在另一个帖子上发表评论,但我目前没有“Rep”这样做。

以下是我构建的示例,如果您在LINQpad中运行它,则会显示我的问题。在单击运行按钮之前,请确保将语言设置为“C#程序”。我应该以不同的方式让样本与对象一起工作?万分感谢!

    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 个答案:

答案 0 :(得分: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)覆盖Equals()中的GetHashCode()(和FileMetaData)。

2)和3)将非常相似,但只要你检查相等性,就可以(并且将)使用覆盖Equals(),而不仅仅是在这种情况下。