2016-05-19 13 views
0

Entity-Framework Guruの素晴らしいパズルがここにあります!Entity Framework:既存のレコードに価値を追加する、またはレコードを挿入する

私は顧客のコレクションを持っています。各顧客にはいくらかの経費があります。私は一日あたりの費用の合計を覚えておく必要があります。

は、エンティティフレームワークでは、これは非常に簡単です:

public class Customer 
{ 
    public int CustomerId {get; set;} 
    public string Name {get; set;} // and other name/address fields 

    public ICollection<Expense> ExpenseHistory{get; set;} 
} 

public class Expense 
{ 
    public int ExpenseId {get; set;} 
    public int CustomerId {get; set;} // foreign key to customer 
    public Customer {get; set;} 

    public DateTime Date {get; set;} 
    public decimal Total {get; set;}  // total expenses on date 
} 

public class ExpenseContext : DbContext 
{ 
    public DbSet<Customer> Customers {get; set;} 
    public DbSet<Expense> Expenses {get; set;} 
} 

は、私は組み合わせ(Expense.CustomerId、Expense.Date)が一意であることを確信しています:私は、顧客ごとに経費の合計を持っているすべての日付のために。だから私はExpenseIdではなく、この組み合わせを主キーとして使うことができました。しかしそれは別の議論です。

プロセスからメッセージの列挙が送信されました。「この顧客IDを持つ顧客はちょうど日付の日付の金額を費やしました」

お客様のプロセスは、お客様が既にDateに何かを費やしたかどうかを確認する必要があります。

  • そうでない場合は、CustomerIdを使用してCustomerの経費明細をDateに作成します。 Totalに値を割り当てます。
  • は、顧客はすでに、それは1つのクエリでこれを行うことが可能です合計

に値を追加し、日付に何かを費やしたか、私は各費用の有無をチェックし、いずれかの費用値を追加しなければならないのならばまたはそれを作成しますか?それとは別に、後者は遅いです、競争条件の可能性があります:私はレコードが存在しないことを知りましたが、他の誰かがそれを作成した可能性があります。または、既存のレコードに値を追加している間に、他の誰かが既に既存のレコードの値を増やしている可能性があります。

最後の方法は次のようになります:上記

public void ProcessExpenses(IEnumerable<Expense> expenses) 
{ 
    using (var dbContext = ...) 
    { 
     foreach (var expense in expenses) 
     { 
      var existingExpense = dbContext.Expenses.SingleOrDefault(item => 
       item.CustomerId == expense.CustomerId && 
       item.Date == expense.Date); 
      if (existingExpense != null) 
      { // already something spent on Date. Add the value: 
       existingExpense.Total += expense.Total; 
      } 
      else 
      { // nothing spent by customerId on Date yet. Add expense: 
       dbContext.Add(expense); 
      } 
      dbContext.SaveChanges(); 
     } 
    } 
} 

ある簡素化ビットは、可算は実際に費やさはcustomerId /日付/値が含まれている別のクラスですが、要点を取得。

答えて

1

あなたはEF 6でそれを手に入れることはできません。MS SQLを使用していると仮定すると、このクエリを使用して必要なものを達成できます。あなたが他のSQL方言を使用する場合は、結果の実体を必要としない場合

var result = ((IObjectContextAdapter)ExpenseContext).ObjectContext.ExecuteStoreQuery<Expense>(
        @" MERGE [Expense] AS target 
       USING (select @CustomerId as CustomerId, @Date as Date, @Total as Total) AS source 
       ON (target.CustomerId = source.CustomerId and target.Date = source.Date) 
       WHEN MATCHED THEN 
       UPDATE 
       SET Total = Total+source.Total 
       WHEN NOT MATCHED THEN 
       INSERT (CustomerId, Date, Total) 
       VALUES (source.CustomerId, source.Date, source.Total) 
       OUTPUT inserted.CustomerId, inserted.Date, inserted.Total;", 
        new SqlParameter("@CustomerId", SqlDbType.Int) { Value = ..}, 
        new SqlParameter("@Date", SqlDbType.Date) { Value = ..}, 
        new SqlParameter("@Total", SqlDbType.Decimal) { Value = ..} 
        ); 

、あなたが

ExpenseContext.Database.ExecuteSqlCommand("MERGE ...", new SqlParameter(....) 

でこの呼び出しを置き換えることができ、UPSERTコマンドまたはMERGEのようなものを確認してください。

また、DATE型の値を.NETコードからSQLに渡して比較する場合は、TIME型がないことを確認してください。 .NETでは、TIME部分を持つDateTimeとして表現されるのはpublic DateTime Date {get; set;}です。

+0

私はすでにSQL Mergeを作成する必要があると思います。しかし私は自分のパラメータからソーステーブルを作る方法を知らなかった。私に見せてくれてありがとう。私が知る必要があることは、このためのストアドプロシージャを作成し、流暢なAPIを使用してストアドプロシージャを作成する方法を調べる方が良いかどうかを確認することです。 –

+0

流暢なAPIを使用してストアドプロシージャを作成することはできません。あなたのSQLサーバー。あなたから電話するネットコードでは、 'ExecuteSqlCommand'や' ExecuteStoreQuery 'などのメソッドの1つを使用する必要があります。 –

+0

セルゲイに感謝します。私は、DbMigration.UP()プロシージャでストアドプロシージャを作成します。詳細については、http://www.dotnetodyssey.com/2015/03/12/calling-stored-procedure-from-entity-framework-6-code-最初/ –

関連する問題