EF TPT inheritance and self references - c#

I have the following structure of EF entities classes:
public abstract class CalendarItem
{
public Guid CalendarItemId { get; set; }
public Guid? ParentCalendarItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual CalendarItem ParentCalendarItem { get; set; }
public virtual ICollection<CalendarItem> ChildCalendarItems { get; set; }
}
public class CalendarSlot : CalendarItem
{
public bool IsAvailable { get; set; }
}
public class CalendarSession : CalendarEvent
{
public DateTimeOffset StartDateTime { get; set; }
public DateTimeOffset EndDateTime { get; set; }
}
Mapping is configured as follows:
public class CalendarItemMap
{
public static void Map(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CalendarItem>().ToTable("CalendarItem");
modelBuilder.Entity<CalendarItem>().HasKey(t => t.CalendarItemId);
modelBuilder.Entity<CalendarItem>().HasOne(ci => ci.ParentCalendarItem)
.WithMany(cs => cs.ChildCalendarItems).HasForeignKey(cs => cs.ParentCalendarItemId);
}
}
With DbSets as:
public DbSet<CalendarItem> CalendarItems { get; set; }
public DbSet<CalendarSession> CalendarSessions { get; set; }
public DbSet<CalendarSlot> CalendarSlots { get; set; }
My question is how to properly organize self-reference (tree) structure of the base class with inheritance provided. All types of entities, namely CalendarSlot and CalendarSession should have references to themselves, CalendarItem cannot be created as an instance.
In the database the tree structure is implemented in the CalendarItem table. CalendarSlot/CalendarSession tables have 1-1 relationship with CalendarItem.
The problem is that in my code I have a place with generic method which converts models to contracts for the API:
internal static TContract ToCalendarItem<TContract, TModel>(TModel calendarItemModel)
where TContract : Contract.CalendarItem, new()
where TModel : Model.CalendarItem
{
return new()
{
CalendarItemId = calendarItemModel.CalendarItemId,
Name = calendarItemModel.Name,
Description = calendarItemModel.Description,
ParentCalendarItem = calendarItemModel.ParentCalendarItem != null ? ToCalendarItem<TContract, TModel>(calendarItemModel.ParentCalendarItem) : null
};
}
and in the line
ParentCalendarItem = calendarItemModel.ParentCalendarItem != null ? ToCalendarItem<TContract, TModel>(calendarItemModel.ParentCalendarItem) : null
I have an error "Argument 1: Cannot convert from 'Model.CalendarItem' to TModel".
How can I tell the system that calendarItemModel.ParentCalendarItem should always be TModel (a class derived from Model.CalendarItem)?
What I am doing wrong here?
Thank you in advance.

Related

Automapper: Cannot create instance of abstract type for collections

