已成功提交对数据库的更改,但是在更新对象上下文时发生了错误

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
你好,

我有一个asp.net Web API。我正在使用EF 6代码优先方法。仅当我使用Jmeter对Web API进行负载测试时,才出现此错误。我怎样才能解决这个问题?

C#:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: Saving or accepting changes failed because more than one entity of type 'BusinessEntity.GameResponse' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration

这是我的GameResponse:

C#:
using System;

namespace BusinessEntity
{
    public class GameResponse
    {
        public int Id { get; set; }
        public string referenceId { get; set; }
        public string productCode { get; set; }
        public int quantity { get; set; }
        public string version { get; set; }
        public string signature { get; set; }
        public string ApplicationCode { get; set; }
        public string validatedToken { get; set; }
        public DateTime? responseDateTime { get; set; } = DateTime.Now;
        public string initiationResultCode { get; set; }
        public string companyToken { get; set; }
        public string estimateUnitPrice { get; set; }
        public string currency { get; set; }
    }
}

这是我正在测试负载的方法:

C#:
private async Task<HttpResponseMessage> CallRazerService(RequestDto requestDto)
        {
            HttpResponseMessage response = null;

            using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
            {
                try
                {
                    //Transform DTO into GameRequest for calling Razer Initiate
                    var config = new MapperConfiguration(cfg =>
                    {
                        cfg.CreateMap<RequestDto, GameRequest>();
                        cfg.CreateMap<GameRequest, GameConfirmRequest>();
                        cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
                        cfg.CreateMap<Coupon, CouponDto>();
                    });
                    var iMapper = config.CreateMapper();
                    var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
                    //Unique reference ID
                    gameRequest.referenceId = Guid.NewGuid().ToString();

                    //Create signature
                    gameRequest = Utilities.CreateSignature(gameRequest, RequestType.Initiate);

                    //Add initiation request into database
                    _unitOfWork.GameRepository.Insert(gameRequest);
                    

                    #region Call Razer initiate/confirm

                    //Call Razer for initiation
                    response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");

                    //Read response
                    var htmlResponse = await response.Content.ReadAsStringAsync();
                    var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);

                    //Adding initiation response into database
                    _unitOfWork.GameResponseRepository.Insert(gameResponse);
                    

                    if (gameResponse.initiationResultCode == "00")
                    {
                        gameRequest.validatedToken = gameResponse.validatedToken;
                        //Create signature
                        var gameConfirmRequest = Utilities.CreateSignature(gameRequest, RequestType.Confirm);

                        //Transform DTO into GameRequest for calling Razer Initiate
                        var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);

                        //Add confirm request into database
                        _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);
                        

                        //Call Razer for confirm
                        response = await Utilities.CallRazer(gameRequest, "purchaseconfirmation");

                        //Read response
                        htmlResponse = await response.Content.ReadAsStringAsync();
                        var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);

                        //Add confirm response into database
                        _unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }

                #endregion

                await _unitOfWork.SaveAsync();
                scope.Complete();
            }

            return response;
        }
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,539
地点
弗吉尼亚州切萨皮克
编程经验
10+
The warning that you got back hints at issues with primary key values. Is ID or referenceId? How are you generating those ID values? Are you letting the database generate the primary key value for you, or are you generating it yourself. If you are generating it yourself, how are you ensuring uniqueness? (I see that you are using GUIDs for referenceId.)
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
为Id的数据库生成它们,为GUID的生成它们如下:
C#:
//Unique reference ID
                gameRequest.referenceId = Guid.NewGuid().ToString();

我认为referenceId生成会导致此问题。如何使它独特? SQL可以为我创建它吗?
 
Last edited:

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,933
地点
英国
编程经验
10+
问题似乎是您没有在数据库表中使用任何ID管理,例如自动增量(AI)。如果这样做,则无需插入或更新自动递增的id字段。

这就是使自动递增的值唯一的原因,通常是,与该行的数据一起插入后,通常不需要更改它们。它们成为保存数据的行的主键。一个表结构可以在没有AI的情况下具有主键,但是您冒着必须管理哪些内容的麻烦。 下一页 ID应该在下一行insert语句上,尤其是当数据库非常乐意为您提供ID时。

我会在黑暗中刺一击,并假设你 ID 将被插入到您的数据库中,并且 不是 主键还是自动递增?
并且为referenceId分配了一个GUID,该GUID应该是唯一字段,对吗?
如果不是,我会重新考虑您关于此表结构的逻辑。您应该有一个ID字段,它是AI和主键,并且应该使GUID字段唯一。

