0

I am going to add a record in my database, but before that, I update other records. The reason is that I have a sequence field in the records like something: Name Sequence Spring 1 Autumn 2

I want to put summer in between thus need to first update the sequence field of Autumn to 3. (the actual code is not about summer and autumn though).

The code that is doing this is as follows:

List<IdAndSequence> ExistingLevels = (from c in _classificationLevel.GetAllClassificationLevels()
                              where c.Id == NewLevel.SuObject.ObjectId
                              && c.Sequence >= NewLevel.SuObject.Sequence
                              select new IdAndSequence
                              {
                              Id = c.Id
                              ,  Sequence = c.Sequence 
                               }).ToList();




           int x = 0;
            while (x < ExistingLevels.Count())
            {
                SuClassificationLevelModel u = new SuClassificationLevelModel();
                u.Sequence = ExistingLevels[x].Sequence++;
                u.Id = ExistingLevels[x].Id;
                _classificationLevel.UpdateClassificationLevel(u);
                x++;

            }

The get all classificationlevels is coming from:

public IEnumerable<SuClassificationLevelModel> GetAllClassificationLevels()
 {
  return context.dbClassificationLevel.AsNoTracking();
 }

And the UpdateClassificationLevel is coming from:

public SuClassificationLevelModel UpdateClassificationLevel(SuClassificationLevelModel suClassificationLevelChanges)
 {
  var suClassificationLevel = context.dbClassificationLevel.Attach(suClassificationLevelChanges);
  suClassificationLevel.State = Microsoft.EntityFrameworkCore.EntityState.Modified;
  context.SaveChanges();
  return suClassificationLevelChanges;
 }

When the program is at:

_classificationLevel.UpdateClassificationLevel(u);

I get the error:

InvalidOperationException: The instance of entity type 'SuClassificationLevelModel' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

I have tried to use at several places AsNoTracking() but that didn't work either.

Any suggestions? Or is the cause in other areas of my code?

UPDATE

The code before this is first of all to check if there are any records already in the system. (it also checks the language used by the user but that is on a different model.)

