To summarize the issue, I have a query that Entity Framework generates based on my LINQ query, which is joining two tables on a value that is stored as a UNIQUEIDENTIFIER in one and a (Nullable) VARCHAR in another.
Because of the two different data types, I have to call ToString() on the GUID value in the LINQ query.
The SQL code that is produced, although admirable, ends up being slow because, rather than just compare the two columns (as I might in a standard SQL query), it tries to convert the UNIQUEIDENTIFIER columns in the one table and tries to catch the Nulls.
If I take the query that Entity Framework produces and simply remove the part where it tries to do the conversion, the query goes from 30 seconds to 0 seconds when there are approximately 300 records to return.
So, assuming that I'm stuck with tables that have different data types to store the same values, what I would like to know is whether I could tell Entity Framework to NOT attempt the conversion on this column and just do a straight up comparison. (We do this all the time in queries and stored procedures and it works just fine and is fast.)
If there is not a way to tell Entity Framework, in this particular instance to do that, would I just need to write a stored procedure and call it, rather than trying to write the query in LINQ?
Here is the method that returns notifications for the user.
var notifications = from un in _context.UserNotifications
join n in _context.Notifications on un.NotificationId equals n.Id
join r in _context.Referrals on n.txt_assess_seq_no equals r.seq_no.ToString()
where un.UserId == userId & !un.IsRead
orderby n.create_timestamp descending
select n;
This gives me the results I want, but the SQL query generated by Entity Framework is slow because of the joining of Notifications.txt_assess_seq_no (which is a VARCHAR in the table) and Referrals.seq_no (which is a UNIQUIDENTIFIER).
Here is the SQL that is generated:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM (SELECT [Extent1].[UserId] AS [UserId], [Extent2].[txt_assess_seq_no] AS [txt_assess_seq_no]
FROM [dbo].[UserNotifications] AS [Extent1]
INNER JOIN [dbo].[ContactDetails] AS [Extent2] ON [Extent1].[NotificationId] = [Extent2].[seq_no]
WHERE [Extent1].[IsRead] = 0 ) AS [Filter1]
INNER JOIN [dbo].[Referrals] AS [Extent3] ON ([Filter1].[txt_assess_seq_no] = (LOWER( CAST( [Extent3].[seq_no] AS nvarchar(max))))) OR (([Filter1].[txt_assess_seq_no] IS NULL) AND (LOWER( CAST( [Extent3].[seq_no] AS nvarchar(max))) IS NULL))
WHERE [Filter1].[UserId] = 'USER ID (GUID) AS STRING'
) AS [GroupBy1]
If I simply change this line:
INNER JOIN [dbo].[Referrals] AS [Extent3] ON ([Filter1].[txt_assess_seq_no] = (LOWER( CAST( [Extent3].[seq_no] AS nvarchar(max))))) OR (([Filter1].[txt_assess_seq_no] IS NULL) AND (LOWER( CAST( [Extent3].[seq_no] AS nvarchar(max))) IS NULL))
to:
([Filter1].[txt_assess_seq_no] = [Extent3].[seq_no])
It is super duper fast and is just what I want, but I'm not quite sure how to get there. I'd like to keep it written in LINQ, but if I just have to use a stored procedure or some old fashioned SQL code, I will do that.
Any thoughts or advice would be greatly appreciated!