Jean-Louis Arnal
Jean-Louis Arnal

Reputation: 11

DbContext's DbSet<> member throws InvalidCastException upon DbSet.ToArray(), trying to cast to another declared entity type

I'm using EntityFrameworkCore 7.0 in conjunction with Pomelo.EntityFrameworkCore.MySql to build a Code-First app relying on MySQL.

I'm using the Fluent Api and IEntityTypeConfiguration<> implementations for each and every entity class there is in my model assembly.

At one point, my DbContext declares this property:

public class SassyDbContext : DbContext
{
   private DbSet<SassyUser> SassyUsers { get; set; }
}

It's supposed to expose objects of type SassyUser, defined as such :

public class SassyUser : IdentifiedObjectBase // abstract implementation of the Id PK.
    {
        private DateTime? _creationDate = null;
        private static readonly byte[] __ = new byte[] { 0xd2, 0xc2, 0xd6, 0xcd, 0xb0, 0x1d, 0x4b, 0x80, 0xbe, 0x60, 0x54, 0x82, 0xb1, 0xfa, 0x9a, 0xc3 };


        public SassyUser() { }


        public string FirstName { get; set; } = string.Empty;
        public string LastName { get; set; } = string.Empty;

        public string Pseudonym {
            get => ModelUtils.DeScrambleString(__, PseudonymEnc);
            set => PseudonymEnc = ModelUtils.ScrambleString(__, value);
        }

        private ICollection<AnnotatedAsset> AuthoredAssets { get; set; } = new HashSet<AnnotatedAsset>();
        private ICollection<AnnotatedAsset> RevisedAssets { get; set; } = new HashSet<AnnotatedAsset>();

        private ICollection<TraitBase> AuthoredTraits { get; set; } = new HashSet<TraitBase>();
        private ICollection<TraitBase> RevisedTraits { get; set; } = new HashSet<TraitBase>();


        private ICollection<AssetImportSession> ImportSessions { get; set; } = new HashSet<AssetImportSession>();

        public string PseudonymEnc { get; set; } = string.Empty;



        public string Password {
            get => ModelUtils.DeScrambleString(__, PasswordEnc);
            set => PasswordEnc = ModelUtils.ScrambleString(__, value);
        }

        public string PasswordEnc { get; set; } = string.Empty;


        public string? Email {
            get => ModelUtils.DeScrambleString(__, EmailEnc!);
            set => EmailEnc = ModelUtils.ScrambleString(__, value!);
        }
        public string? EmailEnc { get; set; } = null;


        public DateTime? CreationDate {
            get {
                if (_creationDate is null)
                    _creationDate = DateTime.Now.ToUniversalTime();
                return _creationDate;
            }
            set {
                if (value is not null)
                    _creationDate = value.Value.ToUniversalTime();
            }
        }


        public Privileges GlobalPrivileges { get; set; } = Privileges.None;


        public bool CanAddAssets(SassyDataset? dataset)
        {
            throw new NotImplementedException();
        }

        public bool CanEdit(AnnotatedAsset asset)
            => CanEdit(asset?.Id ?? null);

        public bool CanEdit(long? assetId)
        {
            throw new NotImplementedException();
        }


    }

And here is its configurator implementation :

 internal class SassyUserConfig : IEntityTypeConfiguration<SassyUser>
    {
        public SassyUserConfig() { }

        public void Configure(EntityTypeBuilder<SassyUser> builder)
        {

            builder.Ignore(e => e.Pseudonym);
            builder.Ignore(e => e.Password);
            builder.Ignore(e => e.Email);
            builder.Ignore("_creationDate");


            builder
                .Property(e => e.CreationDate)
                .HasField("_creationDate")
                .UsePropertyAccessMode(PropertyAccessMode.Property)
                .IsRequired(true);


            builder
                .Property(e => e.EmailEnc)
                .IsUnicode(false)
                .HasColumnName("email")
                .HasCharSet("ASCII")
                .UseCollation("ascii_general_ci")
                .IsRequired(false);

            builder
                .Property(e => e.FirstName)
                .IsUnicode()
                .IsRequired(false);

            builder
                .Property(e => e.LastName)
                .IsUnicode()
                .IsRequired(false);

            builder
                .Property(e => e.GlobalPrivileges)
                .HasConversion<PrivilegeUlongConverter>()
                .HasDefaultValue(Privileges.None)
                .IsRequired(true);

            builder
                .Property(e => e.PseudonymEnc)
                .IsUnicode(false)
                .HasColumnName("jlgkj")
                .HasCharSet("ASCII")
                .UseCollation("ascii_general_ci")
                .IsRequired();

            builder
              .Property(e => e.PasswordEnc)
              .HasColumnName("fdsqqf")
              .IsUnicode(false)
              .HasCharSet("ASCII")
              .UseCollation("ascii_general_ci")
              .IsRequired();

            builder
                .UseTpcMappingStrategy()
                .ToTable("SassyUsers");

        }
    }

