将GUID连接到String - 实体框架会产生慢速查询

时间:2017-07-21 20:30:33

标签: sql-server asp.net-mvc entity-framework linq

总结一下这个问题,我有一个Entity Framework基于我的LINQ查询生成的查询,它在一个值上连接两个表,该值在一个存储为UNIQUEIDENTIFIER,在另一个存储为(Nullable)VARCHAR。

由于两种不同的数据类型,我必须在LINQ查询中的GUID值上调用ToString()。

生成的SQL代码虽然令人钦佩,但最终会变慢,因为它不是仅仅比较两列(我可能在标准SQL查询中),而是尝试转换一个表中的UNIQUEIDENTIFIER列并尝试赶上Nulls。

如果我接受Entity Framework生成的查询并简单地删除它尝试进行转换的部分,那么当有大约300条记录要返回时,查询将从30秒变为0秒。

因此,假设我坚持使用具有不同数据类型的表来存储相同的值,我想知道的是我是否可以告诉实体框架不要尝试在此列上进行转换而只是直接执行比较。 (我们一直在查询和存储过程中执行此操作,它工作正常并且速度很快。)

如果没有办法告诉实体框架,在这个特定实例中这样做,我只需要编写一个存储过程并调用它,而不是尝试在LINQ中编写查询吗?

以下是返回用户通知的方法。

require "csv"
HEADERS = [
  'dog',
  'cat',
  'donkey'
]

def write_file

  CSV.open("data.csv", "wb", :headers => HEADERS, :write_headers => true) do |csv|
    csv << { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
    csv << { 'dog' => 'canine'}
    csv << { 'cat' => 'feline', 'dog' => 'canine', 'donkey' => 'asinine' }
    csv << { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine', 'header not provided in the options to #open' => 'not included in output' }
  end
end

write_file # => 
# dog,cat,donkey
# canine,feline,asinine
# canine,,
# canine,feline,asinine
# canine,feline,asinine

这给了我想要的结果,但由于Notifications.txt_assess_seq_no(表中是VARCHAR)和Referrals.seq_no(它是UNIQUIDENTIFIER)的加入,Entity Framework生成的SQL查询很慢。 / p>

以下是生成的SQL:

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;

如果我只是改变这一行:

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]

为:

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))

这是超级快速的,正是我想要的,但我不太确定如何到达那里。我想用LINQ编写它,但如果我只需要使用存储过程或一些老式的SQL代码,我会这样做。

非常感谢任何想法或建议!

2 个答案:

答案 0 :(得分:2)

如上所述,SQL Server将在连接中执行隐式转换,将(N)VARCHAR值转换为UNIQUEIDENTIFIER以进行比较。请注意,这与您的显式转换相反。 EF中没有办法让LINQ查询生成带隐式转换的查询。

您可以尝试使用EF Core项目,并提出一些方法来指定不同类型之间的隐式转换比较。

Anyhoo,您的性能问题可能与转换差异无关。尝试设置

db.Configuration.UseDatabaseNullSemantics = true;

将简化生成的查询中的空值处理。

对于连接,您可以重写为过滤的交叉连接(即非ANSI连接)以获得此行为。例如

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.SqlServer;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp6
{

    class Foo
    {
        public int Id   { get; set; }
        public string Name { get; set; }
        public Guid UID { get; set; }
    }

    class Bar
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string UID { get; set; }
    }
    class Db: DbContext
    {
        public DbSet<Foo> Foo { get; set; }
        public DbSet<Bar> Bar { get; set; }
    }
    class Program
    {

        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<Db>());

            using (var db = new Db())
            {
                db.Configuration.UseDatabaseNullSemantics = true;

                var q = from f in db.Foo
                        from b in db.Bar
                        where b.UID == f.UID.ToString()
                        select new { f, b };
                Console.WriteLine(q.ToString());


                Console.ReadKey();

            }
        }
    }
}

输出

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[UID] AS [UID],
    [Extent2].[Id] AS [Id1],
    [Extent2].[Name] AS [Name1],
    [Extent2].[UID] AS [UID1]
    FROM  [dbo].[Foos] AS [Extent1]
    CROSS JOIN [dbo].[Bars] AS [Extent2]
    WHERE [Extent2].[UID] = (LOWER( CAST( [Extent1].[UID] AS nvarchar(max))))

答案 1 :(得分:1)

但是,您可以创建一个使用隐式转换并从EF运行的存储过程。