Create Default Value For Expression<Func<TDto, object>[] In Runtime By Knowing the Type Only

1.2k Views Asked by At

i want to call ProjectTo dynamically for example on this overload:

public static IQueryable ProjectTo(this IQueryable source, params Expression<Func<TDestination, object>>[] membersToExpand);

like this

Expression.Call(typeof(AutoMapper.QueryableExtensions.Extensions), "ProjectTo", new[] { DtoType },'What goes here as arguments???')

This is the overload that i want to call

public static MethodCallExpression Call(Type type, string methodName, Type[] typeArguments, params Expression[] arguments);

Based on this link: https://github.com/AutoMapper/AutoMapper/issues/2234#event-1175181678

Update

When i have a parameter like this

IEnumerable<Expression> membersToExpand = new Expression<Func<TDto, object>>[] { };

and pass it to the function like this it works :

Expression.Call(typeof(Extensions), "ProjectTo", new[] { typeof(TDto) }, body, Expression.Constant(membersToExpand));

But when the membersToExpand parameter is null it throw the exception No generic method 'ProjectTo' on type 'AutoMapper.QueryableExtensions.Extensions' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic

So let me clear The Question:

The 'membersToExpand' have to be not null (OK i know that), when it's not null the 'Expression.Call' can find the suitable overload and it will success But the problem is when the 'membersToExpand' is null so i have to create the default 'membersToExpand' and i have to create it dynamically, i can't create it like this

Expression.Constant(null, typeof(Expression<Func<TDto, object>>[]))

How to create this dynamicaly:

typeof(Expression<Func<TDto, object>>[]

when i just know the 'typeof(TDto)' ??

To reproduce the problem Just copy and Paste and then Run:

   class Program
{
    static void Main(string[] args)
    {
        Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<UserGroupDto, UserGroup>();
            cfg.CreateMap<UserGroup, UserGroupDto>()
                .ForMember(dest => dest.UserGroupDetails, conf =>
                {
                    conf.MapFrom(ent => ent.UserGroupDetails);
                    conf.ExplicitExpansion();
                });

            cfg.CreateMap<UserGroupDetailDto, UserGroupDetail>();

            cfg.CreateMap<UserGroupDetail, UserGroupDetailDto>()
                .ForMember(dto => dto.UserGroupName, conf => conf.MapFrom(ent => ent.UserGroup.Title));
        });

        //  Someone Wants To Call WITH Members To Expand
        Start(new Expression<Func<UserGroupDto, object>>[] { e => e.UserGroupDetails }); 

        Console.WriteLine("===============================================");

        //  Someone Wants To Call WITHOUT Members To Expand (It's a pain to pass an empty 'new Expression<Func<UserGroupDto, object>>[] { }' so it's better to create the default, dynamically when it's null )
        Start(new Expression<Func<UserGroupDto, object>>[] { });

        //  Someone Wants To Call WITHOUT Members To Expand and pass it null and it will THROW Exception
        // Start(null); 

        Console.ReadLine();
    }

    static void Start(IEnumerable<Expression> membersToExpand)
    {
        using (var db = new MyDbContext())
        {
            IEnumerable projected = CallProjectToDynamically(db.UserGroups.AsQueryable(), typeof(UserGroupDto), membersToExpand);

            foreach (var item in projected.OfType<UserGroupDto>())
            {
                Console.WriteLine(" UserGroupID =>   " + item.ID);
                Console.WriteLine(" UserGroupTitle =>   " + item.Title);

                if (item.UserGroupDetails != null)
                    item.UserGroupDetails.ToList().ForEach(d =>
                    {
                        Console.WriteLine(" UserGroupDetailID =>   " + d.ID);
                        Console.WriteLine(" UserGroupDetailTitle =>   " + d.Title);
                        Console.WriteLine(" UserGroupDetailUserGroupName =>   " + d.UserGroupName);
                    });

            }

        }
    }

    static IQueryable CallProjectToDynamically<T>(IQueryable<T> source, Type dtoType, IEnumerable<Expression> membersToExpand)
    {
        // What i want is this:
        // if(membersToExpand == null)
        // Create The default dynamically For 'params Expression<Func<TDestination, object>>[] membersToExpand'


        var param = Expression.Parameter(typeof(IQueryable<T>), "data");

        var body = Expression.Call(typeof(Extensions), "ProjectTo", new[] { dtoType }, param, Expression.Constant(membersToExpand));

        return (Expression.Lambda<Func<IQueryable<T>, IQueryable>>(body, param).Compile())(source);
    }
}
public partial class MyDbContext : DbContext
{
    public MyDbContext()
        : base("name=MyDbContext")
    {
        Database.SetInitializer(new MyDbContextDBInitializer());
    }

