web-dev-qa-db-ja.com

エンティティフレームワークを使用したサブクエリ

サブシステムをNHibernateからEntity Frameworkに移植していて、次のクエリを[〜#〜] ef [〜#〜]

var date = DateTime.Now; // It can be any day
AccountBalanceByDate abbd = null;
var lastBalanceDateByAccountQuery = QueryOver.Of<AccountBalanceByDate>()
    .Where(x => x.AccountId == abbd.AccountId && x.Date < date)
    .Select(Projections.Max<AccountBalanceByDate>(x => x.Date));

var lastBalances = session.QueryOver<AccountBalanceByDate>(() => abbd)
    .WithSubquery.WhereProperty(x => x.Date).Eq(lastBalanceDateByAccountQuery)
    .List();

勘定残高クラスは次のとおりです。

public class AccountBalanceByDate
{
    public virtual int Id { get; set; }
    public virtual int AccountId { get; set; }
    public virtual DateTime Date { get; set; }
    public virtual decimal Balance { get; set; }
}

テーブルは次のとおりです。

CREATE TABLE [dbo].[AccountBalanceByDate]
(
    [Id]        int NOT NULL,
    [AccountId] int NOT NULL,
    [Date]      [datetime] NOT NULL,
    [Balance]   [decimal](19, 5) NOT NULL,

    PRIMARY KEY CLUSTERED 
    (
        [Id] ASC
    )
)

サンプルデータは次のとおりです(理解を深めるために数値IDを使用しています)。

Id | Date        | Account | Balance
------------------------------------
 1 | 2014-02-01  | 101     | 1390.00000
 2 | 2014-02-01  | 102     | 1360.00000
 3 | 2014-02-01  | 103     | 1630.00000
 4 | 2014-02-02  | 102     | 1370.00000
 5 | 2014-02-02  | 103     | 1700.00000
 6 | 2014-02-03  | 101     | 1490.00000
 7 | 2014-02-03  | 103     | 1760.00000
 8 | 2014-02-04  | 101     | 1530.00000
 9 | 2014-02-04  | 102     | 1540.00000

AccountBalanceByDateエンティティは、特定の日の口座残高を保持します。日にトランザクションがない場合、その日にはAccountBalanceByDateがないため、前の日を探してそのアカウントの残高を確認する必要があります。

日付2014-02-01でクエリすると、次のようになります。

 No results

日付2014-02-02でクエリすると、次のようになります。

 1 | 2014-02-01  | 101     | 1390.00000
 2 | 2014-02-01  | 102     | 1360.00000
 3 | 2014-02-01  | 103     | 1630.00000

日付2014-02-03でクエリすると、次のようになります。

 1 | 2014-02-01  | 101     | 1390.00000
 4 | 2014-02-02  | 102     | 1370.00000
 5 | 2014-02-02  | 103     | 1700.00000

日付2014-02-04でクエリすると、次のようになります。

 4 | 2014-02-02  | 102     | 1370.00000
 6 | 2014-02-03  | 101     | 1490.00000
 7 | 2014-02-03  | 103     | 1760.00000

日付2014-02-05でクエリすると、次のようになります。

 7 | 2014-02-03  | 103     | 1760.00000
 8 | 2014-02-04  | 101     | 1530.00000
 9 | 2014-02-04  | 102     | 1540.00000

Entity Frameworkで生のSQLを使用してこれを行うことができますが、それは理想的ではありません。

using (var context = new DbContext()) 
{ 
    var lastBalances = context.AccountBalanceByDate.SqlQuery(
        @"SELECT
            *
        FROM 
            [AccountBalanceByDate] AB
        WHERE
            DATE = (
                SELECT
                    MAX(Date) 
                FROM 
                    [AccountBalanceByDate]
                WHERE
                    AccountId = AB.AccountId AND DATE < @p0
            )", date).ToList(); 
}

NHibernateやraw SQLのように1回だけデータベースにアクセスすることをお勧めしますが、linqだけを使用することも可能です?

UPDATE:

質問の結果を修正しました。

Gistのサンプルクエリを示すSQL: https://Gist.github.com/LawfulHacker/275ec363070f2513b887

Gistのエンティティフレームワークサンプル: https://Gist.github.com/LawfulHacker/9f7bd31a21363ee0b646

11
LawfulHacker

次のクエリは、データベースに対するクエリを1つだけ実行することで、必要な処理を正確に実行します。

var accountBalance = context
    .AccountBalanceByDate
    .Where(a => 
        a.Date == context.AccountBalanceByDate
             .Where(b => b.AccountId == a.AccountId && b.Date < date).Max(b => b.Date));

助けてくれて@AgentSharkに感謝します。

コードはGistにあります: https://Gist.github.com/LawfulHacker/9f7bd31a21363ee0b646

22
LawfulHacker

最後に、ソリューション。 :)

var date = DateTime.Now; // It can be any day
var lastBalances = (from a in context.AccountBalanceByDate
        where a.Date < date
        group a by new {a.AccountId} into g
        select g.OrderByDescending(a => a.Date).FirstOrDefault() into r
        select new
        {
            Id = r.Id,
            AccountId = r.AccountId,
            Date = r.Date,
            Balance = r.Balance
        }).ToList();

あなたはLINQでそれを望んでいましたが、個人的には、保守性のためにSQLを保持したかもしれません。

3
Agent Shark