[HttpPost]
public async Task<IActionResult> LevelCreate(SuObjectAndStatusViewModel NewLevel)
{
if (ModelState.IsValid)
{
 var CurrentUser = await userManager.GetUserAsync(User);
 var DefaultLanguageID = CurrentUser.DefaultLangauge;

 var TestForNull = (from c in _classificationLevelVMRepository.GetAllClassificationLevels()
     join l in _classificationLevelVMRepository.GetAllClassificationLevelLanguages()
     on c.Id equals l.ClassificationLevelId
     where c.ClassificationId == NewLevel.SuObject.ObjectId
     && l.LanguageId == DefaultLanguageID
     select c.Sequence).Count();

If there are no records then the sequence will be 1:

int MaxLevelSequence;

if (TestForNull == 0)
{ MaxLevelSequence = 1; }

If there are already records then it will check what the highest sequence is:

{
 MaxLevelSequence = (from c in 
  _classificationLevelVMRepository.GetAllClassificationLevels()
  join l in 
  _classificationLevelVMRepository.GetAllClassificationLevelLanguages()
  on c.Id equals l.ClassificationLevelId
  where c.ClassificationId == NewLevel.SuObject.ObjectId
  && l.LanguageId == DefaultLanguageID
  select c.Sequence).Max();
 MaxLevelSequence++;
}

Then it will check if the new record was going to be at the end or in the middle of the sequence of the existing records. This because if it is at the end, then the existing records don't need to be updated:

if (NewLevel.SuObject.Sequence != MaxLevelSequence)
{

After that it is as the code in the beginning of the explanation.

The following is called (through a interface):

public IEnumerable<SuClassificationLevelModel> 

    GetAllClassificationLevels()
    {
     return context.dbClassificationLevel.AsNoTracking();
    }

UPDATE When I add the AsNoTracking() to:

public IEnumerable<SuClassificationLevelModel> GetAllClassificationLevels()
 {
 return context.dbClassificationLevel.AsNoTracking();
 }

I get an error on the following code:

var TestForNull = (from c in
_classificationLevel.GetAllClassificationLevels() join l in _classificationLevelLanguage.GetAllClassificationLevelLanguages() on c.Id equals l.ClassificationLevelId where c.ClassificationId == NewLevel.SuObject.ObjectId && l.LanguageId == DefaultLanguageID select c.Sequence).Count();

The error is:

InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.
Peter le Grand
  • 53
  • 1
  • 1
  • 8
  • I don't think there is anything wrong with your code. Maybe the problem is elsewhere. Is there code being run before your sample code that could select the same `SuClassificationLevelModel` ? – Steven Lemmens Aug 26 '19 at 07:48
  • Hi Steven, I have added the extra code in the description. I had the feeling all the calls to that model shouldn't effect it. But for sure I am somewhere wrong. – Peter le Grand Aug 26 '19 at 08:09
  • If there is same DbContext between those calls and you just load those entities they are already tracked and thus you got your exception. There is quick fix for it. – dropoutcoder Aug 26 '19 at 08:20
  • They do use the same DbContext. I thought the quick fix should be to use AsNoTracking() but I didn't get that working either. – Peter le Grand Aug 26 '19 at 08:21
  • Thank you. I might be too eager. Really, thanks. – Peter le Grand Aug 26 '19 at 08:24
  • All of it seems to be AsNoTracking. This is weird. Answer should work for you, but this is interesting why it is not working as I would believe you get the right idea. What I am thinking of is if you are not loading different entity(ies) that has one to many relationship with this one and there is no lazy loading. Which should cause caching those entities and tracking them. – dropoutcoder Aug 26 '19 at 08:28
  • It might have to do with where I placed the AsNoTracking(). I tried several placed. I can't remember though where. Just try to recall. Let me edit the main text. – Peter le Grand Aug 26 '19 at 08:31
  • Just copied more code in the above, actually, the current code already included AsNoTracking(). Still have the issue. – Peter le Grand Aug 26 '19 at 08:35
  • Thy solution from the answer. Let me know if it works. – dropoutcoder Aug 26 '19 at 08:36
  • Just checked, I might have called the model differently and I updated that with a AsNoTracking as well. Though, then I get the following error: There is already an open DataReader associated with this Command which must be closed first. – Peter le Grand Aug 26 '19 at 08:38
  • I did some more checking on the placement of AsNoTracking(). If I place it I will get a different error which is: InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations. – Peter le Grand Aug 26 '19 at 08:51

1 Answers1

0

In case you are not sure if entity is already tracked or not you can change entity state with code below.

dbContext.Entry(entity).State = Microsoft.EntityFrameworkCore.EntityState.Modified;

Used like code below in your code.

public SuClassificationLevelModel UpdateClassificationLevel(SuClassificationLevelModel suClassificationLevelChanges)
 {
  dbContext.Entry(suClassificationLevelChanges).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
  context.SaveChanges();
  return suClassificationLevelChanges;
 }

Related sources:

Entry Method

dropoutcoder
  • 2,627
  • 2
  • 14
  • 32
  • Not sure how to place that in the code though. Did some more checking when adding the AsNoTracking(). Will add in the main explanation the result of this. – Peter le Grand Aug 26 '19 at 09:07
  • Gimme a second. I'll update your code and attach it to my answer. – dropoutcoder Aug 26 '19 at 09:12
  • By the way, just to give you an idea, the program I try to build is related to sustainable communities. – Peter le Grand Aug 26 '19 at 09:14
  • Had a look at that part of my code, and to my surprise (I couldn't remember), I had something like that already. See in edit in your answer. – Peter le Grand Aug 26 '19 at 09:24
  • It is not working. Still same issue. (Will check further when arrive home) – Peter le Grand Aug 26 '19 at 09:31
  • With the extra line: dbContext.Entry(suClassificationLevelChanges).State = Microsoft.EntityFrameworkCore.EntityState.Modified; (which was actually already there. The error is as shown above: A second operation started on this context before a previous operation completed. – Peter le Grand Aug 26 '19 at 11:48
  • as the error is now different, I entered a new question:https://stackoverflow.com/questions/57658513/based-on-a-linq-join-i-get-the-error-a-second-operation-started-on-this-context – Peter le Grand Aug 26 '19 at 13:06
  • The problem might be method signatures. You are returning IQueryable, but method signature defines IEnumerable as return value. There are different LINQ extension methods for IEnumerable and IQueryable. I think it might be the issue. – dropoutcoder Aug 26 '19 at 13:17