Возврат IQueryable, содержащий 2 подкласса - PullRequest
4 голосов
/ 18 января 2009

Можете ли вы вернуть IQueryable, который состоит из двух или более разных подклассов? Вот моя попытка показать, что я имею в виду. Выдает ошибку:

System.NotSupportedException: вводится в Союз или Конкат имеют назначенных членов в другом порядке ..

var a = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.ListingID != null
        select new OrderItemA {
            // etc
        } as OrderItem;

var b = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.AdID != null
        select new OrderItemB {
            //etc
        } as OrderItem;

return a.Concat<OrderItem>(b);

Ответы [ 4 ]

5 голосов
/ 22 июля 2009

Попробуйте выполнить конкатат на IEnumerable вместо IQueryable:

return a.AsEnumerable().Concat(b.AsEnumerable());

Если вам нужен результат IQueryable, вы можете сделать это:

return a.AsEnumerable().Concat(b.AsEnumerable()).AsQueryable();

Выполнение этого заставит заставить конкат работать в памяти, а не в SQL, и любые дополнительные операции также будут происходить в памяти (LINQ To Objects).

Однако, в отличие от примера .ToList (), выполнение все равно должно быть отложено (что делает ваши данные загруженными).

2 голосов
/ 09 сентября 2009

Интересно, что после прочтения вашего поста и небольшого тестирования я понял, что то, что вы делаете на самом деле, мне кажется, работает нормально, учитывая, что проекционная часть, которую вы показываете как многоточие в обоих ваших запросах, совпадает. Видите ли, LINQ to SQL, по-видимому, создает базовую проекцию для команды выбора SQL на основе операторов присваивания свойств, а не фактического материализуемого типа, при условии, что обе стороны имеют одинаковое число, тип и порядок (не уверен об этом) из назначений членов запрос UNION должен быть действительным.

Мое решение, с которым я работал, - это создать свойство в моем классе DataContext, которое будет работать так же, как SQL-представление, в котором оно позволяет мне написать запрос (в моем случае объединение между двумя различными таблицами), а затем используйте этот запрос, как будто он сам по себе как таблица при составлении операторов выбора только для чтения.

public partial class MyDataContext
{
    public IQueryable<MyView> MyView
    {
        get
        {
            var query1 = 
                from a in TableA
                let default_ColumnFromB = (string)null
                select new MyView()
                {
                    ColumnFromA = a.ColumnFromA,
                    ColumnFromB = default_ColumnFromB,
                    ColumnSharedByAAndB = a.ColumnSharedByAAndB,
                };

            var query2 = 
                from a in TableB
                let default_ColumnFromA = (decimal?)null
                select new MyView()
                {
                    ColumnFromA = default_ColumnFromA,
                    ColumnFromB = b.ColumnFromB,
                    ColumnSharedByAAndB = b.ColumnSharedByAAndB,
                };

            return query1.Union(query2);
        }
    }
}

public class MyView
{
    public decimal? ColumnFromA { get; set; }
    public string ColumnFromB { get; set; }
    public int ColumnSharedByAAndB { get; set; }
}

Обратите внимание на две ключевые вещи:

Прежде всего, проекция, сформированная запросами, составляющими обе половины Союза, имеет одинаковое количество, тип и порядок столбцов. Теперь LINQ может потребовать, чтобы порядок был таким же (не уверен в этом), но это определенно верно, что SQL делает для UNION, и мы можем быть уверены, что LINQ потребует по крайней мере того же типа и числа столбцов и этих «столбцов» известны члену назначения , а не из свойств типа, который вы создаете в своей проекции.

Во-вторых, LINQ в настоящее время не позволяет использовать несколько констант в проекциях для запросов, которые формулируют Concat или Union, и, насколько я понимаю, это происходит главным образом потому, что эти два отдельных запроса оптимизируются отдельно перед обработкой операции Union. Обычно LINQ to SQL достаточно умен, чтобы понять, что если у вас есть постоянное значение, которое используется только в проекции, то зачем отправлять его в SQL, просто чтобы оно вернулось таким, каким оно было, вместо того, чтобы прикреплять его как пост? процесс после того, как необработанные данные возвращаются с SQL Server. К сожалению, проблема здесь в том, что это тот случай, когда LINQ to SQL является умным для его же блага, поскольку он оптимизирует каждый отдельный запрос слишком рано в процессе. Я нашел способ обойти это, используя ключевое слово let , чтобы сформировать переменную диапазона для каждого значения в проекции, которое будет реализовано путем получения его значения из константы. Каким-то образом это заставляет LINQ to SQL переносить эти константы в фактическую команду SQL, которая сохраняет все ожидаемые столбцы в результирующем UNION. Подробнее об этой технике можно узнать здесь .

Используя эту технику, по крайней мере, у меня есть что-то многократно используемое, так что независимо от того, насколько сложным или уродливым может быть настоящий Union, особенно с переменными диапазона, в ваших конечных запросах вы можете записывать запросы в эти псевдо-представления, такие как MyView и deal со сложностью внизу.

2 голосов
/ 18 января 2009

Я предполагаю, что это потому, что вы используете LINQ в контексте LINQ-to-SQL.

Таким образом, использование Concat означает, что LINQ2SQL потребуется объединить оба запроса в запрос SQL UNION, который может быть из источника System.NotSupportedException.

Можете ли вы попробовать это:

return a.ToList().Concat<OrderItem>(b.ToList());

И посмотри, имеет ли это какое-то значение?

То, что сделано выше, заключается в том, что он выполняет запрос дважды, а затем объединяет их в памяти вместо hot-off-SQL, чтобы избежать проблемы перевода запроса.

Возможно, это не идеальное решение, но если эта работа, мое предположение, вероятно, является правильным, что это проблема перевода запроса:

Дополнительная информация о переводе Union и Concat на SQL:

Надеюсь, это поможет.

1 голос
/ 18 января 2009

Можете ли вы сделать прогноз после Конкат?

// construct the query
var a = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.ListingID != null
        select new {
            type = "A"
            item = oi
        }

var b = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.AdID != null
        select new {
            type = "B"
            item = oi
        }

var temp = a.Concat<OrderItem>(b);

// create concrete types after concatenation
// to avoid inheritance issue

var result = from oi in temp
             select (oi.type == "A"
                 ? (new OrderItemA {
                         // OrderItemA projection

                     } as OrderItem)

                 : (new OrderItemB {
                         // OrderItemB projection

                     } as OrderItem)
             );

return result

Не уверен, что троичный оператор работает в LINQ2SQL в приведенном выше сценарии, но это может помочь избежать проблемы наследования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...