I am trying to map two entities with a many to many relationship that inherit from an abstract base class into Dtos that also inherit from their own abstract base class. When I only include the mapping for the Essay class everything works fine except that the Books collection is null, as soon as I add the mapping for that collection I get the following exception:
Inner Exception 1: ArgumentException: Cannot create an instance of
abstract type Dtos.Dto`1[System.Int64].
Consider the following code:
namespace Entities
{
public abstract class Entity<TId> : Entity
where TId : struct, IEquatable<TId>
{
protected Entity()
{
}
public TId Id { get; set; }
}
public class Essay : Entity<long>
{
public string Name { get; set; }
public string Author { get; set; }
public List<EssayBook> EssayBooks { get; set; }
}
public class Book : Entity<long>
{
public string BookName { get; set; }
public string Publisher { get; set; }
public List<EssayBook> EssayBooks { get; set; }
}
public class EssayBook
{
public long EssayId { get; set; }
public long BookId { get; set; }
public Essay Essay { get; set; }
public Book Book { get; set; }
}
}
namespace Dtos
{
public abstract class Dto<TId>
where TId : struct, IEquatable<TId>
{
protected Dto()
{
}
public TId Id { get; set; }
}
public sealed class Essay : Dto<long>
{
public string Name { get; set; }
public string Author { get; set; }
public List<Book> Books { get; set; }
}
public class Book : Dto<long>
{
public string BookName { get; set; }
public string Publisher { get; set; }
}
}
namespace DtoMapping
{
internal sealed class EssayBookProfile : Profile
{
public EssayBookProfile()
{
this.CreateMap<Entities.Essay, Dtos.Essay>()
.IncludeBase<Entities.Entity<long>, Dtos.Dto<long>>()
.ForMember(dto => dto.Books, opt => opt.MapFrom(e => e.EssayBooks.Select(pl => pl.Book)));
}
}
}
I've been looking to see if there is a different way to configure this mapping but I always find this way. I have also tried to specifically add the mappings for the base classes but I get the exact same result.
On my Web API project I have included the AutoMapper.Extensions.Microsoft.DependendyInjection package version 7.0.0.
As I was creating the gist by the recommendation in the post's comments I realized that the mapping for the Book dto was missing. Unfortunately the exception was not clear about the problem and that took me to ask the question here. After I added the mapping everything started working as expected.

AutoMapper ConvertUsing is not being called

I want to transform two properties of base classes using ConvertUsing, but it is not being called
Base classes
public abstract class BaseDadoMestreViewModel
{
public DateTime UltimaAtualizacao { get; set; }
public bool Excluido { get; set; }
public bool Ativo { get; set; }
}
public abstract class BaseDadoMestre<TEntity> : EntityCrud<TEntity>
where TEntity : class
{
public DateTime UltimaAtualizacao { get; set; }
public string MarcadoEliminacao { get; set; }
public bool Desabilitado { get; set; }
}
Classes
public class CalendarioViewModel: BaseDadoMestreViewModel
{
public DateTime Data { get; set; }
}
public class CalendarioDTO: BaseDadoMestre<CalendarioDTO>
{
public DateTime Data { get; set; }
}
ITypeConverter
public class BaseConverter<TEntity> :
ITypeConverter<BaseDadoMestre<TEntity>, BaseDadoMestreViewModel>,
ITypeConverter<BaseDadoMestreViewModel, BaseDadoMestre<TEntity>>
where TEntity : BaseDadoMestre<TEntity>
{
public BaseDadoMestreViewModel Convert(BaseDadoMestre<TEntity> source, BaseDadoMestreViewModel destination, ResolutionContext context)
{
destination.Excluido = (source.MarcadoEliminacao== "X");
destination.Ativo = !source.Desabilitado;
return destination;
}
public BaseDadoMestre<TEntity> Convert(BaseDadoMestreViewModel source, BaseDadoMestre<TEntity> destination, ResolutionContext context)
{
destination.MarcadoEliminacao = (source.Excluido ? null : "X");
destination.Desabilitado = !source.Ativo;
return destination;
}
}
And finally my map:
CreateMap(typeof(BaseDadoMestre<>), typeof(BaseDadoMestreViewModel)).ConvertUsing(typeof(BaseConverter<>));
CreateMap(typeof(BaseDadoMestreViewModel), typeof(BaseDadoMestre<>)).ConvertUsing(typeof(BaseConverter<>));
CreateMap<CalendarioDTO, CalendarioViewModel>()
When I run the mapping command ConvertUsing is not called.
mapper.Map<CalendarioViewModel>(new CalendarioDTO() { MarcadoEliminacao = "X", Desabilitado = true });
As from Automapper docs you need to either specify the inheritance when creating the mapping for base classes either from derived.
Check this one:
http://docs.automapper.org/en/stable/Mapping-inheritance.html

Entity Framework query with simple join

I am using Entity Framework 6 in a project and am having trouble creating a query.
Say my classes are defined like:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString)
{
}
public DbSet<EntityXXX> XXXSet { get; set; }
public DbSet<EntityYYY> YYYSet { get; set; }
}
public class EntityXXX
{
public string XXXName { get; set; }
public int id { get; set; }
public int YYYid { get; set; }
}
public class EntityYYY
{
public string YYYName { get; set; }
public int id { get; set; }
}
The YYYid property of EntityXXX is the 'id' of the EntityYYY instance that it relates to.
I want to be able to fill a Grid with rows where the first Column is XXXName and the second column is YYYName (from its related EntityYYY), but I can't see how to do this?
I'm sure it's really simple, but I'm new to EF.
You need to put a virtual navigation property on your EntityXXX
public virtual EntityYYY YYY { get; set; }
Then you can do a projection:
db.XXXSet
.Select(x => new { x.XXXName, YYYName = x.YYY.YYYName })
.ToList();
Which will get you the list you need.

C# Entity Framework Code First Abstract Inheritance

In the following classes I am trying to do a code first setup using abstract base classes. Sorry if this is a duplicate question. I did try to search the site and google for an answer but nothing I have tried has worked.
public abstract class IDObject
{
[Key]
public Int32 ID { get; internal set; }
}
public abstract class NamedObject : IDObject
{
public String Name { get; set; }
}
public abstract class HWObject : NamedObject
{
public Double Free { get; set; }
public Double Max { get; set; }
public Double Used { get; set; }
}
public abstract class CPUObject : HWObject { }
public abstract class MemoryObject : HWObject { }
public abstract class StorageObject : HWObject { }
public class Server : NamedObject
{
[JsonConstructor]
internal Server() { }
public String CompanyName { get; set; }
public Boolean IsRunning { get; set; }
public CPUObject CPU { get; set; }
public MemoryObject Memory { get; set; }
public StorageObject Storage { get; set; }
}
I have the following DBContext:
public class DataContext : DbContext
{
public DataContext() : base("name=DataContext") { }
public DbSet<Server> Servers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
When I try to view the Read-Only Data Model I get the following error:
System.InvalidOperationException: The abstract type 'MyApp.CPUObject' has no mapped descendants and so cannot be mapped. Either remove 'MyApp.CPUObject' from the model or add one or more types deriving from 'MyApp.CPUObject' to the model.
How do I change the DBContext so the error goes away?
EDIT:
According to the answer below I have removed abstract from the CPUObject, MemoryObject and StorageObject as they should have never been abstract.
I have changed the DBContext to the following: (The mappings create a 1 to 1 relationship)
public class DataContext : DbContext
{
public DataContext() : base("name=DataContext") { }
public DbSet<Server> Servers { get; set; }
public DbSet<CPUObject> CPUObjects { get; set; }
public DbSet<MemoryObject> MemoryObjects { get; set; }
public DbSet<StorageObject> StorageObjects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Server>().HasRequired(u => u.CPU).WithRequiredDependent().Map(m => m.MapKey("CPUID"));
modelBuilder.Entity<Server>().HasRequired(u => u.Memory).WithRequiredDependent().Map(m => m.MapKey("MemoryID"));
modelBuilder.Entity<Server>().HasRequired(u => u.Storage).WithRequiredDependent().Map(m => m.MapKey("StorageID"));
}
}
CPUObject is currently an abstract class, which cannot be instantiated. Just remove the abstract to make it a concrete object. Then, add public DbSet<CPUObject> CPUs { get; set; } to your DBContext
You are currently getting the error because EF is looking for a concrete class, so the error says you can make another concrete class that inherits (i.e. 'deriving from') CPUObject

EF 5 Mixed Inheritance Types with Multi Layer Inheritance

I am having an issue using entity framework to map an existing database to a code base. I can't seem to find any code that has done anything similar either. I need to have multi-layer inheritance and I need the layers to use two different mapping types. I am having some strange behaviour though. To reproduce the behaviour I have created a simple model and allowed EF to map it the way it wishes.
My entities are as follows:
public abstract class Root
{
public int RootId { get; set; }
public string RootProperty { get; set; }
}
public abstract class ChildA : Root
{
public string ChildAProperty { get; set; }
}
public class ChildB : Root
{
public string ChildBProperty { get; set; }
}
public class SubChildAa : ChildA
{
public string SubChildAaProperty { get; set; }
}
public class SubChildAb : ChildA
{
public string SubChildAbProperty { get; set; }
}
This results in the following schema:
My context is as follows:
class MyContext : DbContext
{
public DbSet<SubChildAa> ChildAas { get; set; }
public DbSet<SubChildAb> ChildAbs { get; set; }
public DbSet<ChildB> ChildBs { get; set; }
public MyContext()
{
//Database.SetInitializer<MyContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Root>().ToTable("Root");
modelBuilder.Entity<ChildB>().ToTable("ChildB");
modelBuilder.Entity<ChildA>().ToTable("ChildA");
modelBuilder.Entity<ChildA>().Map<SubChildAa>(u => u.Requires("ChildAType").HasValue("SubChildAa"));
modelBuilder.Entity<ChildA>().Map<SubChildAb>(u => u.Requires("ChildAType").HasValue("SubChildAb"));
//base.OnModelCreating(modelBuilder);
}
}
I am trying to use TPT for the Root to Child (A or B) mapping and TPH for ChildA to SubChild. The main problem is that EF is putting the discriminator on the Root object. It is also putting the ChildA properties there, which I don't think is correct, but is less of a problem for now. With the discriminator being on the Root, it causes an error because ChildB does not map to the types in that discriminator.
Can someone suggest a way in which I can have the ChildA table actualLy maintain its own properties and discriminator without pushing them up to the root table.
Something like this
public abstract class Root
{
[Key]
public int RootId { get; set; }
public string RootProperty { get; set; }
}
public class ChildA : Root
{
[Required]
int RootId
ForeignKey["RootId"]
public Root Root {get;set;}
public string ChildAProperty { get; set; }
}
public class ChildB : Root {
[Required]
int RootId
ForeignKey["RootId"]
public Root Root {get;set;}
public string ChildBProperty { get; set; }
}
public class SubChildAa : ChildA
{
public string SubChildAaProperty { get; set; }
}
public class SubChildAb : ChildA
{
public string SubChildAbProperty { get; set; }
}
In your context you want TPT for ChildA and ChildB
So
public DbSet<ChildA> ChildAas { get; set; }
public DbSet<ChildB> ChildBs { get; set; }
TPH for subchilds
public DbSet<SubChild> Childs { get; set; }

Categories