Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use backing field when determining collection navigation type to create #15506

Closed
ajcvickers opened this issue Apr 26, 2019 · 2 comments · Fixed by #16531
Closed

Use backing field when determining collection navigation type to create #15506

ajcvickers opened this issue Apr 26, 2019 · 2 comments · Fixed by #16531
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@ajcvickers
Copy link
Member

Looks like right now we are only looking at the property CLR type. See #10800.

Repro:

public class Blog
{
    private List<Post> _posts;
    
    public int Id { get; set; }

    public IEnumerable<Post> Posts
    {
        get => _posts;
        set => _posts = (List<Post>) value;
    }
}

public class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; }
}

public class BloggingContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>();
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 
        => optionsBuilder
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}

public class Program
{
    public static void Main()
    {
        using (var context = new BloggingContext())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            context.Add(new Blog
            {
                Posts = new List<Post>
                {
                    new Post(),
                    new Post(),
                    new Post()
                }
            });
            
            context.SaveChanges();
        }
        
        using (var context = new BloggingContext())
        {
            var blogs = context.Set<Blog>().Include(e => e.Posts).ToList();
        }
    }
}

Throws:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.HashSet`1[Post]' to type 'System.Collections.Generic.List`1[Post]'.
   at Blog.set_Posts(IEnumerable`1 value) in C:\Stuff\TwoTwo\TwoTwo\Program.cs:line 20
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrCollectionAccessorFactory.CreateAndSetHashSet[TEntity,TCollection,TElement](TEntity entity, Action`2 setterDelegate)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrICollectionAccessor`3.GetOrCreateCollection(Object instance)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCollection[TEntity,TRelated,TElement](Int32 includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, Boolean tracking, TEntity entity, Func`1 relate
dEntitiesFactory, Func`3 joinPredicate)
   at lambda_method(Closure , QueryContext , Blog , Object[] )
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._Include[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Action`3 fixup)
   at lambda_method(Closure , Blog )
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Program.Main() in C:\Stuff\TwoTwo\TwoTwo\Program.cs:line 70
@ajcvickers ajcvickers self-assigned this Apr 26, 2019
@divega divega added this to the 3.0.0 milestone Apr 29, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview7 Jul 2, 2019
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jul 9, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview8 Jul 29, 2019
@mhDuke
Copy link

mhDuke commented Nov 9, 2019

i am not sure whether am missing something, or this issue isn't really fixed! but i have a private HashSet backing field for public IEnumerable. if i didnt initialize the hashset myself (on the field declaration). ef core would initialize it with some ICollection type.

private HashSet<VehicleFeature> _features;
public IEnumerable<VehicleFeature> Features 
        { 
            get => _features; 
            private set => _features = new HashSet<VehicleFeature>(value); 
        }

with above code, when ef initializes the entity. The HashSet _features would gladly add duplicate values. _features is no longer a hashSet! But if _features initialization be:
private HashSet<VehicleFeature> _features = new HashSet<VehicleFeature>();
it works just fine.

More over, i created a test entity to make sure it's not something that i am missing.

private HashSet<int> _ints;
public IEnumerable<int> Ints 
        { 
            get => _ints;
            private set => _ints = new HashSet<int>(value); 
        }

But this time i initialize the _ints or not, i receive

System.InvalidCastException
HResult=0x80004002
Message=Unable to cast object of type 'System.Collections.Generic.List1[System.Int32]' to type 'System.Collections.Generic.HashSet1[System.Int32]'.
Source=Microsoft.EntityFrameworkCore.Relational
StackTrace:
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.QueryingEnumerable1.Enumerator.MoveNext() at System.Linq.Enumerable.Single[TSource](IEnumerable1 source)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.First[TSource](IQueryable`1 source)

@ajcvickers your input is much appreciated.
thats ef 3 with Sqlite 3

@ajcvickers
Copy link
Member Author

@mhDuke It's not clear to me what is happening here. Please open a new issue and post a small, runnable project/solution or complete code listing so that we can investigate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants