Search code examples
c#genericspetapocodynamictypenpoco

Need to call InsertBulk<T>(IEnumerable<T> pocos) but T only known at runtime


NPoco (a .NET micro ORM, derived from PetaPoco) has a method for bulk-inserting records into a database, given a list of a generic type. The method signature is:

void InsertBulk<T>(IEnumerable<T> pocos);

Internally it takes the name of the type T and uses it to determine the DB table to insert into (similarly the type's property names are mapped to the column names). Therefore it is critically important that a variable of the correct type is passed to the method.

My challenge is this:

  • I am given a list of objects to insert into the DB, as List<IDataItem> where IDataItem is an interface that all insertable objects' classes must implement
  • The list may contain objects of any type that implements IDataItem, and there may be a mixture of types in the list
  • To underline the problem - I do not know at compile time the actual concrete type that I have to pass to InsertBulk

I have tried the following approach, but the result of Convert.ChangeType is Object, so I am passing a list of Objects to InsertBulk, which is invalid.

   private static Exception SaveDataItemsToDatabase(List<IDataItem> dtos)
   {
        using (var db = new DbConnection())
        {
            try
            {
                var dtosByType = dtos.GroupBy(x => x.GetType());

                db.Data.BeginTransaction();

                foreach (var dataType in dtosByType)
                {
                    var type = dataType.Key;
                    var dtosOfType = dataType.Select(x => Convert.ChangeType(x, type));

                    db.Data.InsertBulk(dtosOfType);
                }

                db.Data.CommitTransaction();

                return null;
            }
            catch (Exception ex)
            {
                db.Data.RollbackTransaction();

                return ex;
            }
        }
    }

Is there any way I can accomplish this?


Solution

  • You have to create a new list of type List<T> and copy all your items to it, then call InsertBulk via reflection.

    foreach(var g in groups)
    {
    
        var dataItemType = g.Key;
        var listType = typeof(List<>).MakeGenericType(new [] { dataItemType });
        var list = (IList) Activator.CreateInstance(listType);
    
        foreach(var data in g)
            list.Add(data);
    
        db.Data.GetType()
               .GetMethod("InsertBulk")
               .MakeGenericMethod(dataItemType)
               .Invoke(db.Data, new object[] { list });
    
    }
    

    See it working here: https://dotnetfiddle.net/BS2FLy