dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.71k stars 3.17k forks source link

Support fixed-size collections #24497

Open uladz-zubrycki opened 3 years ago

uladz-zubrycki commented 3 years ago

Description

Error message is not that descriptive as it could be for a case when fixed size collection (Array) is used for a navigation property. It'd be nice to be pointed to the problematic navigation property or entity type at least with a clean problem description in the error message.

Creating issue because of the conversation happening here https://github.com/dotnet/efcore/issues/6589#issuecomment-689041161

Could the exception message be more intuitive, please? It took me a while to find the cause of this problem, whereas if it had said something different I might have got it quicker "Association property instances cannot be a fixed length type, such as an Array" Note how I said "instances" so as not to mislead people into thinking they cannot declare the property type as IEnumerable

This issue is very old. If you're still seeing this exception with EF Core 3.1 or 5.0, then please open a new issue and attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

Code

I reproduced the issue on a sample used for another issue in this repo previously, so it's not that minimal (and won't probably work even, when this fixed collection case is improved, due to another configuration error), but should be okay for the error discussed. Repro.zip

Include provider and version information

EF Core version: 3.1.12 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: net48 Operating system: Windows 10 IDE: Visual Studio 2019 16.3

ajcvickers commented 3 years ago

Note for triage:

Unhandled exception. System.NotSupportedException: Collection was of a fixed size.
   at System.SZArrayHelper.Remove[T](T value)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.Remove(Object entity, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.RemoveFromCollection(INavigation navigation, InternalEntityEntry value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.RemoveFromCollection(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IReadOnlyList`1 containingPrincipalKeys, IReadOnlyList`1 containingForeignKeys, Object oldValue, Object newValue)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.KeyPropertyChanged(InternalEntityEntry entry, IProperty property, IReadOnlyList`1 keys, IReadOnlyList`1 foreignKeys, Object oldValue, Object newValue)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectKeyChange(InternalEntityEntry entry, IProperty property)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.PropertyChanged(InternalEntityEntry entry, IPropertyBase propertyBase, Boolean setModified)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.PropertyChanged(InternalEntityEntry entry, IPropertyBase property, Boolean setModified)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete, CurrentValueType valueType)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetTemporaryValue(IProperty property, Object value, Boolean setModified)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.SetForeignKeyProperties(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, Boolean setModified, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.FixupToPrincipal(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, Boolean setModified, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.DelayedFixup(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry referencedEntry, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.FireStateChanged(EntityState oldState)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
   at Repro.Program.Main(String[] args) in C:\Stuff\AllTogetherNow\ThreeOne\ThreeOneApp.cs:line 23
ajcvickers commented 3 years ago

Putting this on the backlog to check the first time we create a property accessor. This won't help in all cases because the property can be assigned a new instance of a different type at any time, and we don't want to do an additional check every time we access the property.

FrankIceBass commented 3 years ago

Hi, I have exactly the same problem on the latest version.

Include provider and version information

EF Core version: 5.0.10 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .Net Core 5.0 Operating system: Windows 10 IDE: Visual Studio 2019 16.11.3

Here is the log: System.NotSupportedException: Collection was of a fixed size. at System.SZArrayHelper.Add[T](T value) at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.Add(Object entity, Object value, Boolean forMaterialization) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.AddToCollection(InternalEntityEntry entry, INavigationBase navigation, InternalEntityEntry value, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.ToDependentFixup(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.FireStateChanged(EntityState oldState) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityStateAsync(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintActionAsync(EntityEntryGraphNode`1 node, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraphAsync[TState](EntityEntryGraphNode`1 node, Func`3 handleNode, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.AddRangeAsync(IEnumerable`1 entities, CancellationToken cancellationToken) at WatchMyGame.Web.Dal.Services.ImportDataService.UpdateProjectsAsync(User user, String projectData, Boolean isManager, IDictionary`2 projects, CancellationToken cancellationToken) in C:\Lavoro\Git\WatchMyGame\Src\WatchMyGame.Web.Dal\Services\ImportDataService.cs:line 238 at WatchMyGame.Web.Dal.Services.ImportDataService.UpdateUserAsync(Int64 seasonId, User user, UserSeason userSeason, ImportAnagraficaItemModel anagrafica, IDictionary`2 projects, UserImportConfirmRequest config, CancellationToken cancellationToken) in C:\Lavoro\Git\WatchMyGame\Src\WatchMyGame.Web.Dal\Services\ImportDataService.cs:line 306 at WatchMyGame.Web.Dal.Services.ImportDataService.ProcessUserAsync(Int64 seasonId, User user, UserSeason userSeason, ImportAnagraficaItemModel anagrafica, IDictionary`2 projects, UserImportConfirmRequest config, CancellationToken cancellationToken) in C:\Lavoro\Git\WatchMyGame\Src\WatchMyGame.Web.Dal\Services\ImportDataService.cs:line 352 at WatchMyGame.Web.Dal.Services.ImportDataService.ConfirmImportAnagraficaAsync(Int64 userId, Int64 seasonId, UserImportConfirmRequest config, CancellationToken cancellationToken) in C:\Lavoro\Git\WatchMyGame\Src\WatchMyGame.Web.Dal\Services\ImportDataService.cs:line 94 at WatchMyGame.Web.Services.ImportService.ConfirmImportAnagraficaAsync(Int64 userId, Int64 seasonId, UserImportConfirmRequest request, CancellationToken cancellationToken) in C:\Lavoro\Git\WatchMyGame\Src\WatchMyGame.Web.Services\Services\ImportService.cs:line 117 at WatchMyGame.Web.Controllers.Api.ImportController.ConfirmAnagraficaAsync(UserImportConfirmRequest request, CancellationToken cancellationToken) in C:\Lavoro\Git\WatchMyGame\Src\WatchMyGame.Web\Controllers\Api\ImportController.cs:line 86 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)

ajcvickers commented 3 years ago

@FrankIceBass Fixed-size collections, such as arrays, are not supported since EF needs to add elements to them.

FrankIceBass commented 3 years ago

@ajcvickers I hardly understand which is the fixed-size collections. I'm adding items to a DbSet with AddRangeAsync. I'm using just this on DbSet:

await this.context.ProjectUsers.AddRangeAsync(list, cancellationToken);

ajcvickers commented 3 years ago

@FrankIceBass Typically a collection of one entity type on another entity type. For example, the collection of posts on a blog. This is known as a navigation. See Relationships in the docs.

FrankIceBass commented 3 years ago

@ajcvickers I really hardly understand. I'm using scaffolded db context and it is just a 1-N table. I will try to check more why this "feature" is annoying me so much. By the way I have the same exception even if i try to update objects into that DbSet.

Anyway.. thank you for your support.

ajcvickers commented 3 years ago

@FrankIceBass Feel free to open a new issue attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

uladz-zubrycki commented 3 years ago

@FrankIceBass I'd guess that you are using sth like Array to initialize the navigation property from 1 to N for your relation somewhere during the entity instance initialization.

See the repro I attached initially, it might shed some light.

FrankIceBass commented 3 years ago

@v-zubritsky unfortunately it's not my case.

I have the same exception even trying to add items not related to parent entities.

var list = projectList.Where(item => !userProjects.Where(y => y.ProjectId == item.Id).Any()) .Select(x => new Ef.ProjectUser { CreationDate = DateTime.UtcNow, Deleted = false, IsManager = isManager, LastUpdate = DateTime.UtcNow, ProjectId = x.Id, UserId = user.Id }).ToList();

            await this.context.ProjectUsers.AddRangeAsync(list, cancellationToken);
FrankIceBass commented 3 years ago

Hi everyone, I just solved it... well... it's strange solution but it worked for me. I was watching this.context.ChangeTracker.Entries() and it was full of unchanged properties. I just added this.context.ChangeTracker.Clear() before running the code I posted above... well... it worked...

omuleanu commented 2 years ago

Calling .ToList() solved the problem for me. I was getting the above error message ( Collection was of a fixed size. at System.SZArrayHelper.Remove...) , when I was setting model.Foos = newFoos; only when model.Foos count was 1 and newFoos was more than 1 or the other way around.

rombethor commented 1 year ago

I feel the need to weigh in here, having encountered this error and scratched my head for over an hour as to where the fixed array was, why it updated and why it was failing to 'Remove' with an 'Add'.

Firstly, the exception misled me because I was performing a .Add(entity1) followed by a .Remove(entity2). The exception was being thrown on the add.

Secondly, the fixed array was not in either entity1 or entity2. My schema looked like the following:

[Table]
public class A
{
  [Key]
  public int AId { get; set; }
  public IList<B>? Bs { get; set; } = new List<B>();
}

[Table]
public class B
{
  public int AId { get; set; }
  public int CId { get; set; }
  [ForeignKey("AId")]
  public IList<A> A { get; set; }
  [ForeignKey("CId")]
  public IList<C> C { get; set; }
}

[Table]
public class C
{
  [Key]
  public int CId { get; set; }

  //I'd set as an array to avoid updating from here.
  public IEnumerable<B> Bs { get; set; } = Array.Empty<B>();
}

Then when trying to add an A, with a bunch of Bs in the list, I believe in the background it wanted to update the Array of Cs through the change tracking.

In brief:

MyDbContext db = new(options);

//Perhaps I loaded some Cs earlier on in the same DbContext, bringing them into the local change tracking, perhaps why clearing the change tracking has worked for others
var bunchOfCs = db.Cs.ToList();

A myA = new();
myA.Bs.Add(new B() { AId = 123, BId = 234 });

db.As.Add(myA); //throws error

Changing the C.Bs to a List allowed it to run.

In conclusion, it might be a good idea to add a warning of this type of background behaviour along with the exception.

FreddyJohn commented 6 months ago

@FrankIceBass Fixed-size collections, such as arrays, are not supported since EF needs to add elements to them.

How is it not possible to take a fixed sized array and convert it into an array that is growable and not fixed?

cincuranet commented 6 months ago

How is it not possible to take a fixed sized array and convert it into an array that is growable and not fixed?

Arrays in C#/.NET are always fixed size. List<T>, for example, can grow as required.