How to create an object in a Linq.Expressions.Expression and dynamically add properties to it in C #

3

Hello, I am creating a DataAnnotations library to extend the EntityFrameworkCore with structures that today only exist using FluentAPI. In one of them, I'm trying to recreate this block from FluentAPI:

using Microsoft.EntityFrameworkCore;
namespace Test 
{
    public class A
    {
        public int B{ get; set; }
        public int C{ get; set; }
    }
    public class TestContext: DbContext
    {
        DbSet<A> A { get; set; }
        protected override OnModelCreating(ModelBuilder mb)
        {
            mb.Entity<A>().HasIndex(x => new { x.B, x.C}).IsUnique();
            // ... muitas outras implementações usando o model builder aqui =[
        }
    }
}

To do this, I'll create an abstract attribute that will be tracked by a Factory where, this factory will be called within OnModelCreating , like this:

namespace Test 
{
    public SpAttributeFactory 
    {
        private DbContext context;
        private ModelBuilder builder;

        public SpAttributeFactory(DbContext context, ModelBuilder builder)
        {
            this.context = context;
            this.builder = builder;
        }
        public void Run() { /* ainda vou implementar e não tem relação à questão */ }
    }
    public class TestContext: DbContext
    {
        // ... nova implementação com a factory
        protected override OnModelCreating(ModelBuilder builder)
        {
            new SpAttributeFactory(this, builder).Run();
        }
    }
}

And from this attribute, I'm going to create multiple attributes that make FluentAPI implementations directly in the model as well as the other DataAnnotations that exist, like this:

namespace Test 
{
    public abstract class SpAttribute : Attribute
    {
        protected SpAttribute(Type baseType) => BaseType = baseType;
        protected Type BaseType { get; set; }
        protected abstract void Run(params object[] args);
    }
    [AttributeUsage(AttributeTargets.Class)]
    public abstract class SpTableAttribute : SpAttribute
    {
        protected SpTableAttribute(Type baseType) : base(baseType) { }
    }
    public class SpIndexAttribute : SpTableAttribute
    {
        public SpIndexAttribute(Type baseType, params string[] columns) : base(baseType)
        {
            Columns = columns;
        }

        public bool Unique { get; set; }
        public IEnumerable<string> Columns { get; set; }

        protected override void Run(params object[] args)
        {
            try
            {
                ModelBuilder builder = null;
                DbContext context = null;
                IEnumerable<PropertyInfo> props = null;

                // get dependencies from args

                foreach (var arg in args)
                    if (arg is ModelBuilder)
                        builder = (ModelBuilder)arg;
                    else if (arg is DbContext)
                        context = (DbContext)arg;

                // try get entity or catch error
                var entity = builder.Entity(BaseType);

                // get properties by sended name in attribute

                props = BaseType.GetProperties()
                    .Where(x => Columns.Contains(x.Name));

                // checked if number of props is same of number of colum sended
                if (props.Count() != Columns.Count())
                    throw new ArgumentException(
                        "As colunas informadas não coincidem com as propriedades públicas da classe"
                    );

                // building lambda linq method

                var lParameter = Expression.Parameter(typeof(object), "x");

                /*
                    Aqui está o problema... preciso instanciar no 'body' da
                    função linq um "new {}" e, para cada PropertyInfo 
                    encontrada, eu devo criar uma propriedade no objeto anônimo
                    e atribuir essa propriedade. Ex:

                    .HasIndex(x=> new { x.B, x.CB })
                */

                var indexBuilder = entity.HasIndex();

                if (Unique) indexBuilder.IsUnique();

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
        }
}

What do I need to do to create a Linq.Expression that returns an object containing a series of properties that will be passed to the attribute?

Extra

The result of this implementation will be the ability to do this:

namespace Test 
{
    [SpIndex(typeof(A), "B", "C", Unique = true]
    public class A 
    {
        public int B { get; set; }
        public int C { get; set; }
    }
}
    
asked by anonymous 11.12.2018 / 17:37

1 answer

3

@LeandroLuk, I could not test here, more of a look if this method helps you that I did based on a response from SOEn:

link

public LambdaExpression ToDynamicLambda()
{
    var itemParam = Expression.Parameter(BaseType, "x");
    var members = Columns.Select(f => Expression.PropertyOrField(itemParam, f));
    var addMethod = typeof(IDictionary<string, object>).GetMethod(
                "Add", new Type[] { typeof(string), typeof(object) });


    var elementInits = members.Select(m => Expression.ElementInit(addMethod, Expression.Constant(m.Member.Name), Expression.Convert(m, typeof(Object))));

    var expando = Expression.New(BaseType);
    return Expression.Lambda(Expression.ListInit(expando, elementInits), itemParam);
}
    
11.12.2018 / 18:52