    public virtual DbSet<UserGroup> UserGroups { get; set; }
    public virtual DbSet<UserGroupDetail> UserGroupMembers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserGroup>()
            .HasMany(e => e.UserGroupDetails)
            .WithRequired(e => e.UserGroup)
            .HasForeignKey(e => e.UserGroupID);

    }


}
public class MyDbContextDBInitializer : DropCreateDatabaseAlways<MyDbContext>
{
    protected override void Seed(MyDbContext context)
    {
        IList<UserGroup> defaultStandards = new List<UserGroup>
        {
            new UserGroup() {  Title = "First UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for First UserGroup" } } },
            new UserGroup() {  Title = "Second UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for Second UserGroup" } } },
            new UserGroup() {  Title = "Third UserGroup", UserGroupDetails = new [] { new UserGroupDetail { Title = "UGD for Third UserGroup" } } }
        };

        foreach (UserGroup std in defaultStandards)
            context.UserGroups.Add(std);

        base.Seed(context);
    }
}
public partial class UserGroup
{
    public UserGroup()
    {
        UserGroupDetails = new HashSet<UserGroupDetail>();
    }

    public long ID { get; set; }
    public string Title { get; set; }

    public virtual ICollection<UserGroupDetail> UserGroupDetails { get; set; }

}
public partial class UserGroupDetail
{
    public long ID { get; set; }
    public long UserGroupID { get; set; }
    public string Title { get; set; }

    public virtual UserGroup UserGroup { get; set; }

}
public partial class UserGroupDto
{
    public long ID { get; set; }
    public string Title { get; set; }

    public IEnumerable<UserGroupDetailDto> UserGroupDetails { get; set; }

}
public partial class UserGroupDetailDto
{
    public long ID { get; set; }
    public string Title { get; set; }
    public long UserGroupID { get; set; }
    public string UserGroupName { get; set; }

}

Sincerely.

2

There are 2 best solutions below

1
On
var call = Expression.Call(typeof(AutoMapper.QueryableExtensions.Extensions), "ProjectTo", new[] { typeof(Bar) },
                            Expression.Constant(source), Expression.Constant(null, typeof(Expression<Func<Bar, object>>[])));

But I think you're wasting your time. In the latest version, you're not even allowed to pass null.

0
On

I solve this problem by wrapping code into generic metod. When I use ProjectTo<T> I don't need to pass second argument. Here is my helper, it takes the type and list of fields and returns DataTable. DB2 is DbContext.

public static class DTODataHelper
{
    public static DataTable GetDataOfType(DB2 db, string[] fields, Type type)
    {
        var method = typeof(DTODataHelper).GetMethod("GetData").MakeGenericMethod(type);

        var result = method.Invoke(null, new object[] { db, fields }) as DataTable;

        return result;
    }

    public static DataTable GetData<T>(DB2 db, string[] fields)
    {
        var dtoType = typeof(T);

        var source = Mapper.Configuration.GetAllTypeMaps().Where(i => i.DestinationType == dtoType).Select(i => i.SourceType).FirstOrDefault();

        if (source == null)
            throw new HMException("Не найден источник данных");

        var dbSet = db.Set(source);

        var querable = dbSet.AsQueryable();

        var list = dbSet.ProjectTo<T>().ToList();

        return GetDataTable(list, fields);
    }

    public static DataTable GetDataTable<T>(IEnumerable<T> varlist, string[] fields)
    {
        DataTable dtReturn = new DataTable();
        PropertyInfo[] oProps = null;

        var hasColumnFilter = fields != null && fields.Length > 0;

        if (varlist == null) return dtReturn;

        foreach (T rec in varlist)
        {
            if (oProps == null)
            {
                oProps = rec.GetType().GetProperties();

                var excludedProperties = new List<PropertyInfo>();

                foreach (PropertyInfo pi in oProps)
                {
                    if (hasColumnFilter && !fields.Contains(pi.Name))
                    {
                        excludedProperties.Add(pi);
                        continue;
                    }

                    Type colType = pi.PropertyType;

                    if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        colType = colType.GetGenericArguments()[0];
                    }

                    var piName = pi.GetCustomAttributes().OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? pi.Name;

                    dtReturn.Columns.Add(new DataColumn(piName, colType));
                }

                if (excludedProperties.Count > 0)
                {
                    oProps = oProps.Except(excludedProperties).ToArray();
                }
            }

            DataRow dr = dtReturn.NewRow();
            foreach (PropertyInfo pi in oProps)
            {
                var piName = pi.GetCustomAttributes().OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? pi.Name;

                dr[piName] = pi.GetValue(rec, null) == null ? DBNull.Value : pi.GetValue(rec, null);
            }

            dtReturn.Rows.Add(dr);
        }
        return dtReturn;
    }
}