But here's the problem... This DbContext method:

    public SassyUser GetUser(string uide, string pwde)
            => (from usr in SassyUsers 
                where usr.PseudonymEnc == uide && usr.PasswordEnc == pwde 
                select usr).FirstOrDefault()!;

Throws an InvalidCastException upon return of the query, stating:

System.InvalidCastException
  HResult=0x80004002
  Message=Unable to cast object of type 'Sassy.Model.SassyDataset' to type 'Sassy.Model.SassyUser'.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext() in /_/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs:line 179
   at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found) in /_/src/libraries/System.Linq/src/System/Linq/Single.cs:line 88
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) in /_/src/EFCore/Query/Internal/QueryCompiler.cs:line 68
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) in /_/src/EFCore/Query/Internal/EntityQueryProvider.cs:line 63
   at Sassy.Model.SassyDbContext.GetUser(String uide, String pwde) in S:\sources\C#\Sassy_WPF\SassyDataModel\SassyDbContext.cs:line 262
   at Sassy.AssetsExporter.ExportWorkhorse._worker_DoWork(Object sender, DoWorkEventArgs e) in S:\sources\C#\Sassy_WPF\SassyAssetExporter\ExportWorkhorse.cs:line 169

  This exception was originally thrown at this call stack:
    Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable<T>.Enumerator.MoveNext() in SingleQueryingEnumerable.cs
    System.Linq.Enumerable.TryGetSingle<TSource>(System.Collections.Generic.IEnumerable<TSource>, out bool) in Single.cs
    Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute<TResult>(System.Linq.Expressions.Expression) in QueryCompiler.cs
    Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute<TResult>(System.Linq.Expressions.Expression) in EntityQueryProvider.cs
    Sassy.Model.SassyDbContext.GetUser(string, string) in SassyDbContext.cs
    Sassy.AssetsExporter.ExportWorkhorse._worker_DoWork(object, System.ComponentModel.DoWorkEventArgs) in ExportWorkhorse.cs

How by the devil should a DbSet<SassyUser> try to cast its queries results to SassyDataset when the query result is explicitely typed to SassyUser ? None of those classes inherit each other. They both inherit IdentifiedObjectBase, which is an abstract class that declares and exposes one member, namely:

long? IdentifiedObjectBase.Id {get; set;}

I'm stumped ! Please help !

I fist thought I had mislabeled my tables... But nope... As you can see in the SassyUserConfig class, the table is explicitely named after the entity class 'SassyUser' -> 'SassyUsers' .

Upon successful creation of the schema after a quick SassyDbContext.Database.EnsureCreated(), I add a single SassyUser entity to the database. No the Db report a new row in the SassyUsers table. Nice.

But the DbSet<SassyUser> SassydbContext.SassyUsers property keeps spouting InvalidCastExceptions upon query returns.

I also tried to call the SassydbContext.SassyUsers.ToArray() extension method on the incriminated DbSet : same effect. The problem lies withing a misnaming inside the model itself.

Here's a debug trace of EF on this :

dbug: 26/06/2023 21:45:07.048 CoreEventId.QueryCompilationStarting[10111] (Microsoft.EntityFrameworkCore.Query) 
      Compiling query expression: 
      'DbSet<SassyUser>()'
dbug: 26/06/2023 21:45:07.058 CoreEventId.QueryExecutionPlanned[10107] (Microsoft.EntityFrameworkCore.Query) 
      Generated query execution expression: 
      'queryContext => new SingleQueryingEnumerable<SassyUser>(
          (RelationalQueryContext)queryContext, 
          RelationalCommandCache.QueryExpression(
              Projection Mapping:
                  EmptyProjectionMember -> Dictionary<IProperty, int> { [Property: IdentifiedObjectBase.Id (long?) Required PK AfterSave:Throw ValueGenerated.OnAdd, 0], [Property: SassyUser.CreationDate (_creationDate, DateTime?) Required PropertyAccessMode.Property, 1], [Property: SassyUser.EmailEnc (string) Ansi, 2], [Property: SassyUser.FirstName (string), 3], [Property: SassyUser.GlobalPrivileges (Privileges) Required ValueGenerated.OnAdd, 4], [Property: SassyUser.LastName (string), 5], [Property: SassyUser.PasswordEnc (string) Required Ansi, 6], [Property: SassyUser.PseudonymEnc (string) Required Ansi, 7] }
              SELECT s.Id, s.CreationDate, s.email, s.FirstName, s.GlobalPrivileges, s.LastName, s.fdsqqf, s.jlgkj
              FROM SassyUsers AS s), 
          ReaderColumn[] { ReaderColumn<long>, ReaderColumn<DateTime>, ReaderColumn<object>, ReaderColumn<object>, ReaderColumn<ulong>, ReaderColumn<object>, ReaderColumn<object>, ReaderColumn<object> }, 
          Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, SassyUser>, 
          Sassy.Model.SassyDbContext, 
          False, 
          True, 
          True
      )'
