-1

我有丰富的 SQL 经验,但对 LINQ 还很陌生,并且正在努力将以下 MySQL 查询转换为 LINQ。任何人都可以帮助将以下内容转换为 LINQ 以在具有实体框架的 ASP.net MVC 项目中使用吗?

SELECT
    S.Submission_ID,
    P.Photo_ID,
    C2.Contract_Name,
    J.Job_Number,
    D.Device_Name,
    A.`Display_Name`,
    S.Submission_Status,
    S.Submission_JobRef,
    S.Created,
    TRUE
FROM
    Submission S
        LEFT JOIN Job J ON S.`Job_ID` = J.`Job_ID`
        LEFT JOIN Contract C2 ON J.`Contract_ID` = C2.`Contract_ID`
        INNER JOIN Submission_Status SS ON S.`Submission_Status` = SS.`ID`
        INNER JOIN Device D ON S.`Device_ID` = D.`Device_ID`
        INNER JOIN ACTION A ON S.`Action_ID` = A.`Action_ID`
        INNER JOIN (
            SELECT
                MIN(P.Photo_ID) AS Photo_ID,
                P.Submission_ID
            FROM
                Photo P
            GROUP BY
                P.`Submission_ID`) P ON S.`Submission_ID` = P.Submission_ID
WHERE
    S.`Submission_Status` <> 3 AND
    (LOCATE(@Criteria, C2.`Contract_Name`) > 0 OR
    LOCATE(@Criteria, J.`Job_Number`) > 0 OR
    LOCATE(@Criteria, D.`Device_Name`) > 0 OR
    LOCATE(@Criteria, A.`Display_Name`) > 0 OR
    LOCATE(@Criteria, SS.`Value`) > 0 OR
    LOCATE(@Criteria, S.`Submission_JobRef`) > 0)
ORDER BY
    S.`Submission_ID` DESC

我试图了解多个连接和子查询,但后来卡住了。这就是我到目前为止所拥有的......显然,它不工作或不完整!

Dim results = From S In db.Submissions
                      Join P In db.Photos On S.Submission_ID Equals P.Submission_ID
                      Group Join J In db.Jobs On S.Job_ID Equals J.Job_ID
                        Into Job = Group
                      Join J In db.Jobs On S.Job_ID Equals J.Job_ID
                      Group By P.Submission_ID
                        Into SubmissionPhotoID = Min(P.Photo_ID)
                      Select New With {.Submission_ID = Submission_ID,
                                       .Photo_ID = SubmissionPhotoID,
                                       .Contract_Name = If(IsNothing(S.Job), "", S.Job.Contract.Contract_Name),
                                       .Job_Number = If(IsNothing(S.Job), "", S.Job.Job_Number),
                                       .Device_Name = S.Device.Device_Name,
                                       .Action_Name = S.Action.Display_Name,
                                       .Submission_Status = S.Submission_Status1.ID,
                                       .Submission_JobRef = S.Submission_JobRef,
                                       .Created = S.Created,
                                       .CanEdit = bolCanEdit}
                      Order By S.Submission_ID
                      Skip param.iDisplayStart
                      Take param.iDisplayLength

任何有关上述内容的帮助或指导将不胜感激!


编辑

为了提供帮助,以下是模型中定义上述查询中使用的实体的类。(我省略了一些与问题无关的字段)。

Partial Public Class Submission
    Public Property Submission_ID As Integer
    Public Property Job_ID As Nullable(Of Integer)
    Public Property Device_ID As Integer
    Public Property Action_ID As Integer
    Public Property Submission_Status As Nullable(Of Integer)
    Public Property Submission_JobRef As String
    Public Property Created As Nullable(Of Date)

    Public Overridable Property Action As Action
    Public Overridable Property Device As Device
    Public Overridable Property Job As Job
    Public Overridable Property Photos As ICollection(Of Photo) = New HashSet(Of Photo)
    Public Overridable Property Submission_Status1 As Submission_Status
End Class

Partial Public Class Job
    Public Property Job_ID As Integer
    Public Property Contract_ID As Nullable(Of Integer)
    Public Property Job_Number As String

    Public Overridable Property Contract As Contract
    Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class

Partial Public Class Contract
    Public Property Contract_ID As Integer
    Public Property Contract_Name As String

    Public Overridable Property Jobs As ICollection(Of Job) = New HashSet(Of Job)
End Class

Partial Public Class Submission_Status
    Public Property ID As Integer
    Public Property Value As String

    Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)

End Class

Partial Public Class Device
    Public Property Device_ID As Integer
    Public Property Device_Name As String

    Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class

Partial Public Class Action
    Public Property Action_ID As Integer
    Public Property Display_Name As String

    Public Overridable Property Submissions As ICollection(Of Submission) = New HashSet(Of Submission)
End Class

Partial Public Class Photo
    Public Property Photo_ID As Integer
    Public Property Submission_ID As Integer

    Public Overridable Property Submission As Submission
End Class
4

3 回答 3

2

这是一段相当复杂的 SQL,带有子选择以及左连接和内连接的混合。一些快速的建议:

