passando tipo de entidade como parâmetro no linq

votos
9

Como eu iria sobre passando um tipo de entidade como um parâmetro em LINQ?

Por exemplo, o método vai receber o valor nome da entidade como uma string e eu gostaria de passar o nome da entidade para a consulta linq abaixo. É possível fazer a consulta linq genérico?

public ActionResult EntityRecords(string entityTypeName)
{
    var entityResults = context.<EntityType>.Tolist();
    return View(entityResults);
}

Eu gostaria de passar o tipo de entidade como um parâmetro e devolver todos os valores de propriedade.

Além disso, é possível filtrar os resultados com base a alguma propriedade?

Publicado 20/10/2018 em 13:47
fonte usuário
Em outras línguas...                            


4 respostas

votos
4

Supondo que sua contextclasse é parecido com isto:

public class MyContext : DbContext
{
    public DbSet<Entity1> Entity1 { get; set; }
    public DbSet<Entity2> Entity2 { get; set; }

    // and so on ...
}

solução mais simples é escrever método que parece

private List<object> Selector(string entityTypeName)
{
  if (entityTypeName == "Entity1") 
    return context.Entity1.ToList();

  if (entityTypeName == "Entity2")
    return context.Entity2.ToList()

  // and so on  

  // you may add a custom message here, like "Unknown type"
  throw new Exception(); 
}

Mas nós não queremos para codificar essas coisas, então vamos criar Selectordinamicamente comLinq.Expressions

Definir um Funccampo dentro de seu controlador:

private readonly Func<string, List<object>> selector;

Agora você pode criar uma fábrica para este membro:

private Func<string, List<object>> SelectByType()
{
    var myContext = Expression.Constant(context);
    var entityTypeName = Expression.Parameter(typeof(string), "entityTypeName");

    var label = Expression.Label(typeof(List<object>));
    var body = Expression.Block(typeof(MyContext).GetProperties()
        .Where(p => typeof(IQueryable).IsAssignableFrom(p.PropertyType) && p.PropertyType.IsGenericType)
        .ToDictionary(
            k => Expression.Constant(k.PropertyType.GetGenericArguments().First().Name),
            v => Expression.Call(typeof(Enumerable), "ToList", new[] {typeof(object)}, Expression.Property(myContext, v.Name))
        )
        .Select(kv =>
            Expression.IfThen(Expression.Equal(kv.Key, entityTypeName),
              Expression.Return(label, kv.Value))
        )
        .Concat(new Expression[]
        {
            Expression.Throw(Expression.New(typeof(Exception))),
            Expression.Label(label, Expression.Constant(null, typeof(List<object>))),
        })
    );

    var lambda = Expression.Lambda<Func<string, List<object>>>(body, entityTypeName);
    return lambda.Compile();
}

e atribuir Funca ele (em algum lugar no construtor)

selector = SelectByType();

Agora você pode usá-lo como

public ActionResult EntityRecords(string entityTypeName)
{
    var entityResults = selector(entityTypeName);
    return View(entityResults);
}
Respondeu 22/10/2018 em 20:10
fonte usuário

votos
3

Você tem duas opções:

Opção 1: Você sabe o tipo de entidade em tempo de compilação

Se você souber o tipo de entidade em tempo de compilação, use um método genérico:

public ActionResult EntityRecords<TEntity>()
{
    var entityResults = context.Set<TEntity>.ToList();
    return View(entityResults);
}

Uso:

public ActionResult UserRecords()
{
    return EntityRecords<User>();
}

Opção 2: Você sabe o tipo de entidade única em tempo de execução

Se você realmente quer passar o tipo de entidade como uma string, use a outra sobrecarga Setque leva um tipo:

public ActionResult EntityRecords(string entityType)
{
    var type = Type.GetType(entityType);
    var entityResults = context.Set(type).ToList();
    return View(entityResults);
}

Isto assume que entityTypeé um nome do tipo completo, incluindo a montagem. Veja esta resposta para mais detalhes.
Se as entidades são todos dentro do mesmo conjunto como o contexto - ou em outra montagem bem conhecido - você pode usar este código em vez de obter o tipo de entidade:

