Как написать эту функциональность как универсальный метод расширения в C #? - PullRequest
2 голосов
/ 15 марта 2011

У меня есть следующее:

using (var dsProperties = GetDataset(SP_GET_APPLES, arrParams))
            {
                var apples= dsProperties.Tables[0].AsEnumerable()
                    .Select(r => new Apple()
                                     {
                                         Color = r[0].ToString(),
                                         Year = r[1].ToString(),
                                         Brand= r[2].ToString()
                                     });

                return apples.ToList();
            }

Теперь я хотел бы иметь метод расширения для Dataset, в который я могу передать необходимый Type в качестве параметра и получить желаемый List обратно ... что-то вроде

dsProperties.GetList(Apple); 

, который также может использоваться для

using (var dsProperties = GetDataset(SP_GET_ORANGES, arrParams)){     
dsProperties.GetList(Orange); }

Есть ли способ сделать это?

Ответы [ 5 ]

7 голосов
/ 15 марта 2011

Как насчет этого?

static IEnumerable<T> GetList<T>(this DataSet dataSet, Func<DataRow, T> mapper) {
   return dataSet
      .Tables[0]
      .AsEnumerable()
      .Select(mapper);
}

И использование:

dsProperties.GetList<Apple>(r => 
   new Apple {
      Color = r[0].ToString(),
      Year = r[1].ToString(),
      Brand= r[2].ToString()
});

Это отображение можно также поместить в другое место.

2 голосов
/ 15 марта 2011

Что-то вроде (непроверенного) следующего, но для этого потребуется много обработки ошибок (если поле отсутствует, неправильный тип данных, пустые значения).

public static IEnumerable<T> GetEnumeration<T>(this DataSet dataset) where T: new()
{
  return dataset.Tables[0].AsEnumerable()
    .Select(r => {
      T t = new T();

      foreach (var prop in typeof(T).GetProperties())
      {
        prop.SetValue(t, r[prop.Name]);
      }
      return t;
    });
}

Вы бы использовали его как dataset.GetEnumeration<Apple>().ToList(). Обратите внимание, что это использует отражение и может быть медленным для больших наборов данных, и делает много предположений, например, каждое поле в типе соответствует столбцам в таблице данных. Лично я использую репозиторий для каждого бизнес-объекта, который явно создает объект из строки данных. Больше работы для настройки, но в долгосрочной перспективе у меня немного больше контроля. Возможно, вы могли бы взглянуть и на структуру ORM, например, на NHibernate.

1 голос
/ 15 марта 2011

Я думаю, что ваша лучшая (и самая чистая, как в «безразличной») ставка будет заключаться в создании конструктора для каждого участвующего класса (Apple, Orange и т. Д.), Который принимает DataRow и инициализируетобъект на основе строки.Затем ваш код упрощается до dsProperties.Tables[0].AsEnumerable().Select(r => new Apple(r)).Упростить его далее в общий метод расширения будет сложно, потому что у вас не может быть ограничений типа, которые указывают на существование конструктора, который принимает определенные параметры.

Если вам действительно нужен универсальный метод расширения для этого, я думаю, что вы 'Придется использовать шаблон фабрики, чтобы метод расширения мог создать фабрику, которая может конвертировать DataRow s в желаемый тип.Это будет довольно много кода для (я думаю) довольно небольшой выгоды, но я могу создать пример, если вы хотите его увидеть.

Редактировать: Я бы посоветовал вам скорее создать расширениеметод, который позволяет вам сделать это: dsProperties.CreateFruits(r => new Apple(r)).Это примерно так же коротко, как и с методом расширения, который вы запрашивали.Тем не менее, вам все равно придется создавать конструкторы, поэтому, если вы действительно хотите сохранить кодирование в конструкциях объектов, то вам, вероятно, понадобятся подходы, основанные на отражениях, как описано в других ответах.

Снова отредактируйте: @MikeEast опередил меня до последнего предложения.

0 голосов
/ 15 марта 2011

Мне нравится подход MikeEast, но мне не нравится идея, что вы должны передавать сопоставление каждому вызову GetList. Попробуйте вместо этого вариант:

public static class DataSetEx
{
    private static Dictionary<Type, System.Delegate> __maps
        = new Dictionary<Type, System.Delegate>();

    public static void RegisterMap<T>(this Func<DataRow, T> map)
    {
        __maps.Add(typeof(T), map);
    }

    public static IEnumerable<T> GetList<T>(this DataSet dataSet)
    {
        var map = (Func<DataRow, T>)(__maps[typeof(T)]);
        return dataSet.Tables[0].AsEnumerable().Select(map);
    }
}

Теперь, учитывая классы Apple & Orange, вызовите следующие методы для регистрации карт:

DataSetEx.RegisterMap<Apple>(r => new Apple()
{
  Color = r[0].ToString(),
  Year = r[1].ToString(),
  Brand= r[2].ToString(),
});

DataSetEx.RegisterMap<Orange>(r => new Orange()
{
  Variety = r[0].ToString(),
  Location = r[1].ToString(),
});

Тогда вы можете просто позвонить GetList без карты, например:

var ds = new DataSet();
// load ds here...

// then
var apples = ds.GetList<Apple>();
// or
var oranges = ds.GetList<Orange>();

Это дает хорошее разделение кода без необходимости размышления или повторения.

Это также не мешает вам использовать гибридный подход с использованием отражения в случаях, когда карта явно не была определена. Вы как бы можете получить лучшее из обоих миров.

0 голосов
/ 15 марта 2011

Существует ли стандартное соглашение об именах между вашими хранимыми процедурами и вашими типами?Если это так, то вы можете отразить тип и получить его имя, а затем преобразовать его в хранимую процедуру.

В противном случае в качестве ключа можно использовать статический словарь с именем типа, а значением будет соответствующая хранимая процедура.,Затем в вашем методе вы будете искать хранимую процедуру после размышления о типе.

Также я считаю, что вам нужно будет использовать дженерики.Что-то вроде (прошло уже много времени с тех пор, как я сделал дженерики):

public static IEnumerable<T> GetList<T>(this DataSet ds)

Преобразование столбцов в свойства вашего объекта также будет достигнуто с помощью отражения.Вы бы зациклились на свойствах объекта и нашли бы соответствующий столбец для вставки значения.

Надеюсь, это поможет вам начать.

...