Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ to SQL join generates SQL which joins on IS NULL

Tags:

c#

.net

join

linq

I am not good at Linq expression, today I am running into one weird issue as the below of inner join statement,

var orders = (from q in dao.CurrentDBContext.New_OrderForm 
              join d in dao.CurrentDBContext.New_OrderGoodsDetail on q.billNum equals d.billNum
              select new
             {    
                q.billNum,
                q.orderSource,
                q.sourceOddNum    
                d.PPT    
             }

While I traced the linq statement, I am confused of that Entity Framework will convert the linq statement to the below sql statment

SELECT 
[Extent1].[billNum] AS [billNum], 
[Extent1].[orderSource] AS [orderSource], 
[Extent1].[sourceOddNum] AS [sourceOddNum], 
[Extent2].[PPT] AS [PPT]
FROM [dbo].[New_OrderForm] AS [Extent1]
INNER JOIN [dbo].[New_OrderGoodsDetail] AS [Extent2] 
           ON ([Extent1].[billNum] = [Extent2].[billNum]) OR 
              (([Extent1].[billNum] IS NULL) AND ([Extent2].[billNum] IS NULL))

Do you know why the below SQL segment did automatically append?

OR (([Extent1].[billNum] IS NULL) AND ([Extent2].[billNum] IS NULL)"

I don't expect that the above automatically append, since it did slow down SQL performance. Any suggestions?

like image 856
beyond8848 Avatar asked Jan 08 '16 12:01

beyond8848


People also ask

Is NULL in LINQ to SQL?

In SQL Server, a SQL statement like 'NULL=NULL' evaluates to false. however 'NULL IS NULL' evaluates to true. So, for NULL values in your database columns, you need to use the 'IS' operator instead of the regular '=' operator.

Does join happen on NULL values?

Null values in tables or views being joined never match each other. Since bit columns do not permit null values, a value of 0 appears in an outer join when there is no match for a bit column in the inner table. The result of a join of null with any other value is null.

What type of join is LINQ join?

A LINQ JOIN keyword is used to combine rows from two or more tables, based on a common field between them. Like SQL Joins, the Linq is also provided some keywords to achieve Inner Join and Outer Join. As we know from SQL outer join is divided into 2 groups that is Left Outer Join and Right Outer Join.

How to use left join in LINQ with NULL values?

If there are no columns matching in the right table, it returns NULL values. In LINQ to achieve LEFT JOIN behavior, it is mandatory to use "INTO" keyword and "DefaultIfEmpty ()" method. We can apply LEFT JOIN in LINQ like as :

How to perform right join operation in LINQ to SQL?

In LinQ To SQL, Right Join Operation can be achieved in the similar way as left join that we just did, just by changing the table name in the last part: I guess, you already can guess, how to perform the full outer join. Yes, we will just have to include properties from both tables which are empty as like follows:

What are the different types of LINQ join in SQL?

C# LINQ Joins With SQL 1 Types of LINQ Joins 2 Venn diagram for LINQ Joins. ... 3 LINQ Queries using LINQ PAD. ... 4 INNER JOIN. ... 5 INNER JOIN Among More than Two Tables. ... 6 INNER JOIN On Multiple Conditions. ... 7 LEFT JOIN or LEFT OUTER JOIN. ... 8 CROSS JOIN. ... 9 GROUP JOIN. ... 10 GROUP JOIN As SubQuery. ...

How to join two tables in LINQ without condition?

This join does not need any condition to join two tables. This join returns records or rows that are a multiplication of record number from both the tables means each row on the left table will be related to each row of a right table. In LINQ to achieve CROSS JOIN behavior, there is no need to use Join clause and where clause.


4 Answers

Here is what you can do in case you cannot change the billNum columns to be non nullable.

First, set the option mentioned by @Giorgi

class CurrentDBContext
{
    public CurrentDBContext()
    {
        Configuration.UseDatabaseNullSemantics = true;
        // ...
    }
}

Then change the LINQ query to not use join, but simple where like this

var orders = (from q in dao.CurrentDBContext.New_OrderForm 
              from d in dao.CurrentDBContext.New_OrderGoodsDetail
              where q.billNum == d.billNum
              select ...

The result will be the exact SQL query as the one you've shown (with JOIN!) without the OR part.

like image 103
Ivan Stoev Avatar answered Oct 25 '22 00:10

Ivan Stoev


It seems that Linq translates q.billNum equals d.billNum is such a way that it also includes a valid match in case both q.billNum and d.billNum are NULL (in SQL NULL is never equal to NULL, hence the OR in your query).

Making both fields non-nullable would be the best solution, provided both fields can never be NULL.

If this is not the case, you could also try to add a where clause in your Linq statement to specifiy that both q.billNum and d.billNum cannot be NULL. With any luck, Linq will recognize that nullable values are not possible.

Note: If you are working with Oracle you should check for empty strings as well as NULL (empty string is equivalent to NULL). Empty strings should be fine as a valid value in SQL Server.

As the above did not help, you could try to write the query yourself. If I'm not mistaking it would be something along the following lines (assuming var is an List<Order> in your example code - the results of your query should match the class you are using):

StringBuilder query = new StringBuilder();
query.AppendLine("SELECT [Extent1].[billNum] AS [billNum],");
query.AppendLine("       [Extent1].[orderSource] AS [orderSource],");
query.AppendLine("       [Extent1].[sourceOddNum] AS [sourceOddNum],");
query.AppendLine("       [Extent2].[PPT] AS [PPT]");
query.AppendLine("FROM [dbo].[New_OrderForm] AS [Extent1]");
query.AppendLine("INNER JOIN [dbo].[New_OrderGoodsDetail] AS [Extent2] ON [Extent1].[billNum] = [Extent2].[billNum]");

List<Order> orders = DbContext.Database.SqlQuery<Order>(query.ToString()).ToList();

I have used similar workarounds to get around performance issues in the past.

like image 38
Sam Avatar answered Oct 25 '22 01:10

Sam


If you are using EF6 try setting

context.Configuration.UseDatabaseNullSemantics = true;

and it will not generate NULL checks for those columns.

According to documentation

For example (operand1 == operand2) will be translated as: (operand1 = operand2) if UseDatabaseNullSemantics is true, respectively (((operand1 = operand2) AND (NOT (operand1 IS NULL OR operand2 IS NULL))) OR ((operand1 IS NULL) AND (operand2 IS NULL))) if UseDatabaseNullSemantics is false.

like image 39
Giorgi Avatar answered Oct 25 '22 01:10

Giorgi


Following on from @Giorgi's answer, the UseDatabaseNullSemantics flag will not work with the equals keyword - only the == operand. Thus in order to get round this and ensure the join on billNum is not part of the OR clause this approach should work (in conjunction with the UseDatabaseNullSemantics flag):

var orders = (from q in dao.CurrentDBContext.New_OrderForm 
          from d in dao.CurrentDBContext.New_OrderGoodsDetail 
          where q.billNum == d.billNum
          select new
         {    
            q.billNum,
            q.orderSource,
            q.sourceOddNum    
            d.PPT    
         }

This will generate the JOIN without the OR.

like image 32
strickt01 Avatar answered Oct 25 '22 02:10

strickt01