Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Include causes InvalidCastException

I have a problem with this query in Entity Framework Core 1.1:

var leaves = _context.Proposal.OfType<ProposalLeave>()
                .Include(l => l.Creator).ThenInclude(e => e.User)
                .Include(l=>l.LeaveType) // this include causes the exception
                .Include(l => l.ProposalLeaveStatuses).ThenInclude(l => l.ProposalLeaveStatus)
                .ToList();

Include to LeaveType raise this error:

InvalidCastException: Unable to cast object of type 'System.DateTime' to type 'System.Int32'.

Microsoft.EntityFrameworkCore.ChangeTracking.Internal.SimpleNonNullableDependentKeyValueFactory.TryCreateFromBuffer(Valu fdc eBuffer valueBuffer, out TKey key)

My models:

public class Proposal
{
    [Key]
    public int ProposalId { get; set; }

    [Required]
    public DateTime DateCreated { get; set; }

    [Required]
    public int CreatorId { get; set; }

    [ForeignKey("CreatorId")]
    public Employee Creator { get; set; }
}

public class ProposalLeave : Proposal
{
    [Required]
    public DateTime LeaveStart { get; set; }

    [Required]
    public DateTime LeaveEnd { get; set; }

    [Required]
    public int ProposalLeaveTypeId { get; set; }

    [ForeignKey("ProposalLeaveTypeId")]
    public virtual ProposalLeaveType LeaveType { get; set; }

    public virtual ICollection<ProposalLeaveStatuses> ProposalLeaveStatuses { get; set; }
}

public class ProposalLeaveType
{
    [Key]
    public int LeaveTypeId { get; set; }

    [Required, StringLength(255)]
    public string Name { get; set; }

    [Required, Column(TypeName = "text")]
    public string Description { get; set; }

    public ICollection<ProposalLeave> ProposalLeaves { get; set; }
}

A part of my DbContext:

public class AppDbContext : IdentityDbContext<User, Role, int>
{

    public DbSet<Proposal> Proposal { get; set; }
    public DbSet<ProposalLeaveStatus> ProposalLeaveStatus { get; set; }
    public DbSet<ProposalLeaveStatuses> ProposalLeaveStatuses { get; set; }
    public DbSet<ProposalLeaveType> ProposalLeaveType { get; set; }


    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        // proposal hierarchy
        modelBuilder.Entity<Proposal>()
            .HasDiscriminator<string>("proposal_type")
            .HasValue<Proposal>("proposal_base")
            .HasValue<ProposalCustom>("proposal_custom")
            .HasValue<ProposalLeave>("proposal_leave");

        // proposal statuses many to many
        modelBuilder.Entity<ProposalLeaveStatuses>()
            .HasOne(pt => pt.Proposal)
            .WithMany(p => p.ProposalLeaveStatuses)
            .HasForeignKey(pt => pt.ProposalId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<ProposalLeaveStatuses>()
            .HasOne(pt => pt.ProposalLeaveStatus)
            .WithMany(t => t.ProposalLeaveStatuses)
            .HasForeignKey(pt => pt.ProposalLeaveStatusId);

        //Substantially, there are nothing about Proposal Leave Types, cause its relation one-to-many modeled using data annotations.
    }
}

I checked these models hundreds times and I don't know whats wrong. It's weird casuse I also have ProposalCustom:Proposal model contected with ProposalCustomType in the same way as ProposalLeave:Proposal to ProposalLeaveType and it works fine...

I discovered that if I add that line:

 var leaveTypes = _context.ProposalLeaveType.Include(plt => plt.ProposalLeaves).ToList();

before my query, it also works even with .Include(l=>l.LeaveType) without any exception...

I found a few bug issues on EF github referring to InvalidCastException, but all of them are marked as fixed in EF Core 1.1

@Update Here you can download VS2015 project that reproduce that problem: Project. Setup:

  1. Install .Net Core 1.1. sdk and runtime
  2. Create empty database and replace connection string in appsettings.json
  3. Run Update-Database in Package Manager Console
  4. Run project.
