Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/google-sheets/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 实体框架和SQL Server中奇怪的SaveChanges行为_C#_Sql Server_Entity Framework_Unique Constraint_Savechanges - Fatal编程技术网

C# 实体框架和SQL Server中奇怪的SaveChanges行为

C# 实体框架和SQL Server中奇怪的SaveChanges行为,c#,sql-server,entity-framework,unique-constraint,savechanges,C#,Sql Server,Entity Framework,Unique Constraint,Savechanges,我有一些代码,您可以检查项目,错误包含在UploadContoller方法GetExtensionId中 数据库关系图: 代码(在此控制器中,我正在发送要上载的文件): 我想在数据库中创建扩展,如果它还不存在的话。我使用GetExtensionId: private static object locker = new object(); private int? GetExtensionId(string name) { int? result = n

我有一些代码,您可以检查项目,错误包含在
UploadContoller
方法
GetExtensionId

数据库关系图:

代码(在此控制器中,我正在发送要上载的文件):

我想在数据库中创建扩展,如果它还不存在的话。我使用
GetExtensionId

    private static object locker = new object();
    private int? GetExtensionId(string name)
    {
        int? result = null;
        lock (locker)
        {
            var extItem = db.FileExtensions.FirstOrDefault(m => m.displayname == name);

            if (extItem != null) return extItem.file_extensionid;

            var fileExtension = new FileExtension()
            {
                displayname = name
            };
            db.FileExtensions.Add(fileExtension);
            db.SaveChanges();
            result = fileExtension.file_extensionid;
        }
        return result;
    }
在SQL Server数据库中,我对FileExtension的displayname列有唯一的约束

只有当我上载了几个具有相同扩展名的文件,而数据库中还不存在此扩展名时,问题才会出现

如果我移除
,在
GetExtensionId
中将出现
异常
关于唯一约束

也许,出于某种原因,
foreach
cycle调用
GetExtensionId
的下一次迭代不需要等待?我不知道。 但只有当我设置
锁时,我的代码才能正常工作


如果您知道它发生的原因,请解释。

这听起来像是一个简单的并发竞争条件。想象两个请求同时出现;他们都会检查
FirstOrDefault
,这两个选项都正确地表示“nope”。然后他们都试着插入;一方获胜,一方失败,因为数据库已更改。虽然EF围绕着
SaveChanges
管理事务,但该事务不会从您最初查询数据时开始

lock
似乎可以工作,因为它可以防止它们同时进入正在查看的代码,但这通常不是一个可靠的解决方案,因为它只在单个进程内工作,更不用说节点了

因此:这里有几个选项:

  • 您的代码可以检测外键违规异常并从一开始就重新检查(FirstOrDefault等),这在成功案例中(大多数情况下都是如此)保持简单,在失败案例中(仅一个异常和一个额外的DB命中)成本不高-足够实用
  • 您可以将“select if exists,insert if not”移动到事务中数据库内部的单个操作中(理想情况下是可序列化隔离级别,和/或使用
    UPDLOCK
    提示)-这需要自己编写TSQL,而不是依赖EF,但可以最大限度地减少往返,避免编写“检测故障并补偿”代码
  • 您可以通过EF在事务内部执行选择和可能的插入-坦白地说,这既复杂又混乱:不要这样做(它同样需要是可序列化的隔离级别,但现在可序列化的事务跨越多个往返,这可能会开始影响锁,如果在规模上)

然而,为什么循环的不同迭代在第一次迭代完成之前调用该方法?因为显然,这正是正在发生的事情。。。
    private static object locker = new object();
    private int? GetExtensionId(string name)
    {
        int? result = null;
        lock (locker)
        {
            var extItem = db.FileExtensions.FirstOrDefault(m => m.displayname == name);

            if (extItem != null) return extItem.file_extensionid;

            var fileExtension = new FileExtension()
            {
                displayname = name
            };
            db.FileExtensions.Add(fileExtension);
            db.SaveChanges();
            result = fileExtension.file_extensionid;
        }
        return result;
    }