我正在尝试使用 EF5 Code First 实现一致的多读写器计数器,并且遇到了并发异常(这是我预期的),但我也遇到了主键约束违规,这是我没想到的。这只发生在计数器由代码创建的情况下;如果它已经存在,则按预期进行计数。
这是我正在使用的代码(也带有调试代码):
public class EFCounter
{
private static int UpdateExceptionCount = 0;
private const int StartValue = 1000001;
public int CreateOrIncrement(Guid counterId)
{
using (var context = new EFCounterContext("MsSqlViewModel"))
{
if (context.Counters.Any(cntr => cntr.CounterId == counterId) == false)
{
try
{
context.Counters.Add(
new Counter
{
CounterId = counterId,
Value = StartValue
}
);
context.SaveChanges();
return StartValue;
}
catch (Exception e)
{
//fall through
}
}
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
var counter = context.Counters.First(cntr => cntr.CounterId == counterId);
do
{
try
{
lock (this)
{
counter.Value += 1;
objectContext.SaveChanges(SaveOptions.DetectChangesBeforeSave);
return counter.Value;
}
}
catch (OptimisticConcurrencyException ex)
{
objectContext.Refresh(RefreshMode.StoreWins, counter);
}
catch (UpdateException ex)
{
var ueCount = Interlocked.Increment(ref UpdateExceptionCount);
objectContext.Detach(counter);
counter = context.Counters.First(cntr => cntr.CounterId == counterId);
Console.WriteLine("UpdateExceptions: {0}", ueCount);
}
} while (true);
}
}
}
public class Counter
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid CounterId { get; set; }
[ConcurrencyCheck]
public int Value { get; set; }
}
我只是Parallel.For
用来调用它:
EFCounter counter1 = new EFCounter();
EFCounter counter2 = new EFCounter();
Guid counterId = Guid.NewGuid();
Parallel.For(
1,
10,
i =>
{
Console.WriteLine("1: {0}", counter1.CreateOrIncrement(counterId));
Console.WriteLine("2: {0}", counter2.CreateOrIncrement(counterId));
}
);
作为参考,连接字符串:
<add name="MsSqlViewModel" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=ESRaffleViewModels;Integrated Security=SSPI" />
这是在我的机器上运行的代表的输出;我从来没有真正完成过出现 UpdateExceptions 的运行:
1:1000001 2:1000002 更新异常:1 1:1000003 更新异常:2 2:1000004 更新异常:3 1:1000005 更新异常:4 2:1000006 更新异常:5 更新异常:6 更新异常:7 更新异常:8 更新异常:9 更新异常:10 更新异常:11 更新异常:12 更新异常:13 更新异常:14 更新异常:15 更新异常:16 更新异常:17 更新异常:18 更新异常:19 更新异常:20 更新异常:21 更新异常:22 更新异常:23 更新异常:24 更新异常:25 更新异常:26 更新异常:27 更新异常:28 更新异常:29 更新异常:30 更新异常:31 更新异常:32 更新异常:33 更新异常:34 更新异常:35 更新异常:36 更新异常:37 更新异常:38 更新异常:39 更新异常:40 更新异常:41 更新异常:42 更新异常:43 更新异常:44 更新异常:45 更新异常:46 更新异常:47 更新异常:48 更新异常:49 更新异常:50 更新异常:51 更新异常:52 更新异常:53 更新异常:54 更新异常:55 更新异常:56 更新异常:57 更新异常:58 更新异常:59 更新异常:60 更新异常:61 更新异常:62 更新异常:63 更新异常:64 更新异常:65 更新异常:66 更新异常:67 更新异常:68 更新异常:69 更新异常:70 更新异常:71 更新异常:72 更新异常:73 更新异常:74 更新异常:75 更新异常:76 更新异常:77 更新异常:78 更新异常:79 更新异常:80 更新异常:81 更新异常:82 更新异常:83 更新异常:84 更新异常:85 更新异常:86 更新异常:87 更新异常:88 更新异常:89 更新异常:90 更新异常:91 更新异常:92 更新异常:93 更新异常:94 更新异常:95 更新异常:96 更新异常:97 更新异常:98 更新异常:99 更新异常:100
我在这里错过了一些基本的东西吗?