【问题标题】:How can I code an outer join using LINQ and EF6?如何使用 LINQ 和 EF6 编写外连接?
【发布时间】:2014-03-25 10:43:49
【问题描述】:

我有三个表 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(我假设 UserId == 1 在 Where 子句中):

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   

请注意,由于我收到的建议,这个问题已经发生了一些变化。现在有一个赏金我希望能得到一个我可以接受的快速答案。

【问题讨论】:

  • 为什么从 dbcontext 调用它很重要?
  • 我首先在我的应用程序中使用实体框架代码,并且我有可用的 dbcontext。我认为这是最好的方法。
  • 这对您来说很方便,但我不确定您的用户是否会关心。同样关于提供的答案,为什么您认为创建 sproc/view 而不仅仅是使用实体框架/linq 方法很重要?现在肯定有成千上万的网站解释如何从 EF 调用存储过程/视图。
  • @bENcr - 我无法找到如何使用 LINQ 执行此操作。实际上,我刚刚提出了另一个问题:stackoverflow.com/questions/22633565/… 我很难找到很多关于从 EF 调用存储过程的信息。
  • TestId 33 与结果 2 有什么关系?不应该是 75 岁吗?

标签: c# linq entity-framework


【解决方案1】:

如果您的 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 值可以' t 存储在不可为空的 .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();

【讨论】:

    【解决方案2】:
     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();
    

    第二个想法,可能这会更好。如果有任何匹配的或空的用户测试,这将为您提供考试、测试和用户测试

    【讨论】:

    • 非常感谢。我将在明天(这里几乎是午夜)对此进行测试,并在评论中添加更新。如果它是使用 LINQ 扩展方法语法编码的,你能告诉我它会是什么样子吗?
    • Melina - 查看 Resharper - 它可以将 linq 语句与扩展方法语法进行转换。
    【解决方案3】:

    【讨论】:

    • 我想获得一个服务器解决方案。这是一个一次性查询,所以我认为最好有一个视图或存储过程。我只是不知道该怎么做。
    • 你可以在db中创建一个表,通过sproc你可以删除这个表之前的所有数据并从sproc本身插入新数据,最后你可以使用db context从这个表中选择所有记录.
    • 这个语法是内连接的结果。
    【解决方案4】:

    这里是一个讨论链接,它显示了如何使用参数调用存储过程: How to use DbContext.Database.SqlQuery<TElement>(sql, params) with stored procedure? 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
    

    【讨论】:

      【解决方案5】:

      试试这个:

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

      【讨论】:

        猜你喜欢
        • 2014-09-29
        • 2014-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多