2014-12-02 14 views
12

Tôi đang làm một tham gia vào hai hàm SQL bằng cách sử dụng Entity Framework như ORM của tôi. Khi truy vấn được thực hiện tôi nhận được thông báo lỗi này:LINQ bên trái tham gia bên ngoài lỗi truy vấn: OuterApply đã không có các phím thích hợp

The query attempted to call 'Outer Apply' over a nested query, 
but 'OuterApply' did not have the appropriate keys 

Đây là câu hỏi của tôi:

var ingredientAllergenData = (from ings in db.fnListIngredientsFromItem(productId, (short)itemType, productId) 
          join ingAllergens in db.fnListAllergensFromItems(productId.ToString(CultureInfo.InvariantCulture), (short)itemType, currentLang) 
          on ings.id equals ingAllergens.ingredientId into ingAllergensData 
          from allergens in ingAllergensData.DefaultIfEmpty() 
          where ings.table == "tblIng" || ings.table == "" 
          select new {ings, allergens}).ToList(); 

tôi đã viết cùng một truy vấn trong LINQPad và tôi đã nhận lại kết quả, vì vậy tôi không chắc chắn những gì vấn đề là:

var ingredientAllergenData = (from ings in fnListIngredientsFromItem(1232, 0, 1232) 
          join ingAllergens in fnListAllergensFromItems("1232", 0, 1) 
          on ings.Id equals ingAllergens.IngredientId into ingAllergensData 
          from allergens in ingAllergensData.DefaultIfEmpty() 
          where ings.Table == "tblIng" || ings.Table == "" 
          select new {ings, allergens}).ToList(); 

Câu trả lời từ LINQPad: enter image description here

EDIT Đây là truy vấn SQL được tạo ra trong LINQPad:

-- Region Parameters 
    DECLARE @p0 Int = 1232 
    DECLARE @p1 Int = 0 
    DECLARE @p2 Int = 1232 
    DECLARE @p3 VarChar(1000) = '1232' 
    DECLARE @p4 SmallInt = 0 
    DECLARE @p5 Int = 1 
    DECLARE @p6 VarChar(1000) = 'tblIng' 
    DECLARE @p7 VarChar(1000) = '' 
    -- EndRegion 
    SELECT [t0].[prodId] AS [ProdId], [t0].[id] AS [Id], [t0].[parent] AS [Parent], [t0].[name] AS [Name], [t0].[ing_gtin] AS [Ing_gtin], [t0].[ing_artsup] AS [Ing_artsup], [t0].[table] AS [Table], [t0].[quantity] AS [Quantity], [t2].[test], [t2].[prodId] AS [ProdId2], [t2].[ingredientId] AS [IngredientId], [t2].[allergenId] AS [AllergenId], [t2].[allergenName] AS [AllergenName], [t2].[level_of_containment] AS [Level_of_containment] 
    FROM [dbo].[fnListIngredientsFromItem](@p0, @p1, @p2) AS [t0] 
    LEFT OUTER JOIN (
     SELECT 1 AS [test], [t1].[prodId], [t1].[ingredientId], [t1].[allergenId], [t1].[allergenName], [t1].[level_of_containment] 
     FROM [dbo].[fnListAllergensFromItems](@p3, @p4, @p5) AS [t1] 
     ) AS [t2] ON [t0].[id] = ([t2].[ingredientId]) 
    WHERE ([t0].[table] = @p6) OR ([t0].[table] = @p7) 

Tôi cũng đã cố gắng thể xác định rõ số tương tự vào C# và đã nhận lỗi cùng một lần nữa.

+0

tôi sẽ bắt đầu bằng cách cố gắng làm tất cả các chuyển đổi (đúc để ngắn, ToString cuộc gọi, vv) ra khỏi các truy vấn và gán chúng cho các biến và sử dụng chúng trong truy vấn. Có vẻ như LINQ có thể bị nghẹt thở khi cố gắng thực hiện chuyển đổi một phần của truy vấn. – Becuzz

+0

Bạn nên thử chính xác cùng một truy vấn trong Linqpad (sử dụng các biến). –

+0

Cảm ơn các đề xuất nhưng đó là điều đầu tiên tôi làm. – Aleks

Trả lời

10

Vấn đề là khung thực thể cần biết các cột khóa chính của kết quả TVF là thực hiện một phép nối trái và tệp EDMX được tạo mặc định không chứa thông tin đó. Bạn có thể thêm thông tin giá trị khóa bằng cách ánh xạ kết quả TVF tới một thực thể (thay vì mặc định ánh xạ tới một kiểu phức).

