1
var TblPolls = new[]
{
    new { Guid = 1, Question = "question 1", UserId = 1 },
    new { Guid = 2, Question = "question 2", UserId = 2 },
    new { Guid = 3, Question = "question 3", UserId = 1 },
};

var TblVotes = new[]
{
    new { VoteId = 1, UserId = 1, PollGuid = 1, Value = "No" },
    new { VoteId = 2, UserId = 2, PollGuid = 1, Value = "Yes" },
    new { VoteId = 3, UserId = 3, PollGuid = 1, Value = "Yes" },
    new { VoteId = 4, UserId = 1, PollGuid = 2, Value = "No" },
};

I have these tables above, but as you can see that question 3 has not been voted. So how do i still pull out the data for TblPolls row for Question 3, taking into account that there are no votes for this Poll, so the result should be that the row question 3 is returned but the values in votes are null. Im guessing this is kind of a left join in linq. I have tried this but im not getting back the results i want:

poll = (
        from polls in ctx.TblPolls
        join votes in ctx.TblVotes on polls.Guid equals votes.PollGuid into gg
        where polls.Guid == guid
        from ff in gg.DefaultIfEmpty()
        where (ff.UserId == userId || ff.UserId == null) && ff.PollGuid == guid
        select new
        {
            polls.UserId,
            polls.Id,
            polls.Guid,
            polls.Question,
            polls.Description,
            polls.Date,
            polls.VideoId,
            value = gg.Where(x => x.UserId == userId && x.PollGuid == guid).FirstOrDefault().Value,
            VoteCount = gg.Count(),
        }
        ).ToList();

Whats happening is the if a users vote does not exist for a TblPoll Question im getting empty array when the question should be returned even if there are not votes for that user.

So how would the linq query work for the reuslts to occur as i suggested?

redoc01
  • 2,107
  • 5
  • 32
  • 64
  • 1
    So a left join? Eg. https://stackoverflow.com/questions/19356439/left-join-in-linq-to-entities – Rand Random Jun 19 '23 at 21:00
  • 1
    Why would this be true `ff.PollGuid == guid` for guid == 3 (Question 3)? – Rand Random Jun 19 '23 at 21:04
  • ive been playing with this for a while, its first time i have used linq to this extent using way much advanced scenarios. Im not sure tell you the truth im just trying things. – redoc01 Jun 19 '23 at 21:06
  • 1
    Remove `ff.PollGuid == guid` from `where (ff.UserId == userId || ff.UserId == null) && ff.PollGuid == guid` that’s most likely wrong – Rand Random Jun 19 '23 at 21:09
  • 1
    Try looking into a cross join. I've never done this in Linq... but the basic idea is that you cross join to get all of the possible vote/poll combinations... Then union that result to the actual vote tallies... – TGnat Jun 19 '23 at 21:11
  • 1
    The basic issue here is that, because the items in `TblVotes` are reference types, `gg.DefaultIfEmpty()` returns a single `null` value when gg is empty. Then when you do e.g. `ff.UserId` you crash. Also, why not filter on `guid` and `userId` before the join? If you do you should have better performance. Also, it seems strange that Polls and Votes both have `UserId`. Are these related somehow? – dbc Jun 19 '23 at 21:32
  • 1
    With that in mind, is this what you want? With that in mind, is this what you want? https://dotnetfiddle.net/AcTT97. I don't know how to fix `value = gg.Where(x => x.UserId == userId && x.PollGuid == guid).FirstOrDefault().Value` because you don't show any property `Value` in `TblVotes`. – dbc Jun 19 '23 at 21:33
  • The userid's are needed in both tables becuase the user that created the poll can also vote for the poll question and a specific user can also vote which will update the option selected on the ui of the value selected by the user – redoc01 Jun 19 '23 at 22:38

1 Answers1

2

You have two tables, TblPolls with a property Guid and TblVotes with properties UserId and PollGuid, and you want to find all polls for a given poll guid, and tally up all votes for that poll for a given user id -- or none, if no votes were case.

You can do this with a left outer join as follows:

var selectedPolls = (
    from poll in ctx.TblPolls
    // Filter on polls.Guid before the join, for performance and simplicity.
    where poll.Guid == guid
    // Filter on vote.UserId and vote.PollGuid before the join, for performance and simplicity.
    join vote in ctx.TblVotes.Where(v => v.UserId == userId && v.PollGuid == guid) on poll.Guid equals vote.PollGuid into gg
    from vote in gg.DefaultIfEmpty() // At this point vote will be null if gg was empty.
    select new
    {
        poll.UserId,
        poll.Id, // Not shown in question.
        poll.Guid,
        poll.Question,
        poll.Description, // Not shown in question.
        poll.Date, // Not shown in question.
        poll.VideoId, // Not shown in question.
        //What is vote.Value?  It isn't shown in the question so I don't know how to fix this line:
        //value = gg.Where(x => x.UserId == userId && x.PollGuid == guid).FirstOrDefault().Value,
        //But possibly this is correct:
        value = (vote != null ? vote.Value : -1), // Substitute whatever default value you want here.
        VoteCount = gg.Count(),
    }
).ToList();

Notes:

  • For performance and simplicity, you should filter the Guid of the polls and the UserId and PollGuid of the votes before doing the join.

  • Since the contents of TblVotes are classes not structs, from ff in gg.DefaultIfEmpty() will return null if gg is empty. Then ff.UserId will cause an exception.

  • Your TblVotes does not have a property Value so I don't know how to fix

    value = gg.Where(x => x.UserId == userId && x.PollGuid == guid).FirstOrDefault().Value
    

    Since you are already looping through gg anyway, maybe you just want:

    value = (vote != null ? vote.Value : -1),
    
  • I modified some of your variable names for clarity.

Demo fiddle here.

Update from redoc01:

The solution was almost there, i just modified this line:

join vote in ctx.TblVotes.Where(v => v.UserId == userId && v.PollGuid == guid) on poll.Guid equals vote.PollGuid into gg
where poll.Guid == guid <----- to here
from vote in gg.DefaultIfEmpty() // At this point vote will be null if gg was empty.

and this line:

from value = vote?.Value, to value = vote.Value,

And now works!...

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Im getting this error when using you code i have been trying also nearly getting there the only problem was linking the userId. – redoc01 Jun 19 '23 at 22:05
  • but this is the error im getting: System.InvalidOperationException: 'The LINQ expression 'gg' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. – redoc01 Jun 19 '23 at 22:06
  • thanks guys im understanding the linq much more and using @TGnat technique by using cross joining to eliminate the results that shouldn't be there and also to the guys who also helped. – redoc01 Jun 19 '23 at 22:12
  • 1
    @redoc01 - thanks for the edit. I tested with [LINQ to Objects](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects) and indeed `vote?.Value` won't compile if `TblVotes` is an `IQueryable`. I couldn't reproduce the requirement to move `where poll.Guid == guid` after the join, that may be some restriction specific to entity framework. – dbc Jun 19 '23 at 22:30