Search code examples
c#listentity-frameworkentity-framework-corein-memory-database

Entity Framework In Memory Database provider weird behavior


[HttpGet]
public JsonResult GetTodoItems()
{
    return new JsonResult(_context.TodoItems.ToList());
}

[HttpPost]
public JsonResult PostTodoItem(TodoItem item)
{
    _context.TodoItems.Add(item);
    _context.SaveChanges();
    return new JsonResult("Added successfully!");
}

[HttpDelete("{id}")]
public JsonResult DeleteTodoItem(long id)
{
    _context.TodoItems.Remove(_context.TodoItems.Find(id));
    _context.SaveChanges();
    return new JsonResult("Deleted successfully!");
}

Also, here is the TodoItem model:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

For some reason, I do not understand what goes behind the scenes in the following scenario:

I delete one of the items using the API. And if I now add another item onto the list, the order of TodoItems seems to change, and was wondering what goes behind the scene to invoke such behavior.

Here is an example - this is the starting situation:

[
    { "Id": 1, "Name": "Item1", "IsComplete": false },    
    { "Id": 2, "Name": "Item2", "IsComplete": false },
    { "Id": 3, "Name": "Item3", "IsComplete": false }
]

Now I delete Item2 then the list returned by the API is:

[
    { "Id": 1, "Name": "Item1", "IsComplete": false },
    { "Id": 3, "Name": "Item3", "IsComplete": false}
]

Then I add Item4 then the list is:

[
    { "Id": 1, "Name": "Item1", "IsComplete": false },
    { "Id": 4, "Name": "Item4", "IsComplete": false },
    { "Id": 3, "Name": "Item3", "IsComplete": false }
]

As you see the Item4 gets added to where the last deleted item was. After this if I add subsequent items it continues to add the items at the end of the list and returns so. This is probably because of EF's InMemory Database Provider I am guessing, as it is primarily for test purposes. But even though, I wanted to know how the list works behind this phenomenon.

Using the local watch window didn't help, because it did actually delete the Item2 object from the list and apparently shifted the list to add the new item.

Edit: When I am talking about how list works here, what I mean is how DbSet works in this add and remove operations as _context.TodoItems is a DbSet object and the ToList() is just converting that to a list.


Solution

  • I wanted to know how the list works behind this phenomenon

    Every EF provider will do stuff like this. The order of items returned by this:

    return new JsonResult(_context.TodoItems.ToList());

    is an undocumented implementation detail of the storage provider, and if you want the items returned in any particular order, you must use .OrderBy() in your EF query.

    If you're curious about the InMemory provider implementation the code is on GitHub, and the storage is a

    private readonly Dictionary<TKey, object?[]> _rows;
    

    https://github.com/dotnet/efcore/blob/main/src/EFCore.InMemory/Storage/Internal/InMemoryTable.cs

    Which leads to:

    Remarks

    The order of the values in the Dictionary<TKey,TValue>.ValueCollection is unspecified, but it is the same order as the associated keys in the Dictionary<TKey,TValue>.KeyCollection returned by the Keys property.

    https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.values?view=net-5.0#remarks