like image 773
Kuba Avatar asked Feb 03 '26 11:02

Kuba


1 Answers

Unfortunately all I can do is to confirm the obvious fact that the issue is caused by a EF Core bug.

I was able to trim down the repro to the following:

Model:

public class Proposal
{
    [Key]
    public int ProposalId { get; set; }

    [Required, Column(TypeName = "text")]
    public string Substantiation { get; set; }

    public DateTime DateCreated { get; set; }
}

public class ProposalCustom : Proposal
{
    [Required, StringLength(255)]
    public string Name { get; set; }

    public int ProposalTypeId { get; set; }

    [ForeignKey("ProposalTypeId")]
    public virtual ProposalCustomType ProposalType { get; set; }
}

public class ProposalCustomType
{
    [Key]
    public int ProposalTypeId { get; set; }

    [Required, StringLength(255)]
    public string Name { get; set; }

    [Required, Column(TypeName = "text")]
    public string Description { get; set; }

    public ICollection<ProposalCustom> ProposalCustoms { get; set; }
}

public class ProposalLeave : Proposal
{
    public DateTime LeaveStart { get; set; }

    public DateTime LeaveEnd { get; set; }

    public int ProposalLeaveTypeId { get; set; }

    [ForeignKey("ProposalLeaveTypeId")]
    public virtual ProposalLeaveType LeaveType { get; set; }
}

public class ProposalLeaveType
{
    [Key]
    public int LeaveTypeId { get; set; }

    [Required, StringLength(255)]
    public string Name { get; set; }

    [Required, Column(TypeName = "text")]
    public string Description { get; set; }

    public ICollection<ProposalLeave> ProposalLeaves { get; set; }
}

DbContext:

public DbSet<Proposal> Proposal { get; set; }
public DbSet<ProposalCustomType> ProposalCustomType { get; set; }
public DbSet<ProposalLeaveType> ProposalLeaveType { get; set; }

Configuration:

modelBuilder.Entity<Proposal>()
    .HasDiscriminator<string>("proposal_type")
    .HasValue<Proposal>("proposal_base")
    .HasValue<ProposalCustom>("proposal_custom")
    .HasValue<ProposalLeave>("proposal_leave");

Data:

db.Proposal.Add(new ProposalLeave
{
    Substantiation = "S1",
    DateCreated = DateTime.Today,
    LeaveStart = DateTime.Today,
    LeaveEnd = DateTime.Today,
    LeaveType = new ProposalLeaveType { Name = "PLT1", Description = "PLT1" }
});
db.Proposal.Add(new ProposalCustom
{
    Substantiation = "S2",
    Name = "PC1",
    ProposalType = new ProposalCustomType { Name = "PCT1", Description = "PCT1" }
});
db.SaveChanges();

Queries:

var proposalCustoms = db.Proposal.OfType<ProposalCustom>()
    .Include(l => l.ProposalType)
    .ToList();

var proposalLeaves = db.Proposal.OfType<ProposalLeave>()
    .Include(l => l.LeaveType)
    .ToList();

Althought the ProposalCustom and ProposalLeave looks similar, the first query always works, the second doesn't (it does under some circumstances as you will see below).

Why I'm sure it is a bug. First, because there is nothing wrong with the query and/or the model/configuration. Looks like it's caused by a combination of a several factors, starting with TPH, number and type of the derived class properties and (hmm) the class names (alphabetical order?) of the derived entities.

For instance, if there is no LeaveEnd property, the second query works. And the funniest part (depending of the view of course) is that if you rename ProposalCustom class to ProposalXCustom, or ProposalLeave to ProposalALeave, in other words, make the problematic entity class name first in the alphabetical order (I know it sounds crazy), without any database structure / mapping change, the query works!

I would suggest you reporting it to the EF Core repository. Substantiation column and Required / Column attributes doesn't seem to affect the behavior. Also, note that the first query always works, and the order of execution doesn't matter.

like image 92
Ivan Stoev Avatar answered Feb 05 '26 23:02

Ivan Stoev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!