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; }
}
}
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 thisbuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
– LeandroLuk