How do you use linq to xml to find matching nodes in two different xml files

左心房为你撑大大i 提交于 2019-12-24 19:07:46

问题


I just asked another question here and the answer was spot on.

But that addressed what was essentially a syntax problem. Now I need help with an actual resolution.

This is the same code from the previou question (fixed up and with stuff added).

XElement FILE1 = XElement.Load (@"..\FILE1.XML");
XElement FILE2 = XElement.Load (@"..\FILE2.XML");

var orders = from file1 in FILE1.Descendants("Players").Elements("Player")
                        select new {
                            name=new {
                                clientID=ulm.Element("ClientID").Value,
                                firstName=file1.Element("FirstName").Value,
                                lastName=file1.Element("LastName").Value
                            }                           
                        };

var orders2 = 
             from file2 in FILE2.Descendants("Players").Elements("Player")
                        select new {
                            name=new {
                                clientID=ulm.Element("ClientID").Value,
                                firstName=file2.Element("FirstName").Value,
                                lastName=file2.Element("LastName").Value
                            }                           
                        };

var matchingResults = from i in orders from j in orders2 where (i.name.firstName==j.name.firstName && i.name.lastName==j.name.lastName)
                            select i;
matchingResults.Dump()     

To make it interesting I have added a ClientID to each sequence result before trying to match them up.

What I need is to know does a Player node from order EXISTS in a player node from orders2. Or does it NOT EXIST? Ideally I would also be able to CHOOSE the selection criteria for the NOT EXISTS/EXISTS check. (LastName, or FirstName && LastName, or ClientID only, etc.)

I have NO IDEA how to go about this. Thanks for your help.


回答1:


  • Use Enumerable.Intersect or Enumerable.Except
  • Use concrete types instead of anonymous types.
  • To be able to choose the selection criteria you can create a parameterized IEqualityComparer<Client>.

    class Client
    {
        public string ClientID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
    [Flags]
    enum Criteria { 
        ClientID, FirstName, LastName
    }
    
    class ClientEqualityComparer : IEqualityComparer<Client> {
    
        private Criteria criteria;
        public ClientEqualityComparer(Criteria criteria) {
            this.criteria = criteria;
        }
    
        #region IEqualityComparer<Client> Membres
    
        public bool Equals(Client x, Client y)
        {
            if (criteria.HasFlag(Criteria.ClientID) && x.ClientID != y.ClientID)
                return false;
            if (criteria.HasFlag(Criteria.FirstName) && x.FirstName != y.FirstName)
                return false;
            if (criteria.HasFlag(Criteria.LastName) && x.LastName != y.LastName)
                return false;
            return true;
        }
    
        public int GetHashCode(Client obj)
        {
            int hash = 17;
            if (criteria.HasFlag(Criteria.ClientID))
                hash = hash * 31 + obj.ClientID;
            if (criteria.HasFlag(Criteria.FirstName))
                hash = hash * 31 + obj.FirstName;
            if (criteria.HasFlag(Criteria.LastName))
                hash = hash * 31 + obj.LastName;
        }
    
        #endregion
    }
    
    static void Main(string[] args)
    {
    
        IEnumerable<Client> orders;
        IEnumerable<Client> orders2;
        //...
        var matchingIdFn = orders.Intersect(orders2, 
            new ClientEqualityComparer(Criteria.ClientID | Criteria.FirstName));
    
        var matchingIdFnLn = orders.Intersect(orders2, 
            new ClientEqualityComparer(Criteria.ClientID | Criteria.FirstName | Criteria.LastName));
    
        var different = orders.Except(orders2, new ClientEqualityComparer(Criteria.ClientID | Criteria.FirstName));            
    }
    

var orders = from file1 in FILE1.Descendants("Players").Elements("Player")
                    select new Client{
                        ClientID=ulm.Element("ClientID").Value,
                        FirstName=file1.Element("FirstName").Value,
                        LastName=file1.Element("LastName").Value                        
                    };



回答2:


I second Ahmed KRAIEM's suggestion to use Intersect or Except

Here's another solution that lets you compare using any arbitrary lambda:

void Main()
{
XElement FILE1 = XElement.Parse(
@"<Root>
    <Players>
        <Player><ClientId>1</ClientId><FirstName>Bob</FirstName><LastName>Smith</LastName></Player>
        <Player><ClientId>2</ClientId><FirstName>John</FirstName><LastName>Smith</LastName></Player>
    </Players>
</Root>");
    XElement FILE2 = XElement.Parse(
@"<Root>
    <Players>
        <Player><ClientId>2</ClientId><FirstName>Bob</FirstName><LastName>Smith</LastName></Player>
        <Player><ClientId>3</ClientId><FirstName>Mike</FirstName><LastName>Smith</LastName></Player>
    </Players>
</Root>");

var orders = from file1 in FILE1.Descendants("Players").Elements("Player")
                    select new Player(Int32.Parse(file1.Element("ClientId").Value), file1.Element("FirstName").Value, file1.Element("LastName").Value);

var orders2 = from file2 in FILE2.Descendants("Players").Elements("Player")
                    select new Player(Int32.Parse(file2.Element("ClientId").Value), file2.Element("FirstName").Value, file2.Element("LastName").Value);

//orders.Dump();
//orders2.Dump();

var exists = orders2.Intersect(orders, new LambdaEqualityComparer<Player>((i, j) => i.FirstName == j.FirstName && i.LastName == j.LastName));
// or
//var exists = orders2.Intersect(orders, new LambdaEqualityComparer<Player>((i, j) => i.ClientId == j.ClientId));
exists.Dump();

var notExists = orders2.Except(orders, new LambdaEqualityComparer<Player>((i, j) => i.FirstName == j.FirstName && i.LastName == j.LastName));
// or
//var notExists = orders2.Except(orders, new LambdaEqualityComparer<Player>((i, j) => i.ClientId == j.ClientId));
notExists.Dump();
}

public class Player
{
public int ClientId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

public Player(int clientId, string firstName, string lastName)
{
    ClientId = clientId;
    FirstName = firstName;
    LastName = lastName;
}
}

public class LambdaEqualityComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> EqualityComparer { get; set; }

public LambdaEqualityComparer(Func<T, T, bool> equalityComparer)
{
    EqualityComparer = equalityComparer;
}

public bool Equals(T x, T y)
{
    return EqualityComparer(x, y);
}

public int GetHashCode(T obj)
{
    // If the hash codes are different, then Equals never gets called. Make sure Equals is always called by making sure the hash codes are always the same.
    // (Underneath, the .NET code is using a set and the not (!) of a Find method to determine if the set doesn't already contain the item and should be added.
    // Find is not bothering to call Equals unless it finds a hash code that matches.)
    //return obj.GetHashCode();
    return 0;
}
}

Note that if you don't want to create a Player object, you can remove it completely, populate your anonymous objects in orders and orders2 just like you were doing before, and create a new LambdaEqualityComparer<dynamic> instead of new LambdaEqualityComparer<Player>, though it will be slower due to reflection calls on dynamic.



来源:https://stackoverflow.com/questions/20218821/how-do-you-use-linq-to-xml-to-find-matching-nodes-in-two-different-xml-files

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!