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

Asked

Viewed 104 times

3

Hi, I’m creating a library of DataAnnotations to extend Entityframeworkcore with structures that today only exist using Fluentapi. In one of them, I’m trying to recreate this block of 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 will create an abstract attribute that will be tracked by a Factory where, this Factory will be called inside the OnModelCreating, thus:

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 will create multiple attributes that make the implementations of Fluentapi directly into the model as well as the other Dataannotations that exist, so:

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 I need to do to be able 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 possibility 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; }
    }
}

1 answer

3


@Leandroluk, I could not test here, more of a look if it helps you this method I made based on a response from Soen:

https://stackoverflow.com/questions/35342955/dynamically-creating-an-expression-which-selects-an-objects-property

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);
}
  • This solved my problem, but ended up generating another in my way of working. My solution then was in every class that I had to create a special config, I was adding an extra class that extended to IEntityTypeConfiguration<>. Then on my Dbcontext I did this builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());

Browser other questions tagged

You are not signed in. Login or sign up in order to post.