2013-12-10 10 views
8

Sử dụng EntityFramework, khoản .OrderBy(x => x.Title.StartsWith("foo")) kết quả trong SQL WHERE (Title LIKE 'foo%' ESCAPE '~').SQL được tạo bởi EntityFramework StartsWith() chứa kế hoạch thay đổi ESCAPE '~' (dấu ngã)

Nhìn vào kế hoạch thực hiện cho truy vấn đầy đủ, tôi thấy rằng tôi nhận được một kế hoạch khác (một sử dụng chỉ mục không nhóm của cột) khi tôi xóa ESCAPE '~'.

Tại sao EF cố gắng thoát khỏi chuỗi không cần đến và làm thế nào tôi có thể dừng lại?

+0

Xin vui lòng gửi truy vấn đầy đủ chính xác như chặn bởi SQL Profiler. Có gì đó không đúng ở đây. Mệnh đề ESCAPE hợp lý không nên ảnh hưởng đến quyết định của trình tối ưu hóa. Tôi cũng không thể tái sản xuất: 'CHỌN * TỪ sys.objects WHERE name LIKE 'obj%' ESCAPE '~''. Btw, mệnh đề ESCAPE cho phép bạn thực thi StartsWith ("%") một cách chính xác. Nó là cần thiết. – usr

+0

@usr - [Nó có thể ảnh hưởng đến ước tính số lượng thẻ] (http://connect.microsoft.com/SQLServer/feedback/details/680257/optimization-problem-on-a-query-with-two-similar-like-clauses- một-với-một-biến) 'Chúng tôi không có hỗ trợ cho ước tính chính xác về thẻ trong sự hiện diện của các ký tự thoát được xác định bởi người dùng. Vì vậy, chúng tôi có thể nhận được một ước tính kém và một kế hoạch kém. Chúng tôi sẽ xem xét giải quyết vấn đề này trong bản phát hành trong tương lai.' –

+1

Câu hỏi cũng được trả lời tại đây: http://stackoverflow.com/questions/16935402/make-entity-framework-use-contains-instead-of-like-and- giải thích-thoát –

Trả lời

11

Số dư thừa ESCAPE chắc chắn có thể thay đổi ước tính thẻ và đưa ra một kế hoạch khác. Mặc dù đủ vui vẻ tôi thấy nó làm cho nó chính xác hơn là ít hơn trong bài kiểm tra này!

CREATE TABLE T 
(
Title VARCHAR(50), 
ID INT IDENTITY, 
Filler char(1) NULL, 
UNIQUE NONCLUSTERED (Title, ID) 
) 

INSERT INTO T 
      (Title) 
SELECT TOP 1000 CASE 
        WHEN ROW_NUMBER() OVER (ORDER BY @@SPID) < 10 THEN 'food' 
        ELSE LEFT(NEWID(), 10) 
       END 
FROM master..spt_values 

Without Escape

SELECT * 
FROM T 
WHERE (Title LIKE 'foo%') 

enter image description here

Với Escape

SELECT * 
FROM T 
WHERE (Title LIKE 'foo%' ESCAPE '~') 

enter image description here

Ngắn nâng cấp lên phiên bản EF mới hơn hoặc viết tùy chỉnh thực hiện DbProviderManifest tùy chỉnh của riêng tôi Tôi nghĩ rằng bạn không may mắn khi cố gắng xóa ESCAPE.

Dịch String.StartsWith, String.EndsWithString.Contains-LIKE hơn CHARINDEX được new in EF 4.0

Nhìn vào định nghĩa của System.Data.Entity, Version=4.0.0.0 trong phản xạ chức năng có liên quan có vẻ là (trong System.Data.SqlClient.SqlProviderManifest)

public override string EscapeLikeArgument(string argument) 
{ 
    bool flag; 
    EntityUtil.CheckArgumentNull<string>(argument, "argument"); 
    return EscapeLikeText(argument, true, out flag); 
} 

Chữ ký cho điều đó là

internal static string EscapeLikeText(string text, 
             bool alwaysEscapeEscapeChar, 
             out bool usedEscapeChar) 
{ 

    usedEscapeChar = false; 
    if (((!text.Contains("%") && !text.Contains("_")) && (!text.Contains("[") && !text.Contains("^"))) && (!alwaysEscapeEscapeChar || !text.Contains("~"))) 
    { 
     return text; 
    } 
    StringBuilder builder = new StringBuilder(text.Length); 
    foreach (char ch in text) 
    { 
     switch (ch) 
     { 
      case '%': 
      case '_': 
      case '[': 
      case '^': 
      case '~': 
       builder.Append('~'); 
       usedEscapeChar = true; 
       break; 
     } 
     builder.Append(ch); 
    } 
    return builder.ToString(); 
} 

Vì vậy, nó chỉ là hardcoded để luôn luôn sử dụng thoát và lá cờ được trả về là bỏ qua.

Vì vậy, phiên bản EF đó chỉ thêm ESCAPE '~' vào tất cả các truy vấn LIKE.

Điều này có vẻ là một cái gì đó đã được cải thiện trong cơ sở mã gần đây nhất.

Định nghĩa của SqlFunctionCallHandler.TranslateConstantParameterForLike

// <summary> 
    // Function to translate the StartsWith, EndsWith and Contains canonical functions to LIKE expression in T-SQL 
    // and also add the trailing ESCAPE '~' when escaping of the search string for the LIKE expression has occurred 
    // </summary> 
    private static void TranslateConstantParameterForLike(
     SqlGenerator sqlgen, DbExpression targetExpression, DbConstantExpression constSearchParamExpression, SqlBuilder result, 
     bool insertPercentStart, bool insertPercentEnd) 
    { 
     result.Append(targetExpression.Accept(sqlgen)); 
     result.Append(" LIKE "); 

     // If it's a DbConstantExpression then escape the search parameter if necessary. 
     bool escapingOccurred; 

     var searchParamBuilder = new StringBuilder(); 
     if (insertPercentStart) 
     { 
      searchParamBuilder.Append("%"); 
     } 
     searchParamBuilder.Append(
      SqlProviderManifest.EscapeLikeText(constSearchParamExpression.Value as string, false, out escapingOccurred)); 
     if (insertPercentEnd) 
     { 
      searchParamBuilder.Append("%"); 
     } 

     var escapedSearchParamExpression = constSearchParamExpression.ResultType.Constant(searchParamBuilder.ToString()); 
     result.Append(escapedSearchParamExpression.Accept(sqlgen)); 

     // If escaping did occur (special characters were found), then append the escape character used. 
     if (escapingOccurred) 
     { 
      result.Append(" ESCAPE '" + SqlProviderManifest.LikeEscapeChar + "'"); 
     } 
    } 

SqlProviderManifest.EscapeLikeText là mã tương tự như đã được hiển thị. Lưu ý rằng nó bây giờ vượt qua false làm thông số thứ hai và sử dụng cờ tham số đầu ra để chỉ chắp thêm ESCAPE nếu cần.

+0

Bạn có một blog, Martin? – usr

+0

@usr - Không. Tôi chỉ viết câu trả lời khá dài! –

+0

@MartinSmith, một lần nữa xin cảm ơn một câu trả lời tuyệt vời khác. – stovroz

1

Kể từ khung pháp nhân 6.2, có sự hỗ trợ bổ sung cho .Like() như một phần của DbFunctions.

Vì vậy, bây giờ bạn có thể làm điều này:

var query = db.People.Where(p => DbFunctions.Like(p.Name, "w%")); 

Để biết thêm thông: https://github.com/aspnet/EntityFramework6/issues/241

Các vấn đề liên quan