8

I have two tables below:

Project(
    ProjectID       INT,
    Name            VARCHAR(200),
    AssignedUserID  INT NULL
)
User(
    UserID      INT,
    LastName    VARCHAR(50),
    FirstName   VARCHAR(50)
)

I'm using Entity Framework Database-First approach. So in my Model:

Class Project{
    public int ProjectID;
    public string Name;
    public Nullable<int> AssignedUserID;
}

Class User{
    public int UserID;
    public string LastName;
    public string FirstName;
}

I want to query all PROJECT and its assigned user:

SELECT
    p.ProjectID,
    p.Name,
    u.LastName,
    u.FirstName
FROM Project p
LEFT JOIN Users u ON u.UserID = p.AssignedUserID

Translating to Linq:

var projectDetails =
    from p in context.Project
    join u in context.User
        on p.AssignedUserID equals u.UserID into lj
    from x in lj.DefaultIfEmpty()
        select new {
            ProjectID = p.ProjectID,
            ProjectName = p.Name,
            UserLastName = u.LastName,
            UserFirstName = u.FirstName
        }

However, I'm getting an error:

The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.

I've tried both solutions on int? and int comparison when LEFT OUTER JOIN in Linq issue, but still it's giving me these errors:

Using the solution: (int)(p.AssignedUserID ?? default(int))

LINQ to Entities does not recognize the method 'Int32 ToInt32(Int32)' method, and this method cannot be translated into a store expression.

Using the solution GetValueOrDefault():

LINQ to Entities does not recognize the method 'Int32 GetValueOrDefault(Int32)' method, and this method cannot be translated into a store expression.

How do you do LEFT JOIN on Nullable<int> and int?

Community
  • 1
  • 1
Felix Pamittan
  • 31,544
  • 7
  • 41
  • 67

3 Answers3

14

This is what I do when I have to do a Join on a nullable field

Original Linq:

var projectDetails =
    from p in context.Project
    join u in context.User
        on p.AssignedUserID equals u.UserID into lj
    from x in lj.DefaultIfEmpty()
        select new {
            ProjectID = p.ProjectID,
            ProjectName = p.Name,
            UserLastName = u.LastName,
            UserFirstName = u.FirstName
        }

Modified Linq:

var projectDetails =
    from p in context.Project
    join u in context.User
        on new {User = p.AssignedUserID} equals new {User = (int?)u.UserID} into lj
    from x in lj.DefaultIfEmpty()
        select new {
            ProjectID = p.ProjectID,
            ProjectName = p.Name,
            UserLastName = x.LastName,
            UserFirstName = x.FirstName
        }

The problem is that you are trying to join an int with an int?, which is giving you the error message, cast the int of the UserId to a nullable int, will solve your issue.

CheGueVerra
  • 7,849
  • 4
  • 37
  • 49
2

If any of the columns in join is of Nullable type, we need to fetch itts actual value using .Value, something like this:-

    join u in context.User
        on p.AssignedUserID.Value equals u.UserID into lj

Or you can also do this:-

join u in context.User
            on new { p.AssignedUserID } equals new { AssignedUserID = u.UserID } into lj
Rahul Singh
  • 21,585
  • 6
  • 41
  • 56
  • You need to have `new {AssignedUserID = p.AssignedUserID } equals new { AssignedUserID = u.UserID }` – Jenish Rabadiya Mar 09 '15 at 06:55
  • 1
    @JenishRabadiya - Nope that's not mandatory. Both will infer the same name as `AssignedUserID ` – Rahul Singh Mar 09 '15 at 06:57
  • @RahulSingh: still getting the same error: `The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'`. It wouldn't even allow me to compile. – Felix Pamittan Mar 09 '15 at 06:58
  • @RahulSingh I read it here on the stackover flow thread that it is mandatory. might be I am getting wrong. here is so thread http://stackoverflow.com/questions/19184019/the-type-of-one-of-the-expressions-in-the-join-clause-is-incorrect-in-entity-fra – Jenish Rabadiya Mar 09 '15 at 07:01
  • 1
    I'm sorry, your answer works. There are some call to Convert.Int32 on the select part that's causing the error. Thanks for your time. +1 – Felix Pamittan Mar 10 '15 at 01:23
2

You're actually doing a lot of the work that EF is supposed to be doing for you (this happens alot, in particular with join).

You need a navigation property on your Project class, not just the FK you have now:

EDIT: updated the following POCOs for EF Database-First (i.e. using partial classes).

// generated code
public partial class Project
{
    public int ProjectId { get; set; }
    public string Name { get; set; }
    public int? AssignedUserId { get; set; }
}

public class User
{
    public int UserId { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

// your code
public partial class Project
{
    [ForeignKey("AssignedUserId")]
    public User User { get; set; }
}

Note that you have to remember to add the partial keyword on the generated version of the Project class each time you regenerate it, but this is still better, because — by keeping your additions in a separate partial class declaration — the partial keyword is the only thing you'll have to remember to change in the generated class.

So your query is simply the following:

var projectDetails = from p in context.Projects
                     select new
                     {
                         p.ProjectId,
                         p.Name,
                         p.User.LastName,
                         p.User.FirstName
                     };

Let's see what this looks like:

Console.WriteLine(projectDetails.ToString());

This yields the following SQL:

SELECT
    [Extent1].[ProjectId] AS [ProjectId],
    [Extent1].[Name] AS [Name],
    [Extent2].[LastName] AS [LastName],
    [Extent2].[FirstName] AS [FirstName]
    FROM [Projects] AS [Extent1]
    LEFT OUTER JOIN [Users] AS [Extent2] ON [Extent1].[AssignedUserId] = [Extent2].[UserId]

Which would appear to be exactly what you want.

John Castleman
  • 1,552
  • 11
  • 12
  • Great answer +1, but the tables don't have an explicit FK relationship. Is there any other way without adding the FK? – Felix Pamittan Mar 09 '15 at 23:50
  • I don't understand what you mean? You already had `AssignedUserId` as a property, heck, as a field in the underlying schema ... is that not a Foreign Key? All I did was add a navigation property which is the whole point of using Entity Framework ... without that, I don't why you wouldn't just use LINQ-to-SQL. – John Castleman Mar 10 '15 at 05:47
  • No, it's an FK, not declared as one. – Felix Pamittan Mar 10 '15 at 05:53
  • The use of the `ForeignKeyAttribute` doesn't make it one in the schema ... it's just telling EF how `User` relates to `AssignedUserId`. I started with your original model, used it to create the database, added my navigation property `User`, and it ran against the same database without modification. – John Castleman Mar 10 '15 at 07:24
  • Oh, so I just need to add the `ForeignKeyAttribute` in the class generated by EF/ – Felix Pamittan Mar 10 '15 at 07:28
  • oh, right, Database-First ... I'll update my answer (TL;DR - yes, add `ForeignKeyAttribute` in generated class). – John Castleman Mar 10 '15 at 07:54
  • It's okay. I get the idea. Thank you very much! – Felix Pamittan Mar 10 '15 at 07:56