var type = context.GetType().Assembly.GetType(entityType);

Isso permite que você omitir a montagem na seqüência, mas ainda requer o namespace.

Respondeu 22/10/2018 em 20:17
fonte usuário

votos
1

Você pode conseguir o que você quer mesmo se o contexto não tem DbSetpropriedades (e se isso acontecer, que não prejudique). É chamando o DbContext.Set<TEntity>()método pela reflexão:

var nameSpace = "<the full namespace of your entity types here>";

// Get the entity type:
var entType = context.GetType().Assembly.GetType($"{nameSpace}.{entityTypeName}");

// Get the MethodInfo of DbContext.Set<TEntity>():
var setMethod = context.GetType().GetMethods().First(m => m.Name == "Set" && m.IsGenericMethod);
// Now we have DbContext.Set<>(), turn it into DbContext.Set<TEntity>()
var genset = setMethod.MakeGenericMethod(entType);

// Create the DbSet:
var dbSet = genset.Invoke(context, null);

// Call the generic static method Enumerable.ToList<TEntity>() on it:
var listMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(entType);
var entityList = listMethod.Invoke(null, new[] { dbSet });

Agora você tem a sua lista de entidades.

Uma observação: Para se livrar de algum impacto no desempenho devido à reflexão você pode armazenar em cache alguns tipos e informações sobre métodos não-genéricos.

Outra observação: Eu não acho que eu recomendaria este. Como disse em um comentário: isto levanta um par de preocupações. Por exemplo: você está indo para permitir que um aplicativo cliente para obter todos sem filtro de dados de qualquer tabela de entidade? Seja o que for que você está fazendo: manusear com cuidado.

Respondeu 24/10/2018 em 21:13
fonte usuário

votos
0

No seu exemplo, parece que você tem uma ação de controlador que está tomando o nome da entidade como um parâmetro, para que você não será capaz de fazer o seu método genérico. Mas você pode usar a reflexão e evitar o uso de genéricos para a maior parte.

public ActionResult EntityRecords(string entityTypeName)
{
    var entityProperty = context.GetType().GetProperty(entityTypeName);
    var entityQueryObject = (IQueryable)entityProperty.GetValue(context);
    var entityResults = entityQueryObject.Cast<object>().ToList();
    return View(entityResults);
}

Existem algumas coisas para manter em mente, porém:

  1. O pressuposto é que você tem uma propriedade em seu contexto correspondente ao dado entityTypeNameargumento. Se entityTypeNameé realmente o nome do tipo em vez do nome da propriedade, você vai precisar fazer um trabalho extra para encontrar a propriedade adequada.
  2. Seu Ver terá que saber o que fazer com uma coleção de objetos, onde o tipo dos objetos não é conhecido em tempo de compilação. Ele provavelmente vai ter que usar o reflexo para fazer o que você pretende para que ele faça.
  3. Pode haver algumas preocupações de segurança em um método como este. Por exemplo, se o usuário fornece "banco de dados" ou "Configuração", você pode acabar expondo informações como sua seqüência de conexão, que não tem nada a ver com as entidades reais que você armazenou.

Além disso, é possível filtrar os resultados com base a alguma propriedade?

Sim, e vai envolver um uso semelhante de reflexão e / ou dynamic. Você pode usar uma biblioteca como Dinâmico LINQ para passar strings em sobrecargas do método LINQ-like ( Where, Select, etc.).

public ActionResult EntityRecords(string entityTypeName, FilterOptions options)
{
    var entityProperty = context.GetType().GetProperty(entityTypeName);
    var entityQueryObject = entityProperty.GetValue(context);
    var entityResults = ApplyFiltersAndSuch((IQueryable)entityQueryObject);
    return View(entityResults);
}

private IEnumerable<object> ApplyFiltersAndSuch(IQueryable query, FilterOptions options)
{
    var dynamicFilterString = BuildDynamicFilterString(options);
    return query.Where(dynamicFilterString)
        // you can add .OrderBy... etc.
        .Cast<object>()
        .ToList();
}
Respondeu 22/10/2018 em 15:02
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more