передача типа сущности в качестве параметра в linq - PullRequest
0 голосов
/ 20 октября 2018

Как мне передать тип сущности в качестве параметра в linq?

Например, метод получит значение имени сущности в виде строки, и я хотел бы передать имя сущности в приведенную ниже строку.запрос.Можно ли сделать запрос linq универсальным?

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

Я хотел бы передать тип сущности в качестве параметра и вернуть все значения свойств.

Также возможно ли фильтровать результатына основании какого-то свойства?

Ответы [ 4 ]

0 голосов
/ 25 октября 2018

Вы можете достичь того, что хотите, даже если у контекста нет свойств DbSet (и если он есть, это не вредит).Это вызов метода DbContext.Set<TEntity>() с помощью отражения:

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 });

Теперь у вас есть список объектов.

Одно замечание: избавиться от некоторого влияния на производительность из-за отраженияВы можете кешировать некоторые типы и неуниверсальную информацию о методах.

Еще одно замечание: я не думаю, что рекомендую это.Как сказано в комментарии: это вызывает пару опасений.Например: собираетесь ли вы позволить клиентскому приложению получать все нефильтрованные данные любой таблицы сущностей?Что бы ты ни делал, будь осторожен.

0 голосов
/ 22 октября 2018

Предполагая, что ваш context класс выглядит следующим образом:

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

    // and so on ...
}

самое простое решение - написать метод, который выглядит как

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(); 
}

Но мы не хотим жестко кодировать этовещи, поэтому давайте создадим Selector динамически с Linq.Expressions

Определите поле Func в вашем контроллере:

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

Теперь вы можете создать фабрику для этого члена:

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();
}

и присвойте ему Func (где-то в конструкторе)

selector = SelectByType();

Теперь вы можете использовать его как

public ActionResult EntityRecords(string entityTypeName)
{
    var entityResults = selector(entityTypeName);
    return View(entityResults);
}
0 голосов
/ 22 октября 2018

У вас есть два варианта:

Вариант 1: вы знаете тип сущности во время компиляции

Если вы знаете тип сущности во время компиляции, используйте универсальныйМетод:

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

Использование:

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

Вариант 2: Вы знаете тип сущности только во время выполнения

Если вы действительно хотитепередайте тип объекта в виде строки, используйте другую перегрузку Set, которая принимает тип:

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

Это предполагает, что entityType - это полное имя типа, включая сборку.См. этот ответ для получения подробной информации.
Если все сущности находятся в одной сборке с контекстом - или в другой известной сборке - вы можете использовать этот код вместо этого, чтобы получить тип сущности:

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

Это позволяет вам пропустить сборку в строке, но для этого все еще требуется пространство имен.

0 голосов
/ 22 октября 2018

В вашем примере похоже, что у вас есть действие контроллера, которое принимает имя объекта в качестве параметра, поэтому вы не сможете сделать ваш метод универсальным.Но вы можете использовать рефлексию и по большей части избегать использования обобщений.

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);
}

Однако следует помнить несколько вещей:

  1. Предполагается, чтоу вас есть свойство в вашем контексте, соответствующее данному аргументу entityTypeName.Если entityTypeName на самом деле является именем типа, а не именем свойства, вам нужно проделать дополнительную работу, чтобы найти соответствующее свойство.
  2. Ваше представление должно знать, что делать с коллекцией объектов, гдетип объектов неизвестен во время компиляции.Вероятно, придется использовать рефлексию, чтобы делать то, что вы намерены делать.
  3. В таком методе могут быть некоторые проблемы с безопасностью.Например, если пользователь предоставляет «База данных» или «Конфигурация», вы можете в конечном итоге предоставить такую ​​информацию, как строка подключения, которая не имеет ничего общего с фактическими сущностями, которые вы сохранили.

Кроме того, возможно ли отфильтровать результаты на основе некоторого свойства?

Да, и это будет включать аналогичное использование отражения и / или dynamic.Вы можете использовать библиотеку типа Dynamic LINQ для передачи строк в перегруженные методы, похожие на LINQ (Where, Select и т. Д.).

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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...