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

Error projecting child entity fields to specific class #5883

Closed
meekys opened this issue Jun 28, 2016 · 3 comments
Closed

Error projecting child entity fields to specific class #5883

meekys opened this issue Jun 28, 2016 · 3 comments

Comments

@meekys
Copy link

meekys commented Jun 28, 2016

Steps to reproduce

    public class Model
    {
        public Guid Id { get; set; }
        public ChildModel Child { get; set; }
    }
    public class ChildModel
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
    public class DTO {
        public Guid Id { get; set; }
        public Guid ChildId { get; set; }
    }

    var query = Db.Models.Select(x => new DTO {
        Id = x.Id,
        ChildId = x.Child.Id
    });

    var results = query.ToList(); // Fails with below exception

The issue

When projecting into a specific class, a expression type mismatch occurs.

      System.ArgumentException : Argument types do not match
      Stack Trace:
           at System.Linq.Expressions.Expression.Bind(MemberInfo member, Expression expression)
           at System.Linq.Expressions.MemberAssignment.Update(Expression expression)
           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 System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
           at Remotion.Linq.Clauses.SelectClause.TransformExpressions(Func`2 transformation)
           at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
           at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.NavigationRewritingExpressionVisitor.Rewrite(QueryModel queryModel)
           at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.OptimizeQueryModel(QueryModel queryModel)
           at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
        --- End of stack trace from previous location where exception was thrown ---
           at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
           at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass19_0`1.<CompileQuery>b__0()
           at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
           at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
           at Remotion.Linq.QueryableBase`1.GetEnumerator()
           at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
           at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)

Additional Information

Projecting to an anonymous type works as expected.
ChildId is of the expected type, Guid.

    var query = Db.Models.Select(x => new {
        Id = x.Id,
        ChildId = x.Child.Id
    });

    Assert.Equal(typeof(Guid), query.ElementType.GetProperty("ChildId").PropertyType);

Projecting to a DTO where ChildId is a Nullable Guid also works.

    public class DTO_With_Nullable {
        public Guid Id { get; set; }
        public Guid? ChildId { get; set; }
    }

    var query = Db.Models.Select(x => new DTO_With_Nullable {
        Id = x.Id,
        ChildId = x.Child.Id
    });

Further technical details

EF Core version: 1.0.0
Operating system: Windows 10 x64, Version 1511
Visual Studio version: VS Code

EF_Projection.zip

@rowanmiller
Copy link
Contributor

This is because the relationship is optional, so if the Model does not have an associated ChildModel (i.e. Model.Child would be null) then the value to be stored in DTO.ChildId would be null. For that reason, the value produced by x.Child.Id is Nullable<Guid>.

Interestingly, if this were a LINQ to Objects query, then you'd get a NullReferenceException if Model didn't have a related Child, but EF is a little more forgiving.

You could make the relationship required, if Model will always have a Child:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Model>()
                .HasOne(p => p.Child)
                .WithMany()
                .IsRequired();
        }

Or, you can defend against nulls in the query.

    var query = Db.Models.Select(x => new DTO {
        Id = x.Id,
        ChildId = (x.Child == null ? Guid.Empty : x.Child.Id)
    });

@maumar maumar self-assigned this Jun 28, 2016
@meekys
Copy link
Author

meekys commented Jun 29, 2016

Thanks. Figures I was doing something silly like that.

It's odd that this scenario works for an anonymous type, where the compiler generates a Guid member , but it doesn't work for a defined class with the same member type, or am I missing something here?

@rowanmiller
Copy link
Contributor

Closing as a duplicate of #5522. We want to change this so that we only throw if an actual null value comes back from the database.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants