Сложный класс C # для SQLBulkInsert миллионы или строки

У меня проблема, по-видимому, очень просто, но я не нашел ничего связанного.

public class Fruit
{
  public string name { get; set; }
  public string color { get; set; }
  public string shape { get; set; }
  public Image image { get; set; }
}

public class Image
{
  public string Id { get; set; }
  public string URL { get; set; }
}

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

public static DataTable ToDataTable<T>(this IList<T> data)
{
  PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
  DataTable table = new DataTable();
  foreach (PropertyDescriptor prop in properties)
       table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);

  foreach (T item in data)
  {
       DataRow row = table.NewRow();
       foreach (PropertyDescriptor prop in properties)
            row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;

       table.Rows.Add(row);
   }

   return table;
}

Любая подсказка о том, как получить идентификатор свойства Image для выполнения SQLBulkCopy?

Кстати, вот метод, который я использую для вставки:

public static void SaveToDatabase(DataTable data)
{
    using (var connection = new SqlConnection(dbConn))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Fruit";
                sqlBulkCopy.ColumnMappings.Add("name", "name");
                sqlBulkCopy.ColumnMappings.Add("color", "color");
                sqlBulkCopy.ColumnMappings.Add("shape", "shape");
                sqlBulkCopy.ColumnMappings.Add("image", "image");

                sqlBulkCopy.WriteToServer(data);
            }
            transaction.Commit();
        }
        catch (Exception ex)  // Here I got the datatype Exception because I don't know how to get the Image.ID value
        {
            transaction.Rollback();
        }
    }
}

Очевидно, мой пост немного сбивает с толку, поэтому я собираюсь добавить здесь проблему:

Как получить значения URL и ID из Image класса в следующем коде? В настоящее время он приносит объект Image, который неизвестен, и выдает исключение.

sqlBulkCopy.ColumnMappings.Add("image", "image");

Как их сопоставить? Я знаю, что могу делать это строка за строкой, но мне нужно обрабатывать более 2 миллионов строк при каждом выполнении. Вот почему я пытаюсь использовать массовую копию.

Мне нужно добавить, что эти данные поступают из вызова API в формате Json


person Weasel    schedule 27.07.2020    source источник
comment
Можете ли вы включить код, связывающий эти три фрагмента вместе? То есть, как Fruit и ToDataTable используются / передаются в вашу функцию SaveToDatabase?   -  person RBarryYoung    schedule 27.07.2020
comment
SqlBulkCopy поддерживает только примитивные типы. Чтобы заполнить VARBINARY(MAX) столбцы (которые, я надеюсь, вы используете вместо устаревшего типа IMAGE, имеющего неверно подходящее имя), вам необходимо передать byte[]. Но если вам действительно нужна производительность, вы не хотите возиться с потенциально огромными байтовыми массивами в DataTables, а вместо этого хотите передавать данные. Однако это более сложный процесс, поскольку он включает создание SqlDataRecord экземпляров на лету и использование SqlBytes. Вероятно, существуют библиотеки для упрощения этого, но рекомендовать их - это OT на SO.   -  person Jeroen Mostert    schedule 27.07.2020
comment
Спасибо за ответ: @RBarryYoung: APIClient<fruit> client = new APIClient<fruit>("fruit", UserName, Password); var retrievedData = client.GetAll(); DataTable data = new DataTable(); data = ToDataTable(retrievedData.Result); SaveToDatabase(data);   -  person Weasel    schedule 27.07.2020
comment
@JeroenMostert благодарит за ваш ответ: я согласен с вами, имя неправильное, я должен использовать Picture для класса вместо Image, я не хочу хранить и тип данных Image, но детали изображения фруктов в этом case, ID и URL-адрес изображения   -  person Weasel    schedule 27.07.2020
comment
Ой, я полностью пропустил ваш Image класс, на самом деле, это не изображение (как в фактических данных растрового изображения на System.Drawing.Image), а всего лишь подобъект из двух столбцов. Независимо от того, как вы вставляете, вы не можете сохранить объект непосредственно в столбце (SQL Server не является базой данных O-O). Вы можете делать такие вещи, как хранить JSON или XML для сериализации данных в столбец, использовать ORM, например Entity Framework (или для чуть менее сложных сценариев Dapper), или просто разделить объект (что в данном случае немного) на два отдельных столбцы.   -  person Jeroen Mostert    schedule 27.07.2020
comment
Код в комментариях не очень разборчив, поэтому, если можете, просто отредактируйте свой вопрос и добавьте туда код. (Я бы сделал это за вас, но сейчас я использую свое мобильное устройство, и редактирование на нем затруднительно)   -  person RBarryYoung    schedule 27.07.2020
comment
Что сказал @JeroenMostert. Если вы используете нестандартные типы данных, вам понадобится специальный промежуточный код, чтобы преобразовать его во что-то, что понимает SQLBulkCopy, поэтому я хотел увидеть соединительный код ...   -  person RBarryYoung    schedule 27.07.2020
comment
И прежде чем кто-либо еще поднимет этот вопрос, я, вероятно, должен сделать это сам: технически вы можете хранить объекты в столбцах, добавляя типы как типы CLR в вашу базу данных, но для примитивных объектов, у которых нет методов, это определенно не стоит делать с точки зрения усилий, накладных расходов, простоты использования и обслуживания, поэтому существуют ORM.   -  person Jeroen Mostert    schedule 27.07.2020
comment
Извините, ребята, я неправильно скопировал класс Fruit, плохо, так как я работаю на виртуальной машине и мне не разрешено копировать, я копирую вручную.   -  person Weasel    schedule 28.07.2020
comment
Я добавил основной вопрос внизу сообщения, надеюсь, это немного понятно, спасибо всем за ваш вклад   -  person Weasel    schedule 28.07.2020


Ответы (1)


После изменения кода для проверки я нашел решение:

if (prop.GetChildProperties().Count > 1)
    row[prop.Name] = (Image)prop.GetValue(item);
else
    row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;

Решением было только привести GetValue к классу Image, это было применено в методе ToDataTable:

public static DataTable ToDataTable<T>(this IList<T> data)
{
  PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
  DataTable table = new DataTable();
  foreach (PropertyDescriptor prop in properties)
       table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);

  foreach (T item in data)
  {
       DataRow row = table.NewRow();
       foreach (PropertyDescriptor prop in properties)
           if (prop.GetChildProperties().Count > 1)
               row[prop.Name] = (Image)prop.GetValue(item);
           else
               row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;

       table.Rows.Add(row);
   }

   return table;
}

Спасибо за ваше время и помощь!

person Weasel    schedule 27.07.2020