LINQ GroupJoin Проблема innerKeySelector/outerKeySeletor с двумя таблицами данных

Учитывая DataSet, я оставил присоединение DataTables[1-n] к DataTable[0]. Я создал метод с подписью следующим образом:

public DataTable LeftJoin(DataSet ds, params JoinKey[] JoinKey)

Заметки:

  • Тип возвращаемого значения - DataTable, который является результирующим левым соединением.
  • DataSet ds представляет собой набор DataTables для присоединения.
  • JoinKey - это объект с двумя открытыми свойствами: тип DataType и строка Name. Эта коллекция содержит тип / имя каждого поля для использования в объединении.

Ниже приведены два фрагмента (фрагмент 1 и фрагмент 2). Фрагмент 1 работает правильно. Единственная проблема заключается в том, что второй и третий параметры GroupJoin жестко запрограммированы:

// hard-coded:
br => new                                  
{                                   
  zip = br.Field<string>("ZipCode"),                                   
  store =br.Field<double>"StoreID")
},                                 
jr => new                                 
{                                   
  zip = jr.Field<string>("ZipCode"),                                   
  store = jr.Field<double>("StoreID")                                 
}

Вышеуказанное не желательно. Вместо этого я хотел бы использовать свой объект JoinKey для динамической установки полей, к которым я хочу присоединиться (например, ZipCode и StoreID). Я пытался это сделать во Фрагменте 2. Сначала, пожалуйста, посмотрите фрагмент кода 1.

Фрагмент 1 (рабочий, жестко запрограммированный):

var dtBase = ds.Tables[0].AsEnumerable();  
for (int i = 1; i < ds.Tables.Count; i++)
{  
    var query = dtBase.GroupJoin(ds.Tables[i].AsEnumerable(),
                                 br => new 
                                 {
                                   zip = br.Field<string>("ZipCode"),
                                   store = br.Field<double>("StoreID")
                                 },
                                 jr => new
                                 {
                                   zip = jr.Field<string>("ZipCode"),
                                   store = jr.Field<double>("StoreID")
                                 },
                                 (baseRow, joinRow) => joinRow.DefaultIfEmpty()
                                     .Select(row => new
                                     {
                                         flatRow = baseRow.ItemArray.Concat((row == null) ? new object[ds.Tables[i].Columns.Count] : row.ItemArray).ToArray()
                                     })
                       ).SelectMany(s => s);  



   [... create a DataTable with the resulting left join, etc. ...]  
}

Примечание: переменная "flatRow" хранит массив объектов данных, соединенных слева; он добавляется в DataRowCollection позже в методе (не показан).

Фрагмент 2 (не работает; ошибок нет):

var dtBase = ds.Tables[0].AsEnumerable();  
for (int i = 1; i < ds.Tables.Count; i++)
{  
    var query = dtBase.GroupJoin(ds.Tables[i].AsEnumerable(),
                                 or => KeySelector(or, JoinKey),  
                                 ir => KeySelector(ir, JoinKey),
                                 (baseRow, joinRows) => joinRows.DefaultIfEmpty()
                                     .Select(joinRow => new
                                     {
                                         flatRow = baseRow.ItemArray.Concat((joinRow == null) ? new object[ds.Tables[i].Columns.Count] : joinRow.ItemArray).ToArray()
                                     })
                                 )
                       .SelectMany(s => s);

    [... create a DataTable with the resulting left join, etc. ...]  
}

Вот функция KeySelector, использованная выше (см. Встроенные комментарии):

private IEnumerable KeySelector(DataRow dr, params JoinKey[] JoinKey)
{
  List<object> gl = new List<object>();
  foreach (JoinKey jk in JoinKey)
  {
    // note that I did try to mimic the 'hard-coded' approach from Snippet 1:
    // this does not work:
    // gl.Add(dr.Field<jk.DataType>(jk.Name)); --> it does not like <jk.DataType>

    // I 'hacked' around it by using the following:

    gl.Add(dr[dr.Table.Columns.IndexOf(jk.Name)]);
  }
  return gl;
}

Фрагмент 2 возвращает данные только из DataTable[0]. Ни одна из данных из DataTable[1-n] (если таковая существует) не объединена в переменную flatRow. Интересно, что я получаю правильное количество элементов в массиве. Я знаю, что это как-то связано с методом KeySelector, но (очевидно) я понятия не имею, в чем проблема.

Пожалуйста, дайте мне знать, если кому-то нужна дополнительная информация. Ваша помощь очень ценится...

Спасибо,
Тайлер

1 ответ

Решение

Одна вещь, которая сразу же происходит, - это использование double в ключе просит неприятностей; Сравнение равенства по числам с плавающей запятой, как известно, ненормально. Я все еще смотрю, хотя.

Я полагаю, что главная проблема здесь в том, что просто нет автоматического равенства в списках на основе содержимого; вы возвращаете разные списки (от KeySelector), поэтому они просто никогда не сравнятся друг с другом. Вы могли бы быть в состоянии предоставить пользовательский компаратор...

Это грязно, но что-то возвращается:

    class SetComparer : IEqualityComparer<IEnumerable>
    {

        public readonly static SetComparer Default = new SetComparer();

        public bool Equals(IEnumerable x, IEnumerable y)
        {
            return Enumerable.SequenceEqual(x.Cast<object>(), y.Cast<object>());
        }

        public int GetHashCode(IEnumerable data)
        {
            int hash = 0;
            foreach (object obj in data)
            {
                if (obj != null)
                {
                    hash = hash * 7 + 13 * obj.GetHashCode();
                }
            }
            return hash;
        }
    }

И пройти SetComparer.Default в качестве последнего (необязательного) аргумента GroupJoin,

Обновление: нашел мою ошибку - я потерял GetHashCode(); фиксированный.

Возможно, вы могли бы также сделать нечто подобное, построив выражение во время выполнения для использования в качестве компаратора (и просто вернув саму строку в качестве ключа), но это более сложно.

Другие вопросы по тегам