Linq Outer Join со значениями по умолчанию с обеих сторон - PullRequest
0 голосов
/ 29 июля 2011

У меня проблема с внешним соединением.Я нашел эту очень полезную статью stackoverflow: linq-full-external-join , но у меня возникают проблемы при моем соединении, когда данные извлекаются с ошибкой:

NotSupportedException: Unsupported overload used for query operator 'DefaultIfEmpty'.

Воткод:

var query =
        from i2 in (from sel in (from i in (from a1 in Activities
                    where a1.ActivityDate >= DateTime.Parse("4/15/2011")
                    && a1.ActivityDate < DateTime.Parse("4/19/2011")
                    select new { 
                        a1.ActivityDate,
                        a1.RecID} )
        group i by new { i.RecID }
        into g
        select new {
            g.Key.RecID,
            MaxDate = g.Max(i => i.ActivityDate)})
        join a in Activities on 1 equals 1 
        where (sel.MaxDate == a.ActivityDate && sel.RecID == a.RecID) 
        select new { 
            a.RecID,  
            a.UserName, 
            ActivityDate = a.ActivityDate.Date, 
            Sum1 = (a.StatusID == 7) ? 1 : 0,
            Sum2 = (a.StatusID == 5) ? 1 : 0,
            Sum3 = (a.StatusID == 4) ? 1 : 0})
        group i2 by new {
                UserName = i2.UserName,
                ActivityDate = i2.ActivityDate
                }
        into g2 
        select new {
            JoinId = (string)(g2.Key.ActivityDate + "_" + g2.Key.UserName),
            ActivityDate = (DateTime)g2.Key.ActivityDate,
            UserName = (string)g2.Key.UserName,
            Sum1 = (int)g2.Sum(i2 => i2.Sum1),
            Sum2 = (int)g2.Sum(i2 => i2.Sum2),
            Sum3 = (int)g2.Sum(i2 => i2.Sum3),
        };
    var query2 = from s in ProdHistories
    where s.CompletedDate >= DateTime.Parse("4/15/2011") 
    && s.CompletedDate <= DateTime.Parse("4/19/2011")
    select new { 
        JoinId = (string)(s.CompletedDate + "_" + s.UserName),
        CompletedDate = (DateTime)s.CompletedDate,
        UserName = (string)s.UserName,
        Type1 = (int)s.Type1,
    Type2 = (int)s.Type2,
            Type3 = (int)s.Type3,
            Type4 = (int)s.Type4,
            Type5 = (int)s.Type5,
            Type6 = (int)s.Type6,
            Type7 = (int)s.Type7,
            Type8 = (int)s.Type8,
            };

    var joinLeft = from ph in query2
        join act in query 
        on ph.JoinId equals act.JoinId
        into temp 
        from act in temp.DefaultIfEmpty(new { 
            JoinId = (string)ph.JoinId,
            ActivityDate = (DateTime)ph.CompletedDate,
            UserName = (string)ph.UserName, 
            Sum1 = default(int),
            Sum2 = default(int),
            Sum3 = default(int),
             })
        select new { ph.UserName, 
            ph.CompletedDate,
            ph.Type1,
            ph.Type2,
            ph.Type3,
            ph.Type4,
            ph.Type5,
            ph.Type6,
            ph.Type7,
            ph.Type8,
            act.Sum1,
            act.Sum2,
            act.Sum3};

    query.Dump(); // successfully dumps (in LinqPad) data - no nulls
    query2.Dump(); // successfully dumps (in LinqPad) data - no nulls
    joinLeft.Dump(); // raises: NotSupportedException: Unsupported overload used for query operator 'DefaultIfEmpty'.

