2

I have a list (or can be array) of strings that I want to dynamically create an anonymous object from. How do I do this?

var dataSet = new DataSet();
dataSet.ReadXml(@"");
var dataTable = dataSet.Tables[0];
var dataRow = dataTable.Rows[0];    

var keys = new List<string> {"Column1","Column2"};
var result = new {keys[0] = dataRow[keys[0]], keys[1] = dataRow[keys[1]]}

So that list named "keys" is going to be created outside this method and can contain 1 to many values. I tried creating a dictionary and looping through the list and adding key/value pairs to the dictionary but then I couldnt figure out how to convert the dictionary back to an anonymous type. I also experimented with the expando objects but that didn't seem to get me any farther.

I must be able to return an anonymous type as the result of this method will be using with the GroupBy clause of a LINQ query.

Here is the method I had to dynamically create the dictionary:

    public object Key(DataRow dataRow, List<String> keys)
    {
        var dictionary = new IDictionary<string, object>;
        foreach (string key in keys)
        {
            dictionary.Add(key, dataRow[key]);
        }
        return dictionary;
    }

Here is my LINQ query:

var duplicates = dataTable.AsEnumerable().GroupBy(r => Key(r, keys)).Where(c => c.Count() > 1).ToList();

The GroupBy clause works if I hardcode in an anonymous type from the Key() method. Basically I just need the GroupBy clause to be dynamically set based upon the values in the keys list.

Mike
  • 235
  • 2
  • 8
  • 25
  • 1
    I think you're confusing *anonymous* types - which are defined ad-hoc but are *strongly* typed objects, with *dynamic* types, which can have any number of runtime-defined properties, which is what you're trying to do here, if I understand correctly. I have a feeling that what you need is *neither*. Can you elaborate on the GroupBy thing, show the code that will consume this anonymous object? – Avner Shahar-Kashtan Jun 28 '14 at 05:04
  • As @AvnerShahar-Kashtan says, you are likely confusing dynamic and anonymous. This might help if you're happy with creating dynamic objects from a dictionary - http://stackoverflow.com/questions/7595416/convert-dictionarystring-object-to-anonymous-object – Simon Campbell Jun 28 '14 at 05:06
  • @AvnerShahar-Kashtan - I added in the LINQ query above. – Mike Jun 28 '14 at 05:09
  • Simon - I tried that code from your link previously and couldnt get it to work. More specifically, the dynamic object didnt seem to have that Property attribute. – Mike Jun 28 '14 at 05:12
  • Ok, it's clearer now. You have a datatable that you want to group by a compound property composed of several columns not known at compile time. Right? – Avner Shahar-Kashtan Jun 28 '14 at 05:16
  • Yes, that sounds correct. – Mike Jun 28 '14 at 05:19

1 Answers1

2

Stripping down your question, what you want is to be able to group a list of items based on a runtime property which could be composed of one or more properties of that item. In essence, it means you need a selector function (which is your Key method) that transforms an item into a key.

In order for GroupBy to work, it needs to be able to compare any two instances of the key to see if they're equal. This means the key needs to implement a meaningful Equals() method, or you need an IEqualityComparer implementation that does the work for you. In this case I wouldn't bother with creating a new Key, just write an Equality Comparer that can compare two DataRows directly:

var duplicates = dataTable
        .AsEnumerable()
        .GroupBy(r => r, new MyDataRowComparer(keys))
        .Where(c => c.Count() > 1)
        .ToList();


internal class MyDataRowComparer : IEqualityComparer<DataRow>
{
    private readonly string[] _keys;

    public MyDataRowComparer(string[] keys)
    {
        _keys = keys; // keep the keys to compare by.
    }

    public bool Equals(DataRow x, DataRow y)
    {
        // a simple implementation that checks if all the required fields 
        // match. This might need more work.
        bool areEqual = true;
        foreach (var key in _keys)
        {
            areEqual &= (x[key] == y[key]);
        }
        return areEqual;
    }

    public int GetHashCode(DataRow obj)
    {
        // Add implementation here to create an aggregate hashcode.
    }
}    
Avner Shahar-Kashtan
  • 14,492
  • 3
  • 37
  • 63
  • Thank you, Avner. I added the GetHashCode() implementation and adjusted the Equals(). I had to use the object.Equals() function to compare values because == was return false every time. Another option would have been to add the ToString() to every value. – Mike Jun 28 '14 at 18:05