Linq to Entities Group By (OUTER APPLY) "oracle 11.2.0.3.0 не поддерживает применение"

У меня есть пример кода ниже, который запрашивает список продуктов.

 var productResults = Products.Where((p) => refFilterSequence.Contains(p.Ref))
                .GroupBy(g => g.Code, (key, g) => g.OrderBy(whp => whp.Ref).First()).ToList();


Это работает точно так, как ожидалось, и возвращает 4 строки, которые я хочу использовать при сборке памяти, но при работе с базой данных Oracle:

.GroupBy(g => g.Code, (key, g) => g.OrderBy(whp => whp.Ref).First())

Это вызывает ошибку, говоря, что я должен использовать FirstOrDefault, который не поддерживается в базе данных Oracle. Ошибка oracle 11.2.0.3.0 не поддерживает применение. Google показывает это на CodePlex: https://entityframework.codeplex.com/workitem/910.

Это происходит при использовании следующих двоичных файлов:

  • EntityFramework 6.0.0.0
  • Oracle.ManagedDataAccess 4.121.2.0
  • Oracle.ManagedDataAccess.EntityFramework 6.121.2.0
  • .Net Framework 4.5.1

База данных - это база данных Oracle 11.2.0.3.0.

Сгенерированный sql использует OUTER APPLY (см. рисунок ниже), который не поддерживается версией Oracle 11.2.0.3.0, поэтому почему EF/Oracle.ManagedDataAccess пытается ее использовать? Есть ли способ сказать EF не использовать ключевое слово APPLY?

SQL

На следующей странице говорится, что поддержка APPLY была добавлена ​​в Oracle 12c Release 1, но я не могу обновлять все свои базы данных, чтобы сделать работу GROUP BY. http://www.oracle.com/technetwork/database/windows/newfeatures-084113.html

Похоже, что это известная проблема (Известные проблемы в SqlClient для платформы Entity Framework):

Ниже приведены некоторые типичные сценарии, которые могут привести к наличие операторов CROSS APPLY и/или OUTER APPLY на выходе запрос:

  • Запросы LINQ, которые используют методы группировки, которые принимают селектор элементов.

Прежде чем приступать к созданию представления (мне нужно было бы создать представление в нескольких базах данных), может ли кто-нибудь увидеть другое решение?

Для всех, кто заинтересован, SQL, который будет делать то, что я хочу против этой версии базы данных, будет выглядеть примерно так:

select *
from ( select  RANK() OVER (PARTITION BY sm.product ORDER BY refs.map)      ranking, sm.*
            from    schema.table sm,
                    (
                        select 'R9' ref, 0 map from dual
                        union all
                        select 'R1' ref, 1 map from dual
                        union all
                        select 'R6' ref, 2 map from dual
                    ) refs
            where   sm.ref= refs.ref                               
          ) stock
where ranking  = 1

В конечном итоге код будет находиться в классе сервиса, переданном контроллеру OData и в интерфейсе API. В приведенном ниже примере используются демо-данные, у реальной базы данных 700 000 записей, поэтому я хотел бы избежать выполнения запроса и позволить OData обрабатывать ограничения страниц и дальнейшую фильтрацию.

using System;
using System.Collections.Generic;
using System.Linq;

namespace DemoApp
{
    class Program
    {
        public class Product
        {
            public string Ref { get; set; }
            public string Code { get; set; }
            public int Quantity { get; set; }
        }

        //demo data
        static readonly List<Product> Products = new List<Product>
        {
            new Product { Ref = "B2", Code = "ITEM1", Quantity = 1},
            new Product { Ref = "R1", Code = "ITEM1", Quantity = 2},
            new Product { Ref = "R9", Code = "ITEM1", Quantity = 3},
            new Product { Ref = "R9", Code = "ITEM2", Quantity = 4},
            new Product { Ref = "R6", Code = "ITEM2", Quantity = 5},
            new Product { Ref = "B2", Code = "ITEM3", Quantity = 6},
            new Product { Ref = "R1", Code = "ITEM3", Quantity = 7},
            new Product { Ref = "R9", Code = "ITEM3", Quantity = 8},
            new Product { Ref = "B2", Code = "ITEM4", Quantity = 9},
            new Product { Ref = "X3", Code = "ITEM4", Quantity = 10},
            new Product { Ref = "B8", Code = "ITEM5", Quantity = 10},
            new Product { Ref = "R6", Code = "ITEM5", Quantity = 12},
            new Product { Ref = "M2", Code = "ITEM5", Quantity = 13},
            new Product { Ref = "R1", Code = "ITEM5", Quantity = 14},
        };

    static void Main(string[] args)
    {
        // this array is of variable length, and will not always contain 3 items.
        var refFilterSequence = new List<string> {"R9", "R1", "R6"};

        var results = GetProductsForODataProcessing(refFilterSequence);

        // some further filtering may occur after the queryable is returned.
        // the actual implmentation is an OData Web API, so filters, expansions etc could be added.

        //results = results.Where(p => p.Quantity > 2);

        results.ToList().ForEach(p => Console.WriteLine("RANK:{0}\tREF:{1}\tCode:{2}\tQty:{3}", "?", p.Ref, p.Code, p.Quantity));
        Console.ReadLine();
    }

    static IQueryable<Product> GetProductsForODataProcessing(List<string> filterSequence )
    {
        var productResults = Products.Where((p) => filterSequence.Contains(p.Ref))
            .GroupBy(g => g.Code, (key, g) => g.OrderBy(whp => whp.Ref).First()).AsQueryable();

        return productResults;               
    }
}


// Example Output
// .......................
// REF:R1 Code:ITEM1 Qty:2
// REF:R6 Code:ITEM2 Qty:3
// REF:R1 Code:ITEM3 Qty:7
// REF:R1 Code:ITEM5 Qty:14

Ответ 1

Поскольку вы можете написать запрос самостоятельно. Возможно, вы можете создать с ним хранимую процедуру и вызвать SP из Entity Framework.

Ответ 2

Вы можете попробовать выполнить ToArray перед GroupBy, чтобы он выполнялся в памяти. Это не оптимальная производительность, но она должна работать.

 var productResults = Products.Where((p) => refFilterSequence.Contains(p.Ref)).ToArray()
                    .GroupBy(g => g.Code, (key, g) => g.OrderBy(whp => whp.Ref).First()).ToList();

Ответ 3

Oracle 11 не поддерживает APPLY. Oracle 12, однако.