簡體   English   中英

如何使用LINQ和EF6編寫外連接?

[英]How can I code an outer join using LINQ and EF6?

我有三個表Exam,Test和UserTest。

CREATE TABLE [dbo].[Exam] (
    [ExamId]                      INT            IDENTITY (1, 1) NOT NULL,
    [SubjectId]                   INT            NOT NULL,
    [Name]                        NVARCHAR (50)  NOT NULL,
    [Description]                 NVARCHAR (MAX) NOT NULL,
    CONSTRAINT [PK_Exam] PRIMARY KEY CLUSTERED ([ExamId] ASC),
    CONSTRAINT [FK_ExamSubject] FOREIGN KEY ([SubjectId]) REFERENCES [dbo].[Subject] ([SubjectId]),
    CONSTRAINT [FK_Exam_ExamType] FOREIGN KEY ([ExamTypeId]) REFERENCES [dbo].[ExamType] ([ExamTypeId])
);

CREATE TABLE [dbo].[Test] (
    [TestId]      INT            IDENTITY (1, 1) NOT NULL,
    [ExamId]      INT            NOT NULL,
    [Title]       NVARCHAR (100) NULL,
    [Status]      INT            NOT NULL,
    [CreatedDate] DATETIME       NOT NULL,
    CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([TestId] ASC),
    CONSTRAINT [FK_TestExam] FOREIGN KEY ([ExamId]) REFERENCES [dbo].[Exam] ([ExamId])
);

CREATE TABLE [dbo].[UserTest] (
    [UserTestId]              INT            IDENTITY (1, 1) NOT NULL,
    [UserId]                  NVARCHAR (128) NOT NULL,
    [TestId]                  INT            NOT NULL,
    [Result]                  INT            NULL
    CONSTRAINT [PK_UserTest] PRIMARY KEY CLUSTERED ([UserTestId] ASC),
    CONSTRAINT [FK_UserTestTest] FOREIGN KEY ([TestId]) REFERENCES [dbo].[Test] ([TestId])
);

考試可以進行多次測試,用戶可以多次嘗試任何測試。

如何使用擴展方法語法對LINQ語句進行編碼,該語法允許我查看UserId == 1的以下內容(我假設Where子句中的UserId == 1):

Exam       Test      Title           UserTestID  UserId     Result
1          1         1a               1           1          20 
1          1         1a               2           1          30
1          1         1a               3           1          40         
1          2         1b               4           1          98 
1          3         1c               5           1          44
2          4         2a
2          5         2b               6           1          12

或者如果UserId == 2:

Exam       Test      Title           UserTestID  UserId     Result
1          1         1a               7           2          27  
1          2         1b        
1          3         1c               8           2          45
2          4         2a
2          5         2b        

或者如果UserId為null

Exam       Test      Title           UserTestID  UserId     Result
1          1         1a        
1          2         1b
1          3         1c  
2          4         2a
2          5         2b   

請注意,由於我收到的建議,這個問題已經發生了一些變化。 現在有一個賞金,我希望我能接受的快速回答。

如果您的Test實體具有UserTests集合,則可以使用此查詢:

string userId = "1";
var result = context.Tests
    .SelectMany(t => t.UserTests
        .Where(ut => ut.UserId == userId)
        .DefaultIfEmpty()
        .Select(ut => new
        {
            ExamId = t.ExamId,
            TestId = t.TestId,
            Title = t.Title,
            UserTestId = (int?)ut.UserTestId,
            UserId = ut.UserId,
            Result = ut.Result
        }))
    .OrderBy(x => x.ExamId)
    .ThenBy(x => x.TestId)
    .ThenBy(x => x.UserTestId)
    .ToList();

在此處使用DefaultIfEmpty()可確保LEFT OUTER JOIN以便您始終為給定的Test提供至少一個UserTest實體(可能為null )。 UserTest的非可空屬性(如UserTestId )轉換為可空類型(例如int? )非常重要,否則您可以獲得一個異常,即從數據庫返回的NULL值不能存儲在非可空的.NET類型。

如果你沒有,不想一個UserTests在你收集Test實體可以使用GroupJoin它們基本上將左外連接由兩個表的替代TestId

string userId = "1";
var result = context.Tests
    .GroupJoin(context.UserTests.Where(ut => ut.UserId == userId),
        t => t.TestId,
        ut => ut.TestId,
        (t, utCollection) => new
        {
            Test = t,
            UserTests = utCollection
        })
    .SelectMany(x => x.UserTests
        .DefaultIfEmpty()
        .Select(ut => new
        {
            ExamId = x.Test.ExamId,
            TestId = x.Test.TestId,
            Title = x.Test.Title,
            UserTestId = (int?)ut.UserTestId,
            UserId = ut.UserId,
            Result = ut.Result
        }))
    .OrderBy(x => x.ExamId)
    .ThenBy(x => x.TestId)
    .ThenBy(x => x.UserTestId)
    .ToList();
 var tests = (from t in context.Tests
       // where !t.UsertTests.Any() //if no user took the test
         //    || t.UserTests.Any(ut=>ut.Student.StudentId == stId)
        select new {Test = t, Exam = t.Exam, 
                 UserTests = t.UserTests.Where(ut=>ut.Student.StudentId == stId))
       .ToList();

第二個想法,可能會更好。 如果有任何匹配的或空的用戶,這將為您提供考試,測試和使用習慣

以下是討論的鏈接,該討論顯示了如何使用參數調用存儲過程: 如何在存儲過程中使用DbContext.Database.SqlQuery <TElement>(sql,params)? EF Code First CTP5

以下是編寫存儲過程的一種方法:

CREATE PROCEDURE dbo.sample1 (
@oneId NVARCHAR(128) = N'xx') AS
BEGIN
SET NOCOUNT ON;

SELECT @oneId AS userId,
    r.TestId, 
    r.Result
FROM (
    SELECT t.UserId, e.testId, t.Result
    FROM dbo.UserTest AS e
    LEFT OUTER JOIN dbo.UserTest AS t ON e.TestId = t.TestId AND t.UserId = @oneId
    WHERE  e.UserId = 0) AS r 
ORDER BY r.TestId 

END 
go

試試這個:

var tests = context.Tests.Include( "Exam" )
    .Select( t => new
    {
        Test = t,
        UserTests = t.UserTests.Where( ut => ut.UserId == studentId )
    } )
    .ToList();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM