Как выполнить запрос EF Core с использованием выражения?

Как запросить базу данных с помощью EF Core с помощью Expression?

Я хотел бы построить динамический запрос в соответствии с метаданными и динамически запрашивать/редактировать данные, возможно, это может быть с помощью Expression. Как я мог это сделать?

Цель:

db.Blogs.AsNoTracking().FirstOrDefault(p => p.BlogId == 1);

Код?

var funcAsNoTracking = Expression.Call(dbSet.GetType().GetMethod("AsNoTracking")!);
var funcFirstOrDefault = Expression.Call(typeof(IQueryable<>).GetMethod("FirstOrDefault")!);
/* what to do next or else? */
//Code

Другой код:

public class BloggingContext : DbContext
{
     public DbSet<Blog> Blogs { get; set; }
     public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public List<Post> Posts { get; } = new();
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
77
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Судя по тому, что вы сказали, я думаю, что вы, возможно, слишком много думаете о проблеме.

Если это так просто, как вы сказали, вы могли бы просто сделать что-то вроде.

Expression<Func<Blog, bool>> selectorExpression = (p) => p.BlogId == 1;
var blogs = db.Blogs;
Blog? result = default;

if (useAsNoTracking) // Introduce some logic that sets this.
    blogs = blogs.AsNoTracking();

if (useFirstOrDefault) // Introduce some logic that sets this too.
    result = blogs.FirstOrDefault(selectorExpression);

Однако все становится немного сложнее, если вы пытаетесь динамически создавать Expression, и я ответил на аналогичный вопрос здесь C# использует строковый параметр, чтобы определить, по какому свойству фильтровать в списке объектов

Ответ принят как подходящий

Спасибо за ваш ответ @phuzi

После приятного отдыха на выходных я могу сосредоточиться на реализации и разобраться в ней сам, поделиться ею в надежде помочь другим.

Вывод: ядро ​​EF могло бы это сделать, но я бы хотел попробовать другой способ, например LINQ to SQL, для создания динамического решения.

Обратите внимание: это всего лишь демонстрационный код, показывающий только цель, полный код большой.

Часть А: Метаданные

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public Dictionary<string, object> AllDbSets
    {
        get
        {
            //TODO: setup with dynamic registration and cache
            var dic = new Dictionary<string, object>() { { nameof(Blogs), Blogs }, { nameof(Posts), Posts } };
            return dic;
        }
    }
}

Недавно добавленные AllDbSets

Часть B: Модель динамического поиска

internal class Query
{
    public Query(List<string> fileds, List<IFilter> filters)
    {
        this.Fileds = fileds;
        this.Filters = filters;
    }

    public List<string> Fileds { get; set; }
    public List<IFilter> Filters { get; set; }
}

internal interface IFilter
{

}

internal class EqualFilter : IFilter
{
    public EqualFilter(string field, object value)
    {
        this.Filed = field;
        this.Value = value;
    }

    public string Filed { get; set; }
    public object Value { get; set; }
}

Примечание. Только для этой демонстрации.

Часть C. Процесс Query Engine с демонстрацией

static void Main(string[] args)
{
    var query = new Query(new List<string> { "Blogs.BlogId", "Blogs.Url" }, new List<IFilter>() { new EqualFilter("Blogs.BlogId", 1) });

    var queryDBSetName = query.Fileds.Select(p => p.Split(".")[0]).Distinct().First();


    using BloggingContext db = new();
    {
        //Get DB set and basic metadata from ef core
        var set = db.AllDbSets[queryDBSetName];
        var setType = set.GetType();
        var entityType = setType.GenericTypeArguments.First();

        //AsNoTracking methodinfo
        var methodInfoAsNoTracking = typeof(EntityFrameworkQueryableExtensions).GetTypeInfo().GetDeclaredMethod(nameof(EntityFrameworkQueryableExtensions.AsNoTracking))!.MakeGenericMethod(entityType);
        //Build AsNoTracking expression
        var sourceParam = Expression.Parameter(typeof(IQueryable<>).MakeGenericType(entityType), "source");
        var asNoTrackingExpression = Expression.Call(methodInfoAsNoTracking, sourceParam);

        //FirstOrDefault methodinfo
        var methodInfoFirstOrDefault = typeof(Queryable).GetTypeInfo().DeclaredMethods.First(p => p.Name == nameof(Queryable.FirstOrDefault) && p.GetParameters().Length == 2 && p.GetParameters()[1].Name == "predicate")!.MakeGenericMethod(entityType);

        //Build property predicate expression
        var propertyInfo = entityType.GetProperty("BlogId")!;
        var param = Expression.Parameter(entityType, "p");
        var left = Expression.Property(param, propertyInfo);
        var right = Expression.Constant(1, typeof(int));

        var predicateExpression = Expression.Equal(left, right);

        //Make lambda filter expression
        Expression filterExpression = Expression.Lambda(predicateExpression, param);

        //FirstOrDefault expression with filter
        var callFirstOrDefault = Expression.Call(methodInfoFirstOrDefault, Expression.Lambda(asNoTrackingExpression, sourceParam).Body, filterExpression);

        //Make the call of expression
        var result = Expression.Lambda(callFirstOrDefault, sourceParam).Compile().DynamicInvoke(set);
    }
}

