Implementing interface causes TPT where as inheriting from class causes TPH - ef-code-first

I want to create a self referencing folder table , and related file table
As shown by MyFolder1 and MyFile1.
I understand how to get this structure by inheriting from an interface.
Can it also be achieved by inheriting from an abstract class?
public class Model1Context : DbContext
{
public Model1Context()
: base("name=Model1")
{
Database.SetInitializer(new Model1Initializer());
}
public virtual DbSet<MyFolder1> MyFolder1s { get; set; }
public virtual DbSet<MyFile1> MyFile1s { get; set; }
public virtual DbSet<MyFolder2> MyFolder2s { get; set; }
public virtual DbSet<MyFile2> MyFile2s { get; set; }
}
public class Model1Initializer : DropCreateDatabaseIfModelChanges<Model1Context>
{
}
public class MyFolder1 : BasicBO, ISequencedTreeNode
{
public MyFolder1 MyFolder { get; set; }
public int SeqNo { get; set; }
[NotMapped]
public ISequencedTreeNode Parent
{
get
{
return MyFolder;
}
set
{
MyFolder = (MyFolder1)value;
}
}
}
public class MyFile1 : BasicBO, ISequencedTreeNode
{
public MyFolder1 MyFolder { get; set; }
public int SeqNo { get; set; }
[NotMapped]
public ISequencedTreeNode Parent
{
get
{
return MyFolder;
}
set
{
MyFolder = (MyFolder1)value;
}
}
}
public interface ISequencedTreeNode
{
[Required]
int SeqNo { get; set; }
ISequencedTreeNode Parent { get; set; }
}
public abstract class SequencedTreeNode : BasicBO
{
[Required]
public int SeqNo { get; set; }
public SequencedTreeNode Parent { get; set; }
}
// [Table("MyFolder2")]
public class MyFolder2 : SequencedTreeNode
{
}
// [Table("MyFile2")]
public class MyFile2 : SequencedTreeNode
{
}
public class BasicBO
{
public int Id { get; set; }
public string Name { get; set; }
}
Marking BasicBO as abstract does not seem to affect the table construction.
[Update]
I can force TPT by adding a [Table('tablename')] attribute to MyFile2 and MyFolder2, however these tables will have a single Id field and the SequencedTreeNodes table will still create.
It makes sense to me that an interface may be more effective in forcing TPT since properties always need to be implemented for an interface. However I would have thought the same thing would happen if I made all the properties inside the SequencedTreeNode abstract. However If I do this I get a runtime error 'The property 'SeqNo' is not a declared property on type 'MyFile2''

Related

.NET Core 3.1 dbset model migration to multiple SQL tables

I'm trying a code first approach to creating multiple logging tables for a data archiver. The data that is being archived conforms to one model, but there are three different databases that the archiver queries data from. So I want to have three different SQL tables, one for each database.
ArchiveDataModel:
public class ArchiveDataModel
{
[Key]
public int ID { get; set; }
public string EventId { get; set; }
public string EventData { get; set; }
public string DateCreated { get; set; }
public string DateArchived { get; set; }
}
Here's my ApplicationDbContext:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<ArchiveDataModel> EventDataArchive_A { get; set; }
public DbSet<ArchiveDataModel> EventDataArchive_B { get; set; }
public DbSet<ArchiveDataModel> EventDataArchive_C { get; set; }
}
Running "add-migration" here creates a migration package for a table named "ArchiveDataModel." So I added this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ArchiveDataModel>()
.ToTable("EventDataArchive_A")
.ToTable("EventDataArchive_B")
.ToTable("EventDataArchive_C");
}
But this only creates a migration package for "EventDataArchive_C."
Would it be possible to use this one model to generate three identical tables via code-first approach?
Seems that both Fluent Api and Data Annotations could not solve this issue as far as these instruments target model class, not DbSet.
What you could do is to have a base class, 3 derived classes from a base class and 3 DbSet-s in your context each over corresponding derived model
public abstract class ArchiveDataModel
{
[Key]
public int ID { get; set; }
public string EventId { get; set; }
public string EventData { get; set; }
public string DateCreated { get; set; }
public string DateArchived { get; set; }
}
public class ArchiveDataModel_A : ArchiveDataModel { }
public class ArchiveDataModel_B : ArchiveDataModel { }
public class ArchiveDataModel_C : ArchiveDataModel { }
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{ }
public DbSet<ArchiveDataModel_A> EventDataArchive_A { get; set; }
public DbSet<ArchiveDataModel_B> EventDataArchive_B { get; set; }
public DbSet<ArchiveDataModel_C> EventDataArchive_C { get; set; }
}

Creating navigation code-first results in the error: does not declare a navigation property with the name