Lý do truy vấn tương tự hoạt động trong LINQPad là trình điều khiển ngữ cảnh dữ liệu mặc định để kết nối với cơ sở dữ liệu trong LINQPad sử dụng LINQ to SQL (không phải khung thực thể). Nhưng tôi đã có thể nhận được truy vấn để chạy trong Entity Framework (cuối cùng).

tôi thiết lập một địa phương SQL cơ sở dữ liệu máy chủ tương tự như chức năng bảng giá trị:

CREATE FUNCTION fnListIngredientsFromItem(@prodId int, @itemType1 smallint, @parent int) 
RETURNS TABLE 
AS 
RETURN (
    select prodId = 1232, id = 1827, parent = 1232, name = 'Ossenhaaspunten', ing_gtin =089821, ing_artsup=141020, [table] = 'tblIng', quantity = '2 K' 
); 
go 
CREATE FUNCTION fnListAllergensFromItems(@prodIdString varchar(1000), @itemType2 smallint, @lang int) 
RETURNS TABLE 
AS 
RETURN (
    select prodId = '1232', ingredientId = 1827, allergenId = 11, allergenName = 'fish', level_of_containment = 2 
    union all 
    select prodId = '1232', ingredientId = 1827, allergenId = 16, allergenName = 'tree nuts', level_of_containment = 2 
    union all 
    select prodId = '1232', ingredientId = 1827, allergenId = 12, allergenName = 'crustacean and shellfish', level_of_containment = 2 
); 
go 

Và tôi đã tạo ra một dự án thử nghiệm sử dụng Entity Framework 6.1.2 và tạo ra một tập tin EDMX từ cơ sở dữ liệu bằng cách sử dụng mô hình Entity Data nhà thiết kế trong Visual Studio 2013. với thiết lập này, tôi đã có thể để có được những lỗi tương tự khi cố gắng chạy truy vấn:

