0

I have an abstract base class that is foundation for all derived (~100) classes. This base class should not be saved to the database but its properties should be exposed as columns in the derived classes -> entity models. In the Db context code (OnModel Creation etc), there is NO mention of BaseEntity - only of its derived classes.

In 3.0.1, this worked fine. BaseEntity was ignored and only mentioned classes (via DbSet or in the OnModelxxx) were configured as TPH.

System.InvalidOperationException : Cannot create a DbSet for 'BaseEntity' because this type is not included in the model for the context.

How do I get back to my previous behavior?


My base class:

public abstract class BaseEntity 
    {
        [Key]
        [GraphQLName("id")]
        [GraphQLNonNullType(IsNullable = true)]
        public Guid Identifier { get; set; } = Guid.NewGuid();

        [GraphQLNonNullType(IsNullable = true)]
        public DateTime CreatedDate { get; set; } = DateTime.UtcNow;

        [GraphQLNonNullType(IsNullable = true)]
        public DateTime LastModifiedDate { get; set; } = DateTime.UtcNow;

        [GraphQLNonNullType(IsNullable = true)]
        public DateTime DisplayDate { get; set; } = DateTime.UtcNow;


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

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

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

        public string TypeName { get; set; }
}

.....

Examples Derived classes:

public class Document : BaseEntity
    {
        [GraphQLIgnore]
        public Organization Organization { get; set; }

        [GraphQLNonNullType(IsNullable = true)]
        public DocumentType DocumentType { get; set; }

        /// <summary>
        /// Mimetype of the document
        /// </summary>
        [GraphQLName("type")]
        [GraphQLNonNullType(IsNullable = true)]
        public string MimeType { get; set; }
}

Save Code - uses BaseEntity - throws exception on SaveChangesAsync()

    public async Task<List<T>> Upsert<T>(List<T> baseEntities, bool isRaiseBaseEntityChangeEvent = true) where T : class
            {
                var entities = new HashSet<BaseEntity>();
    
                foreach (var b in baseEntities)
                {
                    entities.Add(b as BaseEntity);
                    TraverseGraph(b as BaseEntity, ref entities);
                }
    
                using (var context = GetDbInstance())
                {
                    try
                    {
                        foreach (var e in entities)
                        {
                            //exists in db
                            if (e.IsPersistedToDatabase == true)
                            {
    
                                context.Attach(e);
                                foreach (var entry in context.Entry(e).Properties)
                                {
                                    try
                                    {
                                        if (entry.Metadata.Name.Equals("CreatedDate"))
                                        {
                                            // Don't allow changing the CreatedDate on existing objects.
                                            entry.IsModified = false;
                                        }
                                        else if (entry.CurrentValue != null && !entry.Metadata.IsPrimaryKey())
                                        {
                                            entry.IsModified = true;
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        System.Diagnostics.Trace.WriteLine(ex.Message);
                                        continue;
                                    }
                                }
    
                            }
                            else
                            {
                                e.IsPersistedToDatabase = true;
                                context.Set<BaseEntity>().Add(e);
                            }
                        }
    
                        //make a copy for the change event
    
                        List<BaseEntityEntry> baseEntries = new List<BaseEntityEntry>();
                        foreach (var e in context.ChangeTracker.Entries())
                            baseEntries.Add(new BaseEntityEntry()
                            {
                                Entity = e.Entity as BaseEntity,
                                State = e.State
                            });
    
                        await context.SaveChangesAsync();
    
                        #region BaseEntity change event raising
                        if (isRaiseBaseEntityChangeEvent)
                        {
                            EventsPublisher.Instance.SignalEntityChange(new BaseEntityChangeEventArgs()
                            {
                                Entities = baseEntries
    
                            });
                        }
                        #endregion
    
                        return baseEntities;
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Trace.WriteLine(ex.Message);
                        throw ex;
                    }
                }
            }
  • Show the code which generates the exception in question. – Ivan Stoev Dec 08 '20 at 21:36
  • It's a bit special purpose - key thing is that all derived classes are referenced as the base abstract class during the save:`code` public async Task> Upsert(List baseEntities, bool isRaiseBaseEntityChangeEvent = true) where T : class `code` – Chris Cardinal Dec 09 '20 at 00:05
  • 1
    I doubt this code works in previous EFC versions, but it really doesn't matter because `Set()` is not a correct call. Replace `context.Set().Add(e);` with just `context.Add(e);`. – Ivan Stoev Dec 09 '20 at 03:32
  • @ChrisCardinal Ivan Stoev is correct..... – Seabizkit Dec 10 '20 at 07:41
  • I just added a Q/A that addresses this. See https://stackoverflow.com/questions/65778874/how-to-automatically-map-tph-derived-classes-in-ef-core – WillC Jan 18 '21 at 17:02
  • @willC - does the base, abstract class (BaseEntity) props appear as columns in the resulting table? My problem was that they would not (using just Add(e)) OR complain about the missing BaseEntity entity in DbSet. – Chris Cardinal Jan 19 '21 at 19:27
  • Yes, the base, abstract columns are being stored in the DB in a table for the abstract class with a discriminator column for the sub type. Not sure if this is the same issue but I was getting that same error as you because the sub types were not being recognized by EFCore when I mapped the base class. In my case I have the base class as a DBSet and map the sub types with .HasDiscriminator().HasValue() – WillC Jan 19 '21 at 20:11

1 Answers1

0
ontext.Set<BaseEntity>()

is definitely wrong.

either use

context.Add(e);

or

context.Set<{ConcreteType}>.Add(e);
CleanCoder
  • 2,406
  • 1
  • 10
  • 35