dbug: 26/06/2023 21:45:07.061 RelationalEventId.CommandCreating[20103] (Microsoft.EntityFrameworkCore.Database.Command) 
      Creating DbCommand for 'ExecuteReader'.
dbug: 26/06/2023 21:45:07.064 RelationalEventId.CommandCreated[20104] (Microsoft.EntityFrameworkCore.Database.Command) 
      Created DbCommand for 'ExecuteReader' (3ms).
dbug: 26/06/2023 21:45:07.067 RelationalEventId.CommandInitialized[20106] (Microsoft.EntityFrameworkCore.Database.Command) 
      Initialized DbCommand for 'ExecuteReader' (5ms).
dbug: 26/06/2023 21:45:07.069 RelationalEventId.ConnectionOpening[20000] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Opening connection to database 'sassyDb' on server 'localhost'.
dbug: 26/06/2023 21:45:07.072 RelationalEventId.ConnectionOpened[20001] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Opened connection to database 'sassyDb' on server 'localhost'.
dbug: 26/06/2023 21:45:07.074 RelationalEventId.CommandExecuting[20100] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT `s`.`Id`, `s`.`CreationDate`, `s`.`email`, `s`.`FirstName`, `s`.`GlobalPrivileges`, `s`.`LastName`, `s`.`fdsqqf`, `s`.`jlgkj`
      FROM `SassyUsers` AS `s`
info: 26/06/2023 21:45:07.076 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT `s`.`Id`, `s`.`CreationDate`, `s`.`email`, `s`.`FirstName`, `s`.`GlobalPrivileges`, `s`.`LastName`, `s`.`fdsqqf`, `s`.`jlgkj`
      FROM `SassyUsers` AS `s`
Exception thrown: 'System.InvalidCastException' in System.Private.CoreLib.dll
'SassyAssetsExporter.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\7.0.5\System.Reflection.Metadata.dll'. Symbols loaded.
fail: 26/06/2023 21:45:07.131 CoreEventId.QueryIterationFailed[10100] (Microsoft.EntityFrameworkCore.Query) 
      An exception occurred while iterating over the results of a query for context type 'Sassy.Model.SassyDbContext'.
      System.InvalidCastException: Unable to cast object of type 'Sassy.Model.SassyDataset' to type 'Sassy.Model.SassyUser'.
         at lambda_method247(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
Exception thrown: 'System.InvalidCastException' in Microsoft.EntityFrameworkCore.Relational.dll
dbug: 26/06/2023 21:45:07.162 RelationalEventId.DataReaderClosing[20301] (Microsoft.EntityFrameworkCore.Database.Command) 
      Closing data reader to 'sassyDb' on server 'localhost'.
dbug: 26/06/2023 21:45:07.164 RelationalEventId.DataReaderDisposing[20300] (Microsoft.EntityFrameworkCore.Database.Command) 
      A data reader for 'sassyDb' on server 'localhost' is being disposed after spending 83ms reading results.
dbug: 26/06/2023 21:45:07.166 RelationalEventId.ConnectionClosing[20002] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Closing connection to database 'sassyDb' on server 'localhost'.
dbug: 26/06/2023 21:45:07.168 RelationalEventId.ConnectionClosed[20003] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Closed connection to database 'sassyDb' on server 'localhost' (2ms).

Why would this "lambda_method247" would return a SassyDataset entity ? Did EF try a failsafe cast to another Entity type because the SassyUser entity was impossible to populate ? What ? Huh ? My head hurts.

Even the SQL query displayed in the debug output seems okay !

Is there a way to sniff out what EF is doing under the hood with the DbSet query ?

Upvotes: 0

Views: 66

Answers (0)

Related Questions