Frage

I'm trying to get a GroupJoin to work with multiple unknown keys using LINQ.

I've seen solutions with anonymous types, but the keys were always pre-defined. In my case, they're user-defined, so I wouldn't know that information at compile time. I tried to use a list of key values and an array of key values, but they never match.

So... this works like a charm:

Func<Component, string> getKeyValue = x => x.Attributes                            //from attributes
                                            .Single(a => a.Name == _keyAttribute) //selects the key attribute
                                            .Value;                              //gets attribute value

var leftJoin = source.GroupJoin(target,                  //join
                                getKeyValue,            //on the same
                                getKeyValue,           //condition
                                (src, corresp) => new
                                {
                                   src,
                                   corresp
                                })
                    .SelectMany(z => z.corresp.DefaultIfEmpty()
                                              .Select(tgt => new { z.src, tgt })) //selects matching
                    .ToList();                                                   //source and target

but this doesn't:

Func<Component, List<string>> getKeyValues = x => x.Attributes                 //from attributes
                                 .Where(a => _keyAttributes.Contains(a.Name)) //selects key attributes
                                 .OrderBy(a => a.Name)                       //order them by name
                                 .Select(a => a.Value)                      //gets attributes' values
                                 .ToList();
var leftJoin = source.GroupJoin(target,                  //join
                                getKeyValues,           //on the same
                                getKeyValues,          //condition
                                (src, corresp) => new
                                {
                                   src,
                                   corresp
                                })
                    .SelectMany(z => z.corresp.DefaultIfEmpty()
                                              .Select(tgt => new { z.src, tgt })) //selects matching
                    .ToList();                                                   //source and target

If it helps, this is the structure I'm working on:

List<string> _keyAttributes;
List<Component> source;
List<Component> target;

[DataContract]
public class Component
{
   [DataMember]
   public List<Attribute> Attributes { get; set; }

   public Component()
   {
      new List<Attribute>();
   }
}

[DataContract]
public class Attribute
{
    [DataMember]
    public string Name { get; set;}
    [DataMember]
    public string Value { get; set;}
}   

Is there a way to solve this using LINQ library or I'd need my own GroupJoin extension method to do that?

War es hilfreich?

Lösung

The problem is that the getKeyValues selector you provide will return a List from each Component to compare on. The returned List from each will be compared by reference, so essentially you've got this going on:

var listA = new List<string> { "SomeString" };
var listB = new List<string> { "SomeString" };

bool areListsEqual = listA == listB;

areListsEqual will return false, since they are compared by reference. Essentially what you'll need is either a different EqualityComparer (available to add via an overload of GroupJoin), or you need a way to compare the properties by value.

An example of something that would work (but not necessarily a good way to do it) would be:

Func<Component, string> getKeyValues = x =>
    string.Join(",", x.Attributes
                      .Where(a => _keyAttributes.Contains(a.Name))
                      .OrderBy(a => a.Name)
                      .Select(a => a.Value).ToArray());

This will create a string that represents the values in each list, and that will be used for comparison. A better way would be to use an EqualityComparer that has your own logic for what makes the lists actually equal based on the values contained within. See here for how you might compare two lists.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top