query.Include("Store_Location").Load();
throws:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: A specified Include path is not valid. The EntityType 'Model.Order' does not declare a navigation property with the name 'Store_Location'.
I used the following code in order to create the navigation code-first:
public partial class Order
{
public Nullable<int> Store_Location_ID { get; set; }
public virtual Store_Location Store_Location { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
}
public partial class Store_Location
{
public int ID { get; set; }
public virtual ICollection<Order> Orders { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
}
https://learn.microsoft.com/en-us/ef/ef6/fundamentals/relationships
Do I need to use the designer? Is there anything I need to do in order for the navigation to be created?
If you create your database with code first approach, then your entities should not be partial classes. Define them like this:
public class Order
{
public int? StoreLocationId { get; set; }
public virtual StoreLocation StoreLocation { get; set; }
}
public class StoreLocation
{
public int Id { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
Then you should create a DbContext class:
public class StoreDbContext : DbContext
{
public StoreDbContext(DbContextOptions<StoreDbContext> options) : base(options)
{
}
public virtual DbSet<StoreLocation> StoreLocations { get; set; }
public virtual DbSet<Order> Orders { get; set; }
}
After creating a context you can use the ef commands to create your database. You can read more about ef core here: https://learn.microsoft.com/en-us/ef/core/get-started/?tabs=netcore-cli
If you define your classes like I did above, you can include your navigation properties strongly typed like this:
query.Include(order => order.StoreLocation);

Map reference property fields to the same table as root class

I have the model with reference properties
internal class AstronomycalBody : IAstronomycalBody
{
public long Id { get; set; }
public string Name { get; set; }
public Coord Coord { get; set; }
public long Mass { get; set; }
public double Speed { get; set; }
public IAstronomycalBody CentralObject { get; set; }
}
public class Coord
{
public long X { get; set; }
public long Y { get; set; }
public long Z { get; set; }
}
I want to use mapping like this
internal class AstronomycalBodyContext : DbContext
{
public DbSet<AstronomycalBody> AstronomycalBody { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(DbSettings.ConnectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AstronomycalBody>().Property(p => p.Coord.X).ForSqliteHasColumnName("CoordX");
modelBuilder.Entity<AstronomycalBody>().Property(p => p.Coord.Y).ForSqliteHasColumnName("CoordY");
modelBuilder.Entity<AstronomycalBody>().Property(p => p.Coord.Z).ForSqliteHasColumnName("CoordZ");
modelBuilder.Entity<AstronomycalBody>().Property(p => p.CentralObject.Id).ForSqliteHasColumnName("CentralObjectId");
}
}
to map the model on this table:
Currently, the compiler is throwing this exception...
Your AstronomycalBody is not a valid EF entity model class.
First, EF Core does not support Complex/value types yet, so the Coord member should be expanded in place.
Second, EF does not work with interfaces, so every navigation reference / collection element type should be entity class.
With that being said, not sure how your IAstronomycalBody looks like and how you can implement it (you might need explicit implementation of some members), but the entity class should be like this:
internal class AstronomycalBody //: IAstronomycalBody
{
public long Id { get; set; }
public string Name { get; set; }
//public Coord Coord { get; set; }
public long CoordX { get; set; }
public long CoordY { get; set; }
public long CoordZ { get; set; }
public long Mass { get; set; }
public double Speed { get; set; }
public AstronomycalBody CentralObject { get; set; }
}
Now, since by convention it will generate the exact table shown, simply remove all shown lines in OnModelCreating and you are done.

How to include multiple child objects?

I'm playing with new ASP.Net 5.0 WebApi and strugling to understand how to return more then one child object, or child of the child.
Lets say I have 4 classes:
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int TypeId { get; set; }
public int ColourId { get; set; }
public virtual Type Type { get; set; }
public virtual Colour Colour { get; set; }
}
public class Type
{
public int Id { get; set; }
public string Name { get; set; }
public int TypeGroupId { get; set; }
public virtual TypeGroup TypeGroup { get; set; }
}
public class Colour
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TypeGroup
{
public int Id { get; set; }
public string Name { get; set; }
}
And would like to return all the data for the car including Type, Colour, and even TypeGroup of the Type. How do I Do it?
When I do like this it includes only Type:
[HttpGet]
public IEnumerable<Car> Get()
{
return _dbContext.Cars.Include(c => c.Type);
}
This is my setup in Startup.cs:
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Is it possible to set to return every child object and grandchild and etc?
Many thanks
You can turn off lazy loading for all entities by using the following in your DbContext class (place this in the constructor):
this.Configuration.LazyLoadingEnabled = false;
This will disable it for all entities - so be wary of this and watch for performance issues.
Another way you can load all the entities for a particular class is to remove the virtual keyword from the property declarations.

Using ComplexType with ToList causes InvalidOperationException

I have this model
namespace ProjectTimer.Models
{
public class TimerContext : DbContext
{
public TimerContext()
: base("DefaultConnection")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectTimeSpan> TimeSpans { get; set; }
}
public class DomainBase
{
[Key]
public int Id { get; set; }
}
public class Project : DomainBase
{
public UserProfile User { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<ProjectTimeSpan> TimeSpans { get; set; }
}
[ComplexType]
public class ProjectTimeSpan
{
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public bool Active { get; set; }
}
}
When I try to use this action I get the exception The type 'ProjectTimer.Models.ProjectTimeSpan' has already been configured as an entity type. It cannot be reconfigured as a complex type.
public ActionResult Index()
{
using (var db = new TimerContext())
{
return View(db.Projects.ToList);
}
}
The view is using the model #model IList<ProjectTimer.Models.Project>
Can any one shine some light as to why this would be happening?
Your IList<ProjectTimeSpan> property is not supported by EF. A complex type must always be part of another entity type, you cannot use a complex type by itself. If you absolutely need to have ProjectTimeSpan as a complex type, you will need to create a dummy entity type that only contains a key and a ProjectTimeSpan, and change the type of Project.TimeSpans to a list of that new type.

Resources