System.NotSupportedException 
    HResult=-2146233067 
    Message=The query attempted to call 'OuterApply' over a nested query, but 'OuterApply' did not have the appropriate keys. 
    Source=EntityFramework 
    StackTrace: 
     at System.Data.Entity.Core.Query.PlanCompiler.NestPullup.ApplyOpJoinOp(Op op, Node n) 
     at System.Data.Entity.Core.Query.PlanCompiler.NestPullup.VisitApplyOp(ApplyBaseOp op, Node n) 
     at System.Data.Entity.Core.Query.InternalTrees.BasicOpVisitorOfT`1.Visit(OuterApplyOp op, Node n) 
     ... 

chạy một biểu thức thay thế cho một trái join dẫn đến một lỗi hơi khác nhau:

var ingredientAllergenData = (db.fnListIngredientsFromItem(1323, (short)0, 1) 
    .GroupJoin(db.fnListAllergensFromItems("1232", 0, 1), 
     ing => ing.id, 
     allergen => allergen.ingredientId, 
     (ing, allergen) => new { ing, allergen } 
    ) 
).ToList(); 

Đây là một stacktrace cắt ngắn từ ngoại lệ mới:

System.NotSupportedException 
    HResult=-2146233067 
    Message=The nested query does not have the appropriate keys. 
    Source=EntityFramework 
    StackTrace: 
     at System.Data.Entity.Core.Query.PlanCompiler.NestPullup.ConvertToSingleStreamNest(Node nestNode, Dictionary`2 varRefReplacementMap, VarList flattenedOutputVarList, SimpleColumnMap[]& parentKeyColumnMaps) 
     at System.Data.Entity.Core.Query.PlanCompiler.NestPullup.Visit(PhysicalProjectOp op, Node n) 
     at System.Data.Entity.Core.Query.InternalTrees.PhysicalProjectOp.Accept[TResultType](BasicOpVisitorOfT`1 v, Node n) 
     ... 

Entity Framework là mã nguồn mở, vì vậy chúng tôi thực sự có thể nhìn vào mã nguồn nơi ngoại lệ này được ném. Các ý kiến ​​trong đoạn này giải thích những gì vấn đề là (https://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Core/Query/PlanCompiler/NestPullup.cs):

// Make sure that the driving node has keys defined. Otherwise we're in 
// trouble; we must be able to infer keys from the driving node. 
var drivingNode = nestNode.Child0; 
var drivingNodeKeys = Command.PullupKeys(drivingNode); 
if (drivingNodeKeys.NoKeys) 
{ 
    // ALMINEEV: In this case we used to wrap drivingNode into a projection that would also project Edm.NewGuid() thus giving us a synthetic key. 
    // This solution did not work however due to a bug in SQL Server that allowed pulling non-deterministic functions above joins and applies, thus 
    // producing incorrect results. SQL Server bug was filed in "sqlbuvsts01\Sql Server" database as #725272. 
    // The only known path how we can get a keyless drivingNode is if 
    // - drivingNode is over a TVF call 
    // - TVF is declared as Collection(Row) is SSDL (the only form of TVF definitions at the moment) 
    // - TVF is not mapped to entities 
    //  Note that if TVF is mapped to entities via function import mapping, and the user query is actually the call of the 
    //  function import, we infer keys for the TVF from the c-space entity keys and their mappings. 
    throw new NotSupportedException(Strings.ADP_KeysRequiredForNesting); 
} 

Điều đó giải thích những con đường dẫn đến lỗi, vì vậy bất cứ điều gì chúng ta có thể làm gì để thoát khỏi con đường nên khắc phục vấn đề. Giả sử chúng ta phải làm điều đó trái tham gia vào các kết quả của một hàm có giá trị bảng, một tùy chọn (có thể là tùy chọn duy nhất?) Là ánh xạ các kết quả của TVF tới một thực thể có khóa chính. Sau đó, Entity Framework sẽ biết các giá trị chính của kết quả TVF dựa trên ánh xạ tới thực thể đó và chúng ta nên tránh các lỗi này liên quan đến các khóa bị thiếu.

Theo mặc định khi tạo tệp EDMX từ cơ sở dữ liệu, TVF được ánh xạ tới một loại phức tạp. Có hướng dẫn về cách thay đổi nó tại https://msdn.microsoft.com/en-us/library/vstudio/ee534438%28v=vs.100%29.aspx. Trong dự án thử nghiệm của tôi, tôi đã thêm một bảng trống với lược đồ khớp với đầu ra của TVF để có được trình thiết kế mô hình để tạo Entities, sau đó tôi đã đi đến trình duyệt mô hình và cập nhật hàm import để trả về một tập hợp các thực thể này (thay vì các kiểu phức tạp được tạo tự động). Sau khi thực hiện những thay đổi này, truy vấn LINQ đó chạy không có lỗi.

var ingredientAllergenData = (from ings in db.fnListIngredientsFromItem(productId, (short)itemType, productId) 
          join ingAllergens in db.fnListAllergensFromItems(productId.ToString(CultureInfo.InvariantCulture), (short)itemType, currentLang) 
          on ings.id equals ingAllergens.ingredientId into ingAllergensData 
          from allergens in ingAllergensData.DefaultIfEmpty() 
          where ings.table == "tblIng" || ings.table == "" 
          select new {ings, allergens}).ToList(); 

Đây là dấu vết SQL truy vấn đã cho tôi:

SELECT 
    1 AS [C1], 
    [Extent1].[prodId] AS [prodId], 
    [Extent1].[id] AS [id], 
    [Extent1].[parent] AS [parent], 
    [Extent1].[name] AS [name], 
    [Extent1].[ing_gtin] AS [ing_gtin], 
    [Extent1].[ing_artsup] AS [ing_artsup], 
    [Extent1].[table] AS [table], 
    [Extent1].[quantity] AS [quantity], 
    [Extent2].[prodId] AS [prodId1], 
    [Extent2].[ingredientId] AS [ingredientId], 
    [Extent2].[allergenId] AS [allergenId], 
    [Extent2].[allergenName] AS [allergenName], 
    [Extent2].[level_of_containment] AS [level_of_containment] 
    FROM [dbo].[fnListIngredientsFromItem](@prodId, @itemType1, @parent) AS [Extent1] 
    LEFT OUTER JOIN [dbo].[fnListAllergensFromItems](@prodIdString, @itemType2, @lang) AS [Extent2] ON ([Extent1].[id] = [Extent2].[ingredientId]) OR (([Extent1].[id] IS NULL) AND ([Extent2].[ingredientId] IS NULL)) 
    WHERE [Extent1].[table] IN ('tblIng','') 
+0

"Làm tốt Watson thân yêu của tôi!" Tôi tự hỏi nếu một cách khác để thoát khỏi ngoại lệ là thêm một 'OrderBy':' db.fnListIngredientsFromItem (productId, (ngắn) itemType, productId) .OrderBy (x => x.ProdId) '. Xem [this] (http://stackoverflow.com/a/27206566/861716). –

+0

Câu trả lời tuyệt vời nó đã cho tôi ý tưởng để giải quyết một vấn đề khác. – rodpl

+0

Giải thích tuyệt vời! Trong trường hợp của tôi, tôi đã nhóm các kết quả của TVF và sau đó thực hiện 'where g.Count() == numSearchTerms || g.FirstOrDefault(). SearchTerm == null'. Đọc này, tôi có ý tưởng để thử 'where g.Count() == numSearchTerms || g.Any (x => x.SearchTerm == null) 'thay vào đó và nó hoạt động! – adam0101

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