mardi 25 février 2020

How can i pass a lambda expression to the method which is called using reflection

I'm working on just another version of Onion Architecture using EF in core environment. I have some mapping that i want it to be applied on almost every table i have, and since the mapping is somehow logical in my scenario, i want to close the gap in my and my co-workers mind, reducing further data corruption.

So i wen't through Reflection step by step, trying to achieve the following scenario:

for (all registered model in context) {
  if(model has IXyz interface) {
    * builder apply mapping on entity
  }
}

The star sign ():* The mapping i want to configure at * point is as following:

builder.Entity<Sample>().HasQueryFilter(m => EF.Property<bool>(m, nameof(IDeletableEntity.IsDeleted)) == false);

I tried many things, but none worked; I jumped from error to error, at both run time & compile time:

1.

System.ArgumentException: 'Static method requires null instance, non-static method requires non-null instance.'

2.

System.ArgumentException: 'Method 'Boolean b__6_0(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder1[Template.Domain.Models.Sample])' declared on type 'Template.Data.Configurations.ApplicationDbContext+<>c__61[Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1[Template.Domain.Models.Sample]]' cannot be called with instance of type 'Template.Data.Configurations.ApplicationDbContext''

3.

Error CS1660 Cannot convert lambda expression to type 'Expression' because it is not a delegate

The latest state of my code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Template.Domain.Interfaces;
using Template.Domain.Models;

namespace Template.Data.Configurations
{
    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Sample> Samples { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            Debugger.Launch();

            var modelBuilderMethods = builder.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance);
            MethodInfo desiredModelBuilderMethod = modelBuilderMethods.FirstOrDefault(modelBuilderMethod => modelBuilderMethod.Name == nameof(builder.Entity) && modelBuilderMethod.IsGenericMethod && modelBuilderMethod.IsGenericMethodDefinition && modelBuilderMethod.GetParameters().Length == 0); //Entity<T>() Method

            //builder.Entity<Sample>().HasQueryFilter(m => EF.Property<bool>(m, nameof(IDeletableEntity.IsDeleted)) == false);
            foreach (var entityType in builder.Model.GetEntityTypes())
            {
                if (entityType.ClrType.GetInterfaces().Any(w => w == typeof(IDeletableEntity)))
                {
                    var entityGenericMethod = desiredModelBuilderMethod.MakeGenericMethod(entityType.ClrType);
                    var entity = entityGenericMethod.Invoke(builder, new object[0]);
                    var entityTypeBuilder = entity.GetType();
                    var entityTypeBuilderMethods = entityTypeBuilder.GetMethods(BindingFlags.Public | BindingFlags.Instance);
                    MethodInfo desiredEntityTypeBuilder = entityTypeBuilderMethods.FirstOrDefault(entityTypeBuilderMethod => entityTypeBuilderMethod.Name == nameof(EntityTypeBuilder.HasQueryFilter) && !entityTypeBuilderMethod.IsGenericMethod && !entityTypeBuilderMethod.IsGenericMethodDefinition); //HasQueryFilter(Expression<Func<T, bool>> filter) Method


                    var queryFilterMethod = this.GetType()
                        .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                        .FirstOrDefault(w => w.Name == nameof(SetQueryFilters)
                                             && w.IsGenericMethod
                                             && w.IsGenericMethodDefinition
                                             && w.GetParameters().Length == 0)
                        ?.MakeGenericMethod(entity.GetType());

                    var lambdaExpression = queryFilterMethod.Invoke(this, new object[0]);
                    desiredEntityTypeBuilder.Invoke(entity, new object[]
                    {
                        lambdaExpression
                    });

                }
            }
        }

        private Expression<Func<T, bool>> SetQueryFilters<T>()
        {
            var func = new Func<T, bool>(m=> EF.Property<bool>(m, nameof(IDeletableEntity.IsDeleted)) == false);
            var exp = Expression.Default(typeof(ApplicationDbContext)); //Expression.Constant(this);
            var methodCallExpression = Expression.Call(exp, func.Method);

            var expression = Expression.Lambda<Func<T, bool>>(methodCallExpression);
            return expression;
        }

    }
}

namespace Template.Domain.Interfaces
{
    public interface IDeletableEntity
    {
        bool IsDeleted { get; set; }
    }
}


using System.ComponentModel.DataAnnotations.Schema;
using Template.Domain.Interfaces;

namespace Template.Domain.Models
{
    public class Sample : IDeletableEntity
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public bool IsDeleted { get; set; }
    }
}

Note to Debug this code you need to call "Update-Database" in "Package Manager Console".

Thank you, Hassan.





Aucun commentaire:

Enregistrer un commentaire