MrProgram
MrProgram

Reputation: 5242

Comparing lists of different type with linq

I have a lists the loads from an XML.

The XML looks like this:

<TablesToSynchronize>
<Table name="dbo.Table1" />
<Table name="dbo.Table2" />
<Table name="dbo.Table3" />
<Table name="dbo.Table4" />
</TablesToSynchronize>

I want to compare a list of XElement with a list of string, but I can't get it together.

    private static readonly XDocument TableSettings = XDocument.Load(GetAssemblyDirectory() + @"\Tables.xml");
    private static List<XElement> TablesToSync = new List<XElement>();

    static void Main(string[] args)
    {
        {
            var test = TableSettings.Descendants("Table").Select(x => x.Attribute("name").Value.ToString());

            test = test.Where(args.ToList().Contains).ToList();


            var test2 = (from x in TableSettings.Descendants("Table")
                where x.Attribute("name").Value == "dbo.Table1"
                select x).ToList();

            //TablesToSync = ??
        }
    }

var test2 is working fine, but it returns only the XElement with "Table1". I want to loop args and check if any item there matches XElement attribute ("name").Value.

What am I missing here?

args contains dbo.Table1, dbo.Table2, dbo.Table3

UPDATE: test2 gives me a list of XElement containing dbo.Table1. test gives me an empty list of string.

The output should be a List<XElement>

Upvotes: 0

Views: 179

Answers (2)

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131364

The following will return two elements:

args = new[] { "dbo.Table1", "dbo.Table3" };
var test = (from elt in TableSettings.Descendants("Table")
            where args.Contains(elt.Attribute("name").Value)
            select elt).ToList();
Debug.Assert(test.Count==2);

or

args = new[] { "dbo.Table1", "dbo.Table3" };
var test = (from elt in TableSettings.Descendants("Table")
            where args.Contains((string)elt.Attribute("name"))
            select elt).ToList();

or the equivalent as a method chain:

var test = TableSettings.Descendants("Table")
                        .Where(elt => args.Contains(elt.Attribute("name").Value))
                        .ToList();

An XAttribute explicitly convertible to one of the basic .NET types so you have to use the (string) cast. Otherwise .NET will compare the XAttribute instance against a string value.

If you have a lot of arguments and worry that Contains is slow, you can convert the string array to a dictionary:

var argsDict = args.ToDictionary(x => x);
var test = TableSettings.Descendants("Table").                    
                Where(elt => argsDict.ContainsKey(elt.Attribute("name").Value))
                .ToList();

A quick test of 10000000 iterations returns these not-very-meaningful results:

  • Where : 33466320 ticks
  • Join : 32560386 ticks
  • Dictionary : 22998512 ticks
  • HashSet : 25979814 ticks

The difference between Where and Join is negligible (~2%) and can easily be attributed to noise. A dictionary or hashset though is significantly faster (~30%).

In fact, the differences between the equivalent methods fluctuate a lot and are essentially meaningless.

This isn't surprising as a dictionary/hashset is essentially acting as an index would act in SQL. In fact, SQL Server will use hash joins when one of the two tables in a join is (relatively) small.

A dictionary may perform better if you need to access more than the key from the arguments, as you can lookup and retrieve the matching value in a single operation. This is similar to using a covering index in SQL:

MyOtherClass v=null;
var test = from elt in TableSettings.Descendants("Table")
           let value = elt.Attribute("name").Value
           where argsDict.TryGetValue(value, out v)
           select new {elt, v};

This is rather ugly though in C# 5. This is where the (now scrapped) declaration expressions in C# 6 would have helped:

var test = from elt in TableSettings.Descendants("Table")
           let value = elt.Attribute("name").Value
           where argsDict.TryGetValue(value, out var v)
           select new {elt, v};

Upvotes: 1

Mahesh
Mahesh

Reputation: 8892

There are two approaches to achieve what you want one using the Where and second using the JOIN

You can apply the join to get your result as

  var mytest = TableSettings.Descendants("Table").Join(arggs, x => x.Attribute("name").Value, y => y, (x, y) => new { Element = x });

and you can apply the Where as

   TablesToSync = TableSettings.Descendants("Table").Where(x => arggs.Contains(x.Attribute("name").Value)).ToList();

If your data is small then it should not give lot of performance improvements to which one you decide to use. But if you have large data read comments and more stuff about this you should decide for yourself which approach you want to follow.

Upvotes: 1

Related Questions