bairog
bairog

Reputation: 3373

How to find IEnumerable<T>.ToList() method by explicitly specifying type of parameters and then call it with a custom parameter type?

Let's say we have the following code (highly simplified modification of my own code):

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ReflectionTest
{
    public interface IDataContext
    { }

    sealed class DataContext : IDataContext
    { }

    public interface ITable<T> : IEnumerable<T>
        where T : class
    {
        List<T> source { get; }
    }


    sealed class Table<T> : ITable<T>
        where T : class, new()
    {
        public Table()
        {
            source = new List<T>() { new T() };
        }

        public List<T> source { get; set; }

        public IEnumerator<T> GetEnumerator() => source.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => source.GetEnumerator();
    }

    public static class DataExtensions
    {
        public static ITable<T> GetTable<T>(this IDataContext dataContext)
            where T : class, new()
        {
            return new Table<T>();
        }
    }

    public class TestData
    {
        public string Name = "Test";
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            var dataContext = new DataContext();

            //var ret = dataContext.GetTable<TestData>().ToList(); but via reflection:
            var getTableMethod = typeof(DataExtensions).GetMethod("GetTable", new[] { typeof(IDataContext) });
            var getTableGeneric = getTableMethod.MakeGenericMethod(typeof(TestData));
            var testDataITable = getTableGeneric.Invoke(null, new object[] { dataContext });

            //var toListMethod = typeof(Enumerable).GetMethod("ToList", new[] { typeof(IEnumerable) });   this doesn't find IEnumerable<T>.ToList() method!
            var toListMethod = typeof(Enumerable).GetMethod("ToList");
            var toListGeneriс = toListMethod.MakeGenericMethod(testDataITable.GetType());

            //System.ArgumentException: 'Unable to cast object of type "ReflectionTest.Table`1[ReflectionTest.TestData]"
            //to type "System.Collections.Generic.IEnumerable`1[ReflectionTest.Table`1[ReflectionTest.TestData]]".'
            var ret = toListGeneriс.Invoke(null, new object[] { testDataITable });

            Console.ReadKey();
        }
    }
}

I'm trying to call var ret = dataContext.GetTable<TestData>().ToList(); but via reflection. And I have two problems here:

  1. typeof(Enumerable).GetMethod("ToList", new[] { typeof(IEnumerable) }); doesn't find IEnumerable.ToList() method. I know that there is only one version of this method and I can simply use typeof(Enumerable).GetMethod("ToList"); - but still, how to find it by explicitly specifying type of parameters?
  2. How can I then correctly call this method with ITable<TestData> parameter type? With my code I get an exception:

System.ArgumentException: 'Unable to cast object of type "ReflectionTest.Table`1[ReflectionTest.TestData]" to type System.Collections.Generic.IEnumerable`1[ReflectionTest.Table`1[ReflectionTest.TestData]]

Upvotes: -1

Views: 102

Answers (2)

Svyatoslav Danyliv
Svyatoslav Danyliv

Reputation: 27282

For linq2db there are helper methods which helps to wotk with reflections. But if your goal is to copy database to another database, there is simplier option.

public static class BulkCopyExtensions
{
    public static void CopyData<T>(this IQueryable<T> sourceQuery, IDataContext destination) where T : class
    {
        destination.GetTable<T>().BulkCopy(sourceQuery.AsEnumerable());
    }

    public static void CopyData<T>(this IDataContext source, IDataContext destination) where T : class
    {
        CopyData(source.GetTable<T>(), destination);
    }

    // MemberHelper.MethodOfGeneric is linq2db method
    private static readonly MethodInfo _copyDataConnections = MemberHelper.MethodOfGeneric(() => ((DataConnection)null!).CopyData<object>(null!));

    public static void CopyAllData(this IDataContext source, IDataContext destination) 
    {
        var typesForCopy = source.GetType().GetProperties()
            .Where(p => typeof(ITable<>).IsSameOrParentOf(p.PropertyType))
            .Select(p => p.PropertyType.GetGenericArguments()[0])
            .ToList();

        foreach (var type in typesForCopy)
        {
            _copyDataConnections.MakeGenericMethod(type).Invoke(null, [source, destination]);
        }
    }
}

So, in your case, copying databse is just call CopyAllData extension method.

dataContextSqlCe.CopyAllData(dataContextSqLite);

Note that this method do not care about order of inserts an you many have FK errors.

Upvotes: 1

Sweeper
Sweeper

Reputation: 271410

For the first problem, you can use Type.MakeGenericMethodParameter to create a "type parameter".

Returns a signature type object that can be passed into the Type[] array parameter of a GetMethod method to represent a generic parameter reference.

Then use that to create a Type representing IEnumerable<TSource>. Passing that to GetMethod will gets you the desired method.

var typeVariable = Type.MakeGenericMethodParameter(0);
var parameterType = typeof(IEnumerable<>).MakeGenericType(typeVariable)
var toListMethod = typeof(Enumerable).GetMethod("ToList", [parameterType]);

For the second problem, it is because you parameterised ToList incorrectly. Recall the signature of ToList:

public static List<TSource> ToList<TSource> (
    this IEnumerable<TSource> source
);

TSource should be the element type of the collection - TestData, not the type of the collection itself.

var toListGeneriс = toListMethod.MakeGenericMethod(typeof(TestData));

Upvotes: 0

Related Questions