-1

私は SQL の経験が豊富ですが、LINQ にはかなり慣れていないため、次の MySQL クエリを LINQ に変換するのに苦労しています。Entityフレームワークを使用したASP.net MVCプロジェクトで使用するために、次のものをLINQに変換するのを手伝ってくれる人はいますか?

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 のままにしておくと、コンパイラがすべてをまとめて 1 つのクエリとしてデータベースに送信します (つまり、最後のステップまで ToList() を実行しないでください)。

個人的には、join 演算子を使用するよりも、2 つの 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());
    }

明らかに、テストの条件定数をさまざまなアイテムに適用するように変更できます。私は契約に一致することを選択しました。テーブルの 1 つだけが一致すると仮定します。

このクエリは、次の 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

デイブ・ジョンソンのコメントに一言で答える-スケーラビリティ。

最近、私はアプリケーションのパフォーマンスを改善しようとしていました。最初に考えたのは、ジョン・ヘンリーのサンプルと同様の複雑さのSQL(複数の結合とフィルター)を追加することでした。結局のところ、それは私の開発マシンでロケットのように機能しました。

アーキテクトは、数百人のユーザーを抱えるいくつかの大規模なアプリケーションがデータベースサーバーに接続されていることに基づいて、データベースサーバーでの複雑なSQLの使用を全面的に禁止しました。素晴らしいSQLを構築するのが好きなのと同じように、私は同意しなければなりませんでした。ロジックをそれを消費するマシンにシフトすることは良いアーキテクチャです。

したがって、宣言型SQLに精通している私たちにとって、linqスキルへの翻訳を学ぶことは重要です。

もちろん、同じSQLがサーバーに送信されるため、以前に提供したソリューションではこれを実現できません。しかし、linqと同等のものを持つことは、さらに最適化できる開始点です。

于 2013-03-07T12:30:24.230 に答える
0

さまざまな記事を非常に多く検索して読んだ後、このクエリを LINQ クエリ構文で記述しようとするのをあきらめ、代わりにメソッド構文を使用しました。

複雑な SQL から LINQ への変換に関する提案とサポートを提供してくれた Ackroydd に心から感謝します。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 に答える