您可以发布表格结构吗?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,539
地点
弗吉尼亚州切萨皮克
编程经验
10+
顺便说一句,OP会在压力测试期间继续指示问题。我想知道在正常的集成测试期间是否还会发生错误。如果它仅在压力测试期间出现,我想知道以下情况之一是否成立:
1.它们从非空数据库开始。因此,要么已经存在预先存在的重复行,要么任何新的主键值最终都重复了现有行。
2.它们有一个并发问题,即正在生成相同的ID。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
我改变了几件事。首先,我手动设置了referenceId,它是唯一的GUID。然后添加带有该referenceId的实体。之后,我在另一个实体中使用相同的referenceId来添加到数据库中。

现在,SQL Server为我生成referenceId,如下所示:
C#:
public class GameRequest
    {
        public int Id { get; set; }
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid referenceId { get; set; }
...

现在是我的代码。但是问题是当我获取生成的refenerceId时 插入 将其导入数据库。 AFAIK,在完成SaveAsync插入之后。为了以某种方式调用Razer进行确认,我需要使用此SQL生成的referenceId。 (请参见下面的代码中的gameRequest)

忘了提及,当我在数据库中将referenceId更改为GUID时,出现了此错误。

System.Data.SqlClient.SqlException:当IDENTITY_INSERT设置为OFF时,无法在表'GameRequests'中为身份列插入显式值。
C#:
...
var config = new MapperConfiguration(cfg =>
                {
                    cfg.CreateMap<RequestDto, GameRequest>();
                    cfg.CreateMap<GameRequest, GameConfirmRequest>();
                    cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
                    cfg.CreateMap<Coupon, CouponDto>();
                    cfg.CreateMap<GameRequest, GameRequestDto>();
                });
                var iMapper = config.CreateMapper();
                var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
                //Unique reference ID
                //gameRequest.referenceId = Guid.NewGuid().ToString();

                var gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);

                //Create signature
                gameRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Initiate);

                //Add initiation request into database
                _unitOfWork.GameRepository.Insert(gameRequest);

                #region Call Razer initiate/confirm

                //Call Razer for initiation
                response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");

                //Read response
                var htmlResponse = await response.Content.ReadAsStringAsync();
                var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);

                //Adding initiation response into database
                _unitOfWork.GameResponseRepository.Insert(gameResponse);

                if (gameResponse.initiationResultCode == "00")
                {
                    gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
                    gameRequestDto.validatedToken = gameResponse.validatedToken;
                    //Create signature
                    var gameConfirmRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Confirm);

                    //Transform DTO into GameRequest for calling Razer Initiate
                    var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);

                    //Add confirm request into database
                    _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);

                    //Call Razer for confirm
                    response = await Utilities.CallRazer(gameRequest, "purchaseconfirmation");
...
              
                   await _unitOfWork.SaveAsync();
 

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,933
地点
英国
编程经验
10+

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,933
地点
英国
编程经验
10+
根据文档中的说明,插入完后,最好不要将identity_insert设置为off吗? (仅仅是为了在其他桌子上做同样的事情?)

备注状态:
任何时候,会话中只有一个表的IDENTITY_INSERT属性可以设置为ON。如果一个表已经将此属性设置为ON,并且为另一个表发出了SET IDENTITY_INSERT ON语句,则SQL Server返回一条错误消息,指出SET IDENTITY_INSERT已经为ON,并报告其设置为ON的表。

如果插入的值大于表的当前标识值,则SQL Server将自动使用新插入的值作为当前标识值。

SET IDENTITY_INSERT的设置是在执行或运行时设置的,而不是在解析时设置的。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
我删除了id列,并设置了referenceId PK。但是仍然出现此错误:

无法将值NULL插入表'GameAPI.dbo.GameRequests'的列'referenceId'中;列不允许为空。 INSERT失败。

