Более простой способ запуска запросов ядра Entity Framework в LINQPad со встроенным драйвером Linq to SQL?

#c# #entity-framework-core #linqpad

#c# #entity-framework-core #linqpad

Вопрос:

Примечание: Это старый вопрос. С LINQPad 5 у вас были ограничения, которые я описал ниже, потому что он не написан на .NET Core. Но теперь вы можете использовать более новый LINQPad 6 или 7, который полностью поддерживает Entity Framework Core, включая драйверы для нескольких систем баз данных.


Я хочу проанализировать основные запросы Entity Framework, которые я скопировал из существующего кода в LINQPad, например

 UserQuery this_context;
void Main()
{
    this_context = this;
    var validationResult = (

        from ProjectInfo pi in this_context.GetAll<ProjectInfo>()
        join GeneralInformation gi in this_context.GetAll<GeneralInformation>() 
                                   on pi.Id equals gi.Id into gpi
        from gps in gpi.DefaultIfEmpty()

        select new { ... }).ToList();
}
 

Я знаю, что Linqpad не поддерживает .NET Core, поэтому мне нужно немного его настроить — для этой цели я создал метод расширения следующим образом:

 public static class Ext
{
    public static  IEnumerable GetAll<T>(this UserQuery uq)
    where T: new()
    {
        return GetAll(uq, new T());
    }

    private static  IEnumerable GetAll<T>(this UserQuery uq, T a)
    {
        switch (a)
        {
            case ProjectInfo v:
                return uq.ProjectInfo.Select(s => s);
            case GeneralInformation v:
                return uq.GeneralInformation.Select(s => s);

            // ... many more cases like the above 

            default: // unknown type
                throw new TypeAccessException($"Unsupported type {a.GetType().FullName}");
        }
    }
    
}
 

Для GetAll получения типа требуется перегруженная функция — она просто создает пустой новый объект и передает его, чтобы оператор switch мог правильно вычесть правильный тип и вернуть правильный запрос.

Это работает нормально, но требуется много усилий, чтобы вставить каждый отдельный тип в оператор switch.

Итак, мой вопрос:

Как этого можно достичь более разумным способом, т. Е. Без необходимости указывать каждый отдельный тип объекта?

Ответ №1:

UserQuery Объект LINQPad, похоже, имеет метод, который именно то, что вы хотите:

 public static class Ext {
    public static IEnumerable GetAll<T>(this UserQuery uq) =>uq.GetTable(typeof(T));
}
 

Я полагаю, вы могли бы оставаться в безопасности с помощью:

 public static IQueryable<T> GetAll<T>(this UserQuery uq) => (IQueryable<T>)uq.GetTable(typeof(T));
 

Использование вашей оригинальной IEnumerable версии приводит к тому, что все таблицы загружаются в память одна за другой, а затем LINQ выполняется для объектов. Использование IQueryable<T> переводит запрос в SQL — это может быть нежелательно в тех случаях, когда LINQ to SQL и EF отличаются по переводу (без уважительной причины), но я мог видеть проблемы со специфическими возможностями EF (например Include , и DbFunctions , хотя я написал пустые Include методы расширения, чтобы скрыть это некоторые).

Если IQueryable версия не работает, ITable<> версия может:

 public static ITable<T> GetAll<T>(this UserQuery uq) where T : class => (ITable<T>)uq.GetTable(typeof(T));
 

Комментарии:

1. Работает нормально, это было именно то, что я искал. Большое спасибо! Однако типобезопасная версия выдает исключение InvalidOperationException по неизвестной причине.

2. Возможно .AsQueryable() , после GetTable ? Я не получаю сообщение об ошибке, когда тестирую его, используя подключение к MS SQL. Я добавил еще одну версию typesafe, которая может работать лучше.

3. InvalidOperationException: Could not translate expression 'Table(ProjectInfo).Cast().GroupJoin(Table(GeneralInformation).Cast(), pi => pi.Id ... В моем случае используются обе типизированные версии, в то время как нетипизированная версия (1-я в вашем ответе) работает нормально

4. Я наконец нашел способ заставить его работать с типизированным результатом: public static IEnumerable<T> GetAll<T>(this UserQuery uq) => (IEnumerable<T>)uq.GetTable(typeof(T)); — это работает нормально, поэтому причина заключалась в том, что классы интерфейса IQueryable<T> и ITable<T> не на 100% совместимы с Linq.

5. @Matt Я не думаю, что это правильно — IQueryable<T> это основа LINQ (для SQL / EF). Если вы приведете к IEnumerable<T> I, я был бы обеспокоен тем, что вы загружаете всю таблицу в память и выполняете не SQL-запрос, а LINQ к объектам, которые могут быть немного разными.