そこには
最初に EF コードを使用して、次のシーケンスに従ってデータを永続化しています: フィルター => 削除 => 追加、添付のサンプルを実行 (2 つのスレッドが同時に実行)、フィルタリング後に既存のレコードが返されないことに気付きました。フィルタリング後に2つのレコードがあります。私が考えた/期待したことは、フィルターが1つの既存のレコードを返す必要があるたびにです。また、変更の保存中に発生するいくつかの例外があります。
私が知っているように、EF はデフォルトで Read Committed 分離レベルを使用してトランザクションを実行します。これは、フィルタリング中に共有ロックがリソースに設定されることを意味すると思いますが、既存のレコードまたは 2 つの既存のレコードがないことを確認できるのはなぜですかフィルタリングの後、削除操作と追加操作を合わせてアトミック操作にする必要がありますよね? 私が正しければ、フィルタリング後にレコードが 1 つだけ存在するはずです。
見逃したものはありますか?このケースを正しく処理するにはどうすればよいですか?
助けてください。
もう 1 つの質問: LastUpdated 列を同時実行トークンとして使用し、次のケースを正しく処理するには: 1. データベース内のエンティティがコンテキスト内のエンティティよりも新しい場合は、別のスレッドを開始してエンティティを履歴データベースにアーカイブします。2. データベース内のエンティティがコンテキスト内のエンティティよりも古い場合は、保存を再試行してデータベース内のエンティティを上書きします。
ケースを処理するために以下のコードを使用する権利がありますか?
internal void SaveChangesEx()
{
bool saveFailed;
do
{
saveFailed = false;
try
{
base.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = false;
ex.Entries.ToList().ForEach(entry =>
{
if (entry.State == System.Data.EntityState.Deleted)
{
var current = base.Set<Customer>().FirstOrDefault();
Customer rfqInDb = (Customer)entry.GetDatabaseValues();
// If the newer one aready exists, start the archive, otherwise, retry by reloading the entity from DB and marking the state as deleted
if (current.LastUpdated < customerInDb.LastUpdated)
{
using (var archiver = new CustomerArchiveDBContext())
{
archiver.RFQS.Add(current);
archiver.SaveChangesEx();
}
saveFailed = false;
}
else
{
//Refresh the context and retry
entry.Reload();
entry.State = System.Data.EntityState.Deleted;
}
}
});
}
} while (saveFailed);
}
スクリプト: ========
CREATE TABLE [dbo].[Customers](
[FirstName] [nvarchar](20) NOT NULL,
[LastName] [nvarchar](60) NULL,
[Company] [nvarchar](250) NULL,
[Telephone] [nvarchar](20) NULL,
[LastUpdated] [datetime] NULL
) ON [PRIMARY]
コード ========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.Data.Entity.Validation;
using System.Threading.Tasks;
using System.Threading;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace EFOptimisticConcurrency
{
public abstract class Entity
{
public int Id { get; set; }
}
public class Customer
: Entity
{
public string FirstName { get; set; }
/// <summary>
/// Get or set the surname of this customer
/// </summary>
public string LastName { get; set; }
/// <summary>
/// Get or set the telephone
/// </summary>
public string Telephone { get; set; }
/// <summary>
/// Get or set the company name
/// </summary>
public string Company { get; set; }
public DateTime LastUpdated { get; set; }
}
class CustomerEntityConfiguration
: EntityTypeConfiguration<Customer>
{
/// <summary>
/// Create a new instance of customer entity configuration
/// </summary>
public CustomerEntityConfiguration()
{
//configure keys and properties
this.HasKey(c => c.FirstName);
this.Ignore(c => c.Id);
this.Property(c => c.FirstName)
.HasMaxLength(20)
.IsRequired();
this.Property(c => c.LastName)
.HasMaxLength(60)
.IsRequired();
this.Property(c => c.Company)
.HasMaxLength(250);
//this.Property(c => c.LastUpdated).IsConcurrencyToken();
this.Property(c => c.Telephone)
.HasMaxLength(20);
this.ToTable("Customers");
}
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Configurations.Add(new CustomerEntityConfiguration()); ;
}
}
public class Program
{
public static volatile int showStopper = 0;
static void Main(string[] args)
{
var color = Console.ForegroundColor;
EntityFrameworkProfiler.Initialize();
Task.Factory.StartNew(() =>
{
while (true)
{
Customer customer = new Customer();
customer.FirstName = "FirstName";
customer.LastName = "Last " + new Random().Next(0, 10000).ToString();
customer.Telephone = "686868";
customer.Company = "MyCity";
customer.LastUpdated = DateTime.Now;
if (showStopper == 2)
{
Console.ReadLine();
showStopper = 0;
}
try
{
Console.WriteLine("Start the Store => " + customer.LastName + " , " + customer.LastUpdated.ToString());
{
int i = 0;
using (var customerConext = new CustomerContext())
{
Console.WriteLine("Start the filter 1 => " + customer.Telephone + " , " + customer.LastUpdated.ToString());
var matched = customerConext.Customers.Where(c => c.Telephone == "686868" && c.LastUpdated < customer.LastUpdated);
foreach (var hit in matched)
{
i++;
customerConext.Customers.Remove(hit);
}
if (i == 2)
{
Console.WriteLine("1 - 2 exist, has the problem now");
showStopper = 2;
}
else if (i == 0)
{
Console.WriteLine("1 - 0 exist, has the problem now");
showStopper = 2;
}
Console.WriteLine("Start Adding 1 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
try
{
customerConext.Customers.Add(customer);
customerConext.SaveChanges();
Console.WriteLine("SaveChanges 1 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
}
catch (Exception ex)
{
Console.WriteLine("Exception 1 : " + ex.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
if (ex.InnerException != null)
{
Console.WriteLine("Inner Exception 2 : " + ex.InnerException.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception 1 " + ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message);
}
showStopper = 2;
}
}
});
Thread.Sleep(10000);
Task.Factory.StartNew(() =>
{
while (true)
{
Console.ForegroundColor = color;
try
{
Customer customer = new Customer();
customer.FirstName = "FirstName";
customer.LastName = "Last " + new Random().Next(0, 10000).ToString();
customer.Telephone = "686868";
customer.Company = "MyCity2";
customer.LastUpdated = DateTime.Now;
if (showStopper == 3)
{
Console.ReadLine();
showStopper = 0;
}
Console.WriteLine("Start the store 2 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
{
int i = 0;
using (var customerConext = new CustomerContext())
{
Console.WriteLine("Start the filter 2 => " + customer.Telephone + " , " + customer.LastUpdated.ToString());
var matched = customerConext.Customers.Where(c => c.Telephone == "686868" && c.LastUpdated < customer.LastUpdated);
foreach (var hit in matched)
{
i++;
customerConext.Customers.Remove(hit);
}
Console.WriteLine("Start Adding 2 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
try
{
customerConext.Customers.Add(customer);
customerConext.SaveChanges();
Console.WriteLine("SaveChanges 2 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
}
catch (Exception ex)
{
Console.WriteLine("Exception 2 : " + ex.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
if (ex.InnerException != null)
{
Console.WriteLine("Inner Exception 2 : " + ex.InnerException.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
}
showStopper = 2;
}
}
if (i == 2)
{
Console.WriteLine("1 - 2 exist, has the problem now");
showStopper = 2;
}
else if (i == 0)
{
Console.WriteLine("1 - 0 exist, has the problem now");
showStopper = 2;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception 2 " + ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message);
}
}
}
});
Console.WriteLine("PRESS ANY KEY TO END");
Console.ReadLine();
}
}
}