我该怎么办?
C#:
public class GameRequest
    {
        
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid referenceId { get; set; }
...
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
我手动更改了迁移文件;
AlterColumn("dbo.GameRequests", "referenceId", c =>c.Guid(nullable:false,身份:true,defaultValueSql:"newsequentialid()"));
现在已通过此步骤,我必须获取生成的GUID,并在其余代码中使用它。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
此时,我可以以某种方式获取生成的GUID吗?
C#:
_unitOfWork.GameRepository.Insert(gameRequest);

我认为调用SaveAsync时会生成GUID吗?但是到最后,我需要以某种方式获取GUID吗?
C#:
//Add initiation request into database
                _unitOfWork.GameRepository.Insert(gameRequest);

                #region Call Razer initiate/confirm

                //Call Razer for initiation
                response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");

                //Read response
                var htmlResponse = await response.Content.ReadAsStringAsync();
                var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);

                //Adding initiation response into database
                _unitOfWork.GameResponseRepository.Insert(gameResponse);

                if (gameResponse.initiationResultCode == "00")
                {
                    gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
                    gameRequestDto.validatedToken = gameResponse.validatedToken;
                    //Create signature
                    var gameConfirmRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Confirm);

                    //Transform DTO into GameRequest for calling Razer Initiate
                    var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);

                    //Add confirm request into database
                    _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);

                    //Call Razer for confirm
                    response = await Utilities.CallRazer(gameRequest, "purchaseconfirmation");

                    //Read response
                    htmlResponse = await response.Content.ReadAsStringAsync();
                    var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);

                    //Add confirm response into database
                    _unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
                }

                #endregion

                await _unitOfWork.SaveAsync();
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
如果在插入后立即使用事务作用域并添加SaveAsync,是否有意义?
C#:
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
 {
              ...

                //Add initiation request into database
                _unitOfWork.GameRepository.Insert(gameRequest);
               await _unitOfWork.SaveAsync();

                #region Call Razer initiate/confirm

                //Call Razer for initiation
                response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");

                //Read response
                var htmlResponse = await response.Content.ReadAsStringAsync();
                var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);

                //Adding initiation response into database
                _unitOfWork.GameResponseRepository.Insert(gameResponse);

                if (gameResponse.initiationResultCode == "00")
                {
                    gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
                    gameRequestDto.validatedToken = gameResponse.validatedToken;
                    //Create signature
                    var gameConfirmRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Confirm);

                    //Transform DTO into GameRequest for calling Razer Initiate
                    var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);

                    //Add confirm request into database
                    _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);

                    //Call Razer for confirm
                    response = await Utilities.CallRazer(gameRequest, "purchaseconfirmation");

                    //Read response
                    htmlResponse = await response.Content.ReadAsStringAsync();
                    var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);

                    //Add confirm response into database
                    _unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
                }

                #endregion

                await _unitOfWork.SaveAsync();
                scope.Complete();
}
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
这会回滚吗?有什么建议?

C#:
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
 {
              ...

                //Add initiation request into database
                _unitOfWork.GameRepository.Insert(gameRequest);
               await _unitOfWork.SaveAsync();

                ...

                //Adding initiation response into database
                _unitOfWork.GameResponseRepository.Insert(gameResponse);
                
                ...
              
                    //Add confirm request into database
                    _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);
 
               ...
                  

                    //Add confirm response into database
                    _unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
                }

              ....
          

                await _unitOfWork.SaveAsync();
                scope.Complete();
}
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,539
地点
弗吉尼亚州切萨皮克
编程经验
10+
好吧,如果抛出异常或如果您显式调用事务范围的回滚操作不回滚,则数据库事务提供程序或.NET Framework的事务范围中均存在错误。考虑到数据库开发人员如何沉迷于使事务和回滚正常工作,我想说代码回滚失败的可能性非常低。 (这全部取决于合作范围内交易范围内的所有元素。)
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
我现在放心了 :) 因为没有其他方法可以想到在插入时获取referenceId。
C#:
_unitOfWork.GameRepository.Insert(gameRequest);
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,539
地点
弗吉尼亚州切萨皮克
编程经验
10+
为什么需要找回ID?要在另一个表中建立关系?如果正确使用Entity Framework,则不必管理这种类型的关系。只要一个实体引用了另一个实体,EF就应该通过在幕后为您管理ID来保持关系。
 

Raysefo

知名会员
已加入
2019年2月22日
留言内容
194
编程经验
10+
我将Unity生命周期管理器更改为瞬态。在JMeter中,我尝试如下:

  • 10个用户10秒:成功
  • 50位用户10秒:成功
  • 100位用户10秒:成功
  • 150位用户10秒:我得到18 {0}超时已过期。从池中获取连接之前已经过超时时间。这可能是因为所有池化连接都在使用中,并且达到了最大池大小。
 
最佳 底部