开发者

Linq Outer Join with defaults on both sides

I have a problem with an outer join. I found this very helpful stackoverflow article: linq-full-outer-join but I am having issues on my join when the data is retrieved with the error:

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

Here is the code:

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'.

I tried the same outer join clause and use fixed data and it works:

 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.Su开发者_如何学Pythonm3};

 joinLeft.Dump();

result:

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

I saw another stackoverflow article where Jon Skeet uses IEnumerable as part of a solution to this error in another context, but I'm not quite sure how to apply that.

Thanks for any clues!


I think the post you took the code from answers your question as well (bold is mine).

This works as written since it is in LINQ to Objects. If LINQ to SQL or other, the overload of DefaultIfEmpty() that takes in a default may not work. Then you'd have to use the conditional operator to conditionally get the values.

i.e.,

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),
                    };

Update from Robb

This is the answer, and I was able to get my code working by converting the IQueryable to a List using ToList().

My comment on update

Definitely this is another option if you don't want you joins to run on the database and you're comfortable with doing them on objects. However this may affect performance if there are really many objects (how many—it depends). Follow the usual optimization mantra: measure, measure and measure. If this is of no concern, ToList will work fine.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