将其分解为一系列 linq 语句,从您的核心对象开始,并在后续步骤中添加相关部分。如果您将结果保留为 IQueryable,编译器将为您将所有结果放在一起并作为一个查询发送到数据库(即在最后一步之前不要 ToList())。

就个人而言,我使用两个 from 和一个 where 扩展方法进行连接,而不是使用连接运算符。一方面,我让您更容易知道您正在获得左连接或内连接。

例如:

FROM Submission S LEFT JOIN Job J ON S.`Job_ID` = J.`Job_ID`

我会这样做(对不起,我是 c#,所以语法对于 VB 可能不太正确)

Dim results = from s in db.Submissions
              from j in db.Jobs.Where(j=> j.Job_Id == s.Job_Id).DefaultIfEmpty()

因此,连接条件在 Jobs 的 .Where() 内,而 .DefaultIfEmpty() 告诉它左连接(本质上,如果连接失败,Job 将是默认值)。

进一步编辑:

经过实验,我得到了这个代码来返回一个结果(是不是正确的结果是另一个问题)。再次对 c# 语法感到抱歉。

    [TestMethod]
    public void Query()
    {
        const string conStr = "Data Source=(local);Initial Catalog=ComplexSqlToLinq; Integrated Security=True";
        var db = new MyDbContext(conStr);

        const string criteria = "Contract1";

        var minPhotos = from p in db.Photos
                        group p by p.SubmissionId
                        into g
                        select new {SubmissionId = g.Key, PhotoId = g.Min(p=>p.PhotoId)};

        var query = from s in db.Submissions
                    from j in db.Jobs.Where(j => j.JobId == s.JobId).DefaultIfEmpty()
                    from c in db.Contracts.Where(c => c.ContractId == j.ContractId).DefaultIfEmpty()
                    from ss in db.SubmissionStatuses.Where(ss => ss.Id == s.SubmissionStatus)
                    from d in db.Devices.Where(d => d.DeviceId == s.DeviceId)
                    from a in db.Actions.Where(a => a.ActionId == s.ActionId)
                    from p in minPhotos.Where(p => p.SubmissionId == s.SubmissionId)

                    where s.SubmissionStatus != 3 &&
                       ( c.ContractName.Contains(criteria) ||
                         j.JobNumber.Contains(criteria) ||
                         d.DeviceName.Contains(criteria) ||
                         a.DisplayName.Contains(criteria) ||
                         ss.Value.Contains(criteria) ||
                         s.SubmissionJobRef.Contains(criteria))

                    select new
                               {
                                   s.SubmissionId,
                                   p.PhotoId,
                                   c.ContractName,
                                   j.JobNumber,
                                   d.DeviceName,
                                   a.DisplayName,
                                   s.SubmissionStatus,
                                   s.SubmissionJobRef,
                                   s.Created,
                                   SomeBool = true
                               };

        var result = query.ToList();
        Assert.IsTrue(result.Any());
    }

显然,您可以改变测试中的标准常数以适用于不同的项目,我选择匹配合同 - 我假设只有一个表会匹配。

此查询生成以下 SQL,看起来有点做作,但在功能上与您的原始 SQL 非常相似。

SELECT 
    [Filter1].[SubmissionId] AS [SubmissionId], 
    [GroupBy1].[A1] AS [C1], 
    [Filter1].[ContractName] AS [ContractName], 
    [Filter1].[JobNumber] AS [JobNumber], 
    [Filter1].[DeviceName] AS [DeviceName], 
    [Filter1].[DisplayName] AS [DisplayName], 
    [Filter1].[SubmissionStatus] AS [SubmissionStatus], 
    [Filter1].[SubmissionJobRef] AS [SubmissionJobRef], 
    [Filter1].[Created] AS [Created], 
    cast(1 as bit) AS [C2]
FROM   
(
    SELECT 
        [Extent1].[SubmissionId] AS [SubmissionId], 
        [Extent1].[SubmissionStatus] AS [SubmissionStatus], 
        [Extent1].[SubmissionJobRef] AS [SubmissionJobRef], 
        [Extent1].[Created] AS [Created], 
        [Extent2].[JobNumber] AS [JobNumber], 
        [Extent3].[ContractName] AS [ContractName], 
        [Extent4].[Value] AS [Value], 
        [Extent5].[DeviceName] AS [DeviceName], 
        [Extent6].[DisplayName] AS [DisplayName]
    FROM      
        [dbo].[Submissions] AS [Extent1]
        LEFT OUTER JOIN [dbo].[Jobs] AS [Extent2] ON [Extent2].[JobId] = [Extent1].[JobId]
        LEFT OUTER JOIN [dbo].[Contracts] AS [Extent3] ON [Extent3].[ContractId] = [Extent2].[ContractId]
        INNER JOIN [dbo].[SubmissionStatus] AS [Extent4] ON [Extent4].[Id] = [Extent1].[SubmissionStatus]
        INNER JOIN [dbo].[Devices] AS [Extent5] ON [Extent5].[DeviceId] = [Extent1].[DeviceId]
        INNER JOIN [dbo].[Actions] AS [Extent6] ON [Extent6].[ActionId] = [Extent1].[ActionId]
    WHERE 
        3 <> [Extent1].[SubmissionStatus] 
) AS [Filter1]

INNER JOIN  (
    SELECT 
        [Extent7].[SubmissionId] AS [K1], 
        MIN([Extent7].[PhotoId]) AS [A1]
    FROM 
        [dbo].[Photos] AS [Extent7]
    GROUP BY 
        [Extent7].[SubmissionId] ) AS [GroupBy1] 
    ON [GroupBy1].[K1] = [Filter1].[SubmissionId]
WHERE 
(
    [Filter1].[ContractName] LIKE @p__linq__0 ESCAPE N'~') OR 
    ([Filter1].[JobNumber] LIKE @p__linq__1 ESCAPE N'~') OR 
    ([Filter1].[DeviceName] LIKE @p__linq__2 ESCAPE N'~') OR 
    ([Filter1].[DisplayName] LIKE @p__linq__3 ESCAPE N'~') OR 
    ([Filter1].[Value] LIKE @p__linq__4 ESCAPE N'~') OR 
    ([Filter1].[SubmissionJobRef] LIKE @p__linq__5 ESCAPE N'~')
)   
于 2013-03-06T17:05:28.867 回答
1