Я попробовал то же самое условие внешнего соединения и использую фиксированные данные, и оно работает:

 var query = new[]
 {
      new {     JoinId = "12345", ActivityDate = DateTime.Parse("1/1/2011"), UserName = "UID1", Sum1 = 10, Sum2 = 11, Sum3 = 12 },
      new {     JoinId = "23456", ActivityDate = DateTime.Parse("1/2/2011"), UserName = "UID2", Sum1 = 20, Sum2 = 21, Sum3 = 22 },
      new {     JoinId = "34567", ActivityDate = DateTime.Parse("1/3/2011"), UserName = "UID3", Sum1 = 30, Sum2 = 31, Sum3 = 32 },
 };

 var query2 = new[]
 {
      new {     JoinId = "12345", CompletedDate = DateTime.Parse("1/1/2011"), UserName = "UID1", Type1 = 110, Type2 = 111, Type3 = 112, Type4 = 113, Type5 = 114, Type6 = 115, Type7 = 116, Type8 = 117 },
      new {     JoinId = "23456", CompletedDate = DateTime.Parse("1/2/2011"), UserName = "UID2", Type1 = 210, Type2 = 211, Type3 = 212, Type4 = 213, Type5 = 214, Type6 = 215, Type7 = 216, Type8 = 217 },
      new {     JoinId = "45678", CompletedDate = DateTime.Parse("1/5/2011"), UserName = "UID4", Type1 = 310, Type2 = 311, Type3 = 312, Type4 = 313, Type5 = 314, Type6 = 315, Type7 = 316, Type8 = 317 },
 };

 var joinLeft = from ph in query2
           join act in query 
           on ph.JoinId equals act.JoinId
           into temp 
           from act in temp.DefaultIfEmpty(new { 
                JoinId = (string)ph.JoinId,
                ActivityDate = (DateTime)ph.CompletedDate,
                UserName = (string)ph.UserName, 
                Sum1 = default(int),
                Sum2 = default(int),
                Sum3 = default(int),
                 })
           select new { ph.UserName, 
                ph.CompletedDate,
                ph.Type1,
                ph.Type2,
                ph.Type3,
                ph.Type4,
                ph.Type5,
                ph.Type6,
                ph.Type7,
                ph.Type8,
                act.Sum1,
                act.Sum2,
                act.Sum3};

 joinLeft.Dump();

результат:

UserName CompletedDate    Type1 Type2 Type3 Type4 Type5 Type6 Type7 Type8 Sum1 Sum2 Sum3 
UID1 1/1/2011 12:00:00 AM 110   111   112   113   114   115   116   117   10   11   12
UID2 1/2/2011 12:00:00 AM 210   211   212   213   214   215   216   217   20   21   22
UID4 1/5/2011 12:00:00 AM 310   311   312   313   314   315   316   317   0    0    0

Я видел другая статья о стеке потока , где Джон Скит использует IEnumerable как часть решения этой ошибки в другом контексте, но я не совсем уверен, как это применить.

Спасибо за любые подсказки!

1 Ответ

1 голос
/ 29 июля 2011

Я думаю, что сообщение , которое вы взяли с кода , также отвечает на ваш вопрос (жирный шрифт - мой).

Это работает так, как написано, поскольку находится в LINQ to Objects. Если LINQ to SQL или другое, перегрузка DefaultIfEmpty(), которая принимает значение по умолчанию, может не работа. Тогда вам придется использовать условный оператор для условно получить значения.

т.е..,

var leftOuterJoin = from first in firstNames
                    join last in lastNames
                    on first.ID equals last.ID
                    into temp
                    from last in temp.DefaultIfEmpty()
                    select new
                    {
                        first.ID,
                        FirstName = first.Name,
                        LastName = last != null ? last.Name : default(string),
                    };

Обновление от Робба

Это ответ, и я смог заставить свой код работать, преобразовав IQueryable в Список, используя ToList().

Мой комментарий к обновлению

Определенно это еще один вариант, если вы не хотите, чтобы вы join работали с базой данных, и вам удобно работать с ними на объектах. Однако это может повлиять на производительность, если действительно много объектов (сколько - зависит). Следуйте обычной оптимизационной мантре: измерить, измерить и измерить . Если это не имеет значения, ToList будет работать нормально.

...