Фильтр из запроса игнорируется, но для демо-версии это не имеет значения.

Опять же, это всего лишь тестовая демо-версия, над которой нужно много работать.

Отдайте кесарю то, что есть кесарю. ИМХО, не пытайтесь использовать отражение со всеми этими дженериками, вместо этого напишите общий метод и используйте отражение только для его вызова. Таким образом, вы можете работать с db.Set<T>(), IQueryable<T>, .AsNoTracking(), .FirstOrDefault() и т. д. напрямую и сосредоточиться только на построении дерева Expression.

Jeremy Lakeman 01.07.2024 04:55

И даже тогда вы, вероятно, сможете решить другие проблемы, не строя дерево Expression вручную. Просто примените это interface IKey { int Id { get; set; }}, class Blog : IKey, и вы сможете db.Set<T>().AsNoTracking().FirstOrDefault(p => p.Id == 1); для любого стола.

Jeremy Lakeman 01.07.2024 05:02

@JeremyLakeman, насколько я понимаю, общий тип T не определен до отправки «Запроса», поскольку запрашивающая сторона попытается запросить любой объект и включить динамическую подпоследовательность. Мне не хотелось бы использовать жесткий код для переключения регистра всех типов сущностей, поскольку пример кода — лишь небольшая часть решения. система управляется метаданными и ничего не знает о фиксированных типах.

Lei Chi 01.07.2024 05:02

Если вам действительно нужно построить деревья выражений, сделайте это. Но ИМХО вам следует сначала попытаться избежать этого.

Jeremy Lakeman 01.07.2024 05:04

Мы поддерживаем .Where(), OrderBy() и т. д., но не можем настраивать статические решения.

Lei Chi 01.07.2024 05:05

@JeremyLakeman, недоразумение является целью системы, это система с низким кодом, управляемая метаданными, а не простое приложение, мы не могли избежать динамичных и сложных вещей.

Lei Chi 01.07.2024 05:07

Давайте продолжим обсуждение в чате.

Lei Chi 01.07.2024 05:14

Если вы можете избежать использования отражения и построения деревьев выражений вручную, вам следует это сделать. Я не говорю, что таким образом можно решить все проблемы, но большинство можно. Из вашего примера;

public interface IKey {
    int Id { get; set; }
}

public class Blog : IKey
{
    public int Id { get; set; }
    // etc
}

public class Post
{
    public int Id { get; set; }
    // etc
}

public T GetItem<T>(DbContext db, int id) where T : class, IKey
    => db.Set<T>().AsNoTracking().FirstOrDefault(p => p.Id == 1);

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

большое спасибо за вашу демонстрацию, очень рад, что мы смогли подробно поговорить об этом разделе. В вашем сегменте кода я сталкиваюсь с реальной ситуацией: A: «Блог»/«Сообщение», определенное системным администратором, у нас не может быть общего T в коде, все, что мы знаем, это то, что у нас есть сущность и имя. B: IKey является статическим, системный администратор хотел бы иметь ключ GUID или несколько ПК. Что мы знаем, так это описание метаданных, в коде не может быть сущности/типа.

Lei Chi 01.07.2024 05:24

Я хотел бы использовать динамический тип объекта, ключ/значения или динамический тип времени выполнения, но это совсем другое.

Lei Chi 01.07.2024 05:27

Я хотел бы изучить «Dynamic Linq», чтобы посмотреть, что мы можем получить.

Lei Chi 01.07.2024 05:30

Для всех ключей управления вы должны иметь возможность повторно использовать интерфейс, подобный приведенному выше, даже если вам это необходимо .Property(p => p.Id).HasColumnName("BlogId"). Вызов GetItem<T> через .MakeGenericMethod(type).Invoke по-прежнему намного проще, чем выполнение каждого шага этой функции посредством отражения. По крайней мере для всех тех же типов ключей. Но если абсолютно все динамично, то Entity Framework, вероятно, не тот инструмент. Если все управляется метаданными, вы можете просто использовать SqlCommand/SqlDataReader напрямую.

Jeremy Lakeman 01.07.2024 06:40

да, я доказываю, что использование EF Core — это полная ошибка. у нас уже есть библиотека AST (абстрактное синтаксическое дерево) для SQL, метаданные-> AST-> SQL, выполняемая SqlCommand/SqlDataReader и работающая с DCL, DDL, DML и DQL. Конечно, это слишком далеко за пределами возможностей. еще раз спасибо за разговор, закрываю этот вопрос.

Lei Chi 01.07.2024 07:26

Другие вопросы по теме