用一个词来回应 Dave Johnson 的评论 - 可扩展性。

最近我试图提高应用程序的性能,我的第一个想法是添加一些复杂性与 John Henry 的示例相似的 SQL - 多个连接和过滤器。毕竟,它在我的开发机器上表现得像火箭一样。

架构师断然禁止在数据库服务器上使用复杂的 SQL,因为它连接了几个拥有 100 多个用户的大型应用程序。尽管我很喜欢构建灵活的 SQL,但我不得不同意。将逻辑转移到使用它的机器上是很好的架构。

所以对于我们这些精通声明式 SQL 的人来说,学习翻译到 linq 技能很重要。

当然,我之前给出的解决方案并没有实现这一点,因为相同的 SQL 被发送到服务器。但是拥有一个 linq 等价物是一个可以进一步优化的开始。

于 2013-03-07T12:30:24.230 回答
0

经过大量搜索和阅读各种文章后,我放弃了尝试用 LINQ 查询语法编写此查询,而改用方法语法。

非常感谢 Ackroydd 在将复杂的 SQL 转换为 LINQ 方面的建议和支持。当您知道您可以在几分钟内用 SQL 完成某些事情,但需要使用 LINQ 来实现可伸缩性并与现有代码保持一致时,您会感到非常沮丧!

这是我最终得到的结果,因为我确信它对其他人有用:

Dim query As IQueryable(Of Submission)

' Initialise the new query
query = db.Submissions.Include(Function(s) s.Action) _
                              .Include(Function(s) s.Photos) _
                              .Include(Function(s) s.Device) _
                              .Include(Function(s) s.Job) _
                              .Include(Function(s) s.Submission_Status1) _
                              .Include(Function(s) s.Job.Contract) _
                              .Include(Function(s) s.Comments) _
                              .AsNoTracking

' Apply initial filters
query = query.Where(Function(S) Not S.Submission_Status1.ID.Equals(3))

' Apply search criteria if passed
If Not String.IsNullOrEmpty(param.sSearch) Then
    query = query.Where(Function(S) S.Job.Contract.Contract_Name.Contains(param.sSearch) OrElse
                                            S.Job.Job_Number.Contains(param.sSearch) OrElse
                                            S.Device.Device_Name.Contains(param.sSearch) OrElse
                                            S.Action.Display_Name.Contains(param.sSearch) OrElse
                                            S.Submission_Status1.Value.Contains(param.sSearch) OrElse
                                            S.Submission_JobRef.Contains(param.sSearch))
End If

' Order the results
query = query.OrderByDescending(Function(S) S.Submission_ID)

' Paginate the results
query = query.Skip(param.iDisplayStart).Take(param.iDisplayLength)

' Return only the required columns
Dim resultData = query.AsEnumerable.Select(Function(S) New AjaxSubmissionOverview With { _
                                                           .Submission_ID = S.Submission_ID,
                                                           .Photo_ID = S.Photos.First.Photo_ID,
                                                           .Contract_Name = If(IsNothing(S.Job), "", S.Job.Contract.Contract_Name),
                                                           .Job_Number = If(IsNothing(S.Job), "", S.Job.Job_Number),
                                                           .Device_Name = S.Device.Device_Name,
                                                           .Action_Name = S.Action.Display_Name,
                                                           .Submission_Status = S.Submission_Status,
                                                           .Submission_JobRef = S.Submission_JobRef,
                                                           .Latest_Comment = If(S.Comments.Count = 0, "", HtmlHelpers.Truncate(S.Comments.Last.Comment1, 100)),
                                                           .Created = S.Created,
                                                           .CanEdit = bolCanEdit})
于 2013-03-08T10:13:22.800 回答