Efcore: Queries fail after upgrading to EF Core 1.1

Created on 21 Dec 2016  路  6Comments  路  Source: dotnet/efcore

After upgrading an application to EF Core 1.1 many queries do not work anymore. With the EF Core 1.0.x releases everything was fine. It may have something to do with using generic parameters, but just have a look on the example below.

I was finally able to reproduce this issue within a more or less simple "one-file" demo project, which works fine using EF Core 1.0.2, but crashes using EF Core 1.1. Using the current nightly build 1.2.0-preview1-22878 crashes too by the way.

Unhandled Exception: System.ArgumentException: An item with the same key has already been added. Key: join User u.User in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[User]) on Property([u], "UserId") equals Property([u.User], "Id")
   at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(Object key)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.SnapshotQuerySourceMapping(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.OptimizeJoinClause(JoinClause joinClause, QueryModel queryModel, Int32 index, Action baseVisitAction, MethodInfo operatorToFlatten, Boolean groupJoin)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitJoinClause(JoinClause joinClause, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection`1 bodyClauses, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalEntityQueryableExpressionVisitor.VisitSubQuery(SubQueryExpression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMember(MemberExpression node)
   at System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.ReplaceClauseReferences(Expression expression, IQuerySource querySource, Boolean inProjection)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitWhereClause(WhereClause whereClause, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection`1 bodyClauses, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, INodeTypeProvider nodeTypeProvider, IDatabase database, ILogger logger, Type contextType)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass15_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)
   at Program.Item[TView](IQueryable`1 query, Guid key) in C:\Users\Axel\Desktop\efcore\Program.cs:line 161
   at Program.Main(String[] args) in C:\Users\Axel\Desktop\efcore\Program.cs:line 153

Steps to reproduce

Just run the "program" below using different EF Core versions.

```c#
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.EntityFrameworkCore;

// --- Contracts ---------------------------------------------------------------

public interface IHasKey
{
Guid Id { get; set; }
}

// --- Models ------------------------------------------------------------------

public class Project : IHasKey
{
public Guid Id { get; set; }

public string Name { get; set; }

public ISet<ProjectUser> User { get; set; }

}

public class ProjectUser : IHasKey
{
public Guid Id { get; set; }

public Guid ProjectId { get; set; }

public Project Project { get; set; }

public Guid UserId { get; set; }

public User User { get; set; }

}

public class User : IHasKey
{
public Guid Id { get; set; }

public string Name { get; set; }

}

// --- ViewModels --------------------------------------------------------------

public class ProjectView : IHasKey
{
public Guid Id { get; set; }

public string Name { get; set; }

public IEnumerable<PermissionView> Permissions { get; set; }

}

public class PermissionView : IHasKey
{
public Guid Id { get; set; }

public Guid UserId { get; set; }

public string UserName { get; set; }

}

// --- Context -----------------------------------------------------------------

public class ModelContext : DbContext
{
public DbSet Project { get; set; }

public DbSet<ProjectUser> ProjectUser { get; set; }

public DbSet<User> User { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var connectionStringBuilder = new SqlConnectionStringBuilder()
    {
        DataSource = "(localdb)\\MSSQLLocalDB",
        InitialCatalog = "EFCore",
        IntegratedSecurity = true
    };

    optionsBuilder.UseSqlServer(connectionStringBuilder.ConnectionString);
}

}

// --- Test --------------------------------------------------------------------

public class Program
{
public static void Main(string[] args)
{
// Init
using (var context = new ModelContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();

        var projects = new[]
        {
            new Project { Name = "Project 1" },
            new Project { Name = "Project 2" },
            new Project { Name = "Project 3" },
        };

        context.Project.AddRange(projects);

        var users = new[]
        {
            new User { Name = "User 1" },
            new User { Name = "User 2" },
            new User { Name = "User 3" },
        };

        context.User.AddRange(users);

        var permissions = (from project in projects
                           from user in users
                           select new ProjectUser
                           {
                               ProjectId = project.Id,
                               Project = project,
                               UserId = user.Id,
                               User = user
                           }).ToList();

        context.ProjectUser.AddRange(permissions);

        context.SaveChanges();
    }

    // Query
    using (var context = new ModelContext())
    {
        var query = from p in context.Project
                    select new ProjectView
                    {
                        Id = p.Id,
                        Name = p.Name,
                        Permissions = from u in p.User
                                      select new PermissionView
                                      {
                                          Id = u.Id,
                                          UserId = u.UserId,
                                          UserName = u.User.Name
                                      }
                    };

        var target = context.ProjectUser.First();

        Item(query, target.ProjectId);
    }
}

public static TView Item<TView>(IQueryable<TView> query, Guid key)
    where TView : IHasKey
{
    // Boom!
    return query.SingleOrDefault(item => item.Id == key);
}

}
```

Further technical details

EF Core version: 1.1.0
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10 / Windows 7
IDE: Visual Studio Code / Visual Studio 2015

closed-fixed type-bug

Most helpful comment

Risk: Low, a one line change that changes a dictionary update from a potentially throwing Add call to an overwriting set accessor operation.
Justification: This change fixes a regression that causes many previously working LINQ queries to fail.

All 6 comments

BTW, generic parameters seem not to be the root cause. I've had another crash with a very similar stack trace, but no time to reproduce it with a small sample like above.

Reopening as this still needs to go through patch approval process.

@anpete could you please write down justification/risk assessment for this fix?

Any ETA for EF Core 1.1.1?

Risk: Low, a one line change that changes a dictionary update from a potentially throwing Add call to an overwriting set accessor operation.
Justification: This change fixes a regression that causes many previously working LINQ queries to fail.

This patch bug is approved. Please use the normal code review process w/ a PR and make sure the fix is in the correct branch, then close the bug and mark it as done.

Was this page helpful?
0 / 5 - 0 ratings