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','')
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
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). –
Cảm ơn các đề xuất nhưng đó là điều đầu tiên tôi làm. – Aleks