EF6: Include over nested TPH structure in SQL Server - c#

Hello everybody and a happy new 2017,
I have the following table-/object structure.
[Table("Table1")]
public class Table1
{
[Key]
public long Table1Id { get; set; }
public virtual ICollection<Table2> ItemsOfTable2 { get; set; }
}
[Table("Table2")]
public class Table2
{
[Key]
public long Table2Id { get; set; }
public long Table1Id { get; set; }
[ForeignKey("Table1Id")]
public virtual Table1 Table1Object { get; set; }
public virtual ICollection<Table3Base> ItemsOfTable3 { get; set; }
[NotMapped]
public virtual ICollection<Table3Red> RedItems
{
get { return this.ItemsOfTable3.OfType<Table3Red>().ToList(); }
}
[NotMapped]
public virtual ICollection<Table3Blue> BlueItems
{
get { return this.ItemsOfTable3.OfType<Table3Blue>().ToList(); }
}
}
[Table("Table3Base")]
public abstract class Table3Base
{
[Key]
public long Table3Id { get; set; }
public long Table2Id { get; set; }
[ForeignKey("Table2Id")]
public virtual Table2 Table2Object { get; set; }
}
public class Table3Red : Table3Base
{
public string SpecialPropertyForRed { get; set; }
}
public class Table3Blue : Table3Base
{
public int SpecialPropertyForBlue { get; set; }
public virtual ICollection<Table4> ItemsOfTable4 { get; set; }
}
[Table("Table4")]
public class Table4
{
[Key]
public long Table4Id { get; set; }
public long Table3Id { get; set; }
[ForeignKey("Table3Id")]
public virtual Table3Blue Table3BlueObject { get; set; }
}
public class MyContext : DbContext
{
public virtual IDbSet<Table1> Table1DbSet { get; set; }
public virtual IDbSet<Table2> Table2DbSet { get; set; }
public virtual IDbSet<Table3Red> Table3RedDbSet { get; set; }
public virtual IDbSet<Table3Blue> Table3BlueDbSet { get; set; }
public virtual IDbSet<Table4> Table4DbSet { get; set; }
}
So, in the middle of this "tree", there is a TPH structure (classes Table3Base, Table3Red, Table3Blue stored in database table "Table3Base"). And we only have IDbSets for Table3Red and Table3Blue, not for Table3Base. Every object has a collection navigation property of the next table objects.
Class Table3Blue has another collection navigation property to items of Table4 objects.
As further (but hopefully irrelevant) information: The default discriminator is mapped to an internal enum:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// TPH: Map Standard-Discriminator to Enum
modelBuilder.Entity<Table3Base>()
.Map<Table3Red>(m => m.Requires("Typ").HasValue((int)Table3TypEnum.Red))
.Map<Table3Blue>(m => m.Requires("Typ").HasValue((int)Table3TypEnum.Blue));
}
Due to performance issues (loading every single record one by one is very slow; Lazy Loading is active), we want to read this structure from Table1 to Table4 via include like this:
var table1Records = this.m_Context.Table1DbSet
.Include(t => t.ItemsOfTable2)
.Include(t => t.ItemsOfTable2.Select(t2 => t2.ItemsOfTable3))
.Include(t => t.ItemsOfTable2.Select(t2 => t2.ItemsOfTable3.OfType<Table3Blue>().Select(t3 => t3.ItemsOfTable4)))
.ToList();
The first and the second include seem to work, but the third include throws an Argument exception "The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parametername: path".
What am I doing wrong? How do I include Table4-objects on my way to the database?
Kind regards, Mate

This is our workaround. I doubt this is the best solution, so better ways are highly appreciated...
Table3Base gets the collection navigation property to Table4 as a virtual property.
Table3Red (the object without Table4-objects) overrides this property with a getter returning an empty list of Table4 objects and no setter.
Therefore, we can cascade our Include down to Table4 without any type checks. There are no unnecessary records in our PTH database table "Table3Base". So everything is fine, except the clumsy definition of Table3Red with an unused navigation property. :-(
BTW: Include with a long path includes all objects along this path, so the explicit ".Include(A).Include(A.B).Include(A.B.C)" is not necessary; ".Include(A.B.C)" will do the same. The iterating .Include in the code sample is for clarity.
HTH, Mate

Related

Why I can't delete the related entities

This is my Database diagram:
Now I want to delete the related Factors1 records whenever I delete a record from Factors Table. i.e cascade delete. So I have the following code with fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Factors>()
.HasOptional(r => r.Details).WithOptionalDependent()
.WillCascadeOnDelete(true);
}
But this throw the following error:
The declared type of navigation property Models.Factors.Details is not compatible with the result of the specified navigation.
This is my Models:
public class Customer
{
public int Id { get; set; }
...
public virtual ICollection<Factor> Factors { get; set; }
public virtual ICollection<Factors> Details { get; set; }
}
public class Factor
{
public int Id { get; set; }
...
public int? CustomerRefId { get; set; }
[ForeignKey("CustomerRefId")]
public virtual Customer Customer { get; set; }
public int? FactorsRefId { get; set; }
[ForeignKey("FactorsRefId")]
public virtual Factors Factors { get; set; }
}
public class Factors
{
public int Id { get; set; }
...
public int? CustomerRefId { get; set; }
[ForeignKey("CustomerRefId")]
public virtual Customer Customer { get; set; }
public virtual ICollection<Factor> Details { get; set; }
}
I posted my model and also the answers in the mentioned duplicate post do not address my problem.
Your mapping defines a one-to-one association between Factor and Factors, but in reality it is one-to-many. I think EF could do better by telling you that the target of a one-to-one association can't be an collection. Anyway, you should change it into
modelBuilder.Entity<Factors>()
.HasMany(r => r.Details).WithRequired(d => d.Factors)
.WillCascadeOnDelete(true);
By the way, do yourself (and future readers) a favor and fix this highly delusive naming.

fluent nhibernate circular dependencies

I have 2 entities and everything works fine except NHibernate won't load the FieldGroupItems property in the second entity on an object.
I suspect it's because there is a circular dependency between the 2 entities.
I really need both ChildGroups and FieldGroupItems. If I remove ChildGroups than FieldGroupItems is loaded fine.
Is there a way to have what I want. The only way I can think of is to use Guid collections instead of object collections to store only the Ids and fetch data manually from code.
Any help is appreciated.
public class FieldGroupItemInstance : TenantBaseEntity
{
public virtual Guid ItemId { get; set; } //ID from the database to update actual object later
public virtual bool IsTemporaryId { get; set; } //true if field group is new (doesn't exist in system)
public virtual IList<QuestionnaireInstanceField> Fields { get; set; }
public virtual IList<QuestionnaireFieldGroupInstance> ChildGroups { get; set; }
public FieldGroupItemInstance()
{
Fields = new List<QuestionnaireInstanceField>();
ChildGroups = new List<QuestionnaireFieldGroupInstance>();
}
}
public class QuestionnaireFieldGroupInstance : TenantBaseEntity
{
public virtual Guid FieldGroupTemplateId { get; set; }
public virtual IList<FieldGroupItemInstance> FieldGroupItems { get; set; } //Each repeated group of instances
//public virtual FieldGroupItemInstance Parent { get; set; }
public QuestionnaireFieldGroupInstance()
{
FieldGroupItems = new List<FieldGroupItemInstance>();
}
}
If you are using fluentnhibernate to map your entities, this should work. Just pay attention to the Cascade options.
public class FieldGroupItemInstanceMap()
{
public FieldGroupItemInstanceMap()
{
Table("FieldGroupItemInstance");
HasManyToMany(x => x.ChildGroups)
.Table("FieldGroupItemInstance_QuestionnaireFieldGroupInstance")
.ParentKeyColumn("IdFieldGroupItemInstance")
.ChildKeyColumn("IdQuestionnaireFieldGroupInstance")
.Cascade.None();
}
}
public class QuestionnaireFieldGroupInstanceMap()
{
public QuestionnaireFieldGroupInstanceMap()
{
Table("QuestionnaireFieldGroupInstance");
HasManyToMany(x => x.FieldGroupItems)
.Table("FieldGroupItemInstance_QuestionnaireFieldGroupInstance")
.ParentKeyColumn("IdQuestionnaireFieldGroupInstance")
.ChildKeyColumn("IdFieldGroupItemInstance")
.Cascade.None();
}
}

EF5 Model-First, DBContext code generation and derived class

I'm creating a EF5 entity model with the designer (VS2012), and used the EF5 DbContext generator as code generation item.
My model contains an entity deriving from another (not abstract).
So let's say the base entity is called BaseEntity, and the derived entity is DerivedEntity.
Now I see in the generated context class, that there is no
Public DbSet<DerivedEntity> DerivedEntities { get; set; }
defined.
Only
Public DbSet<BaseEntity> BaseEntities { get; set; }
is defined.
Is this normal ? And if yes, how do I query the derived entities in linq ?
I'm used to query like this:
using(var ctx = new EntityContainer)
{
var q = from e in ctx.DerivedEntities <-- but this is now not possible since it doesn't exist
select e;
return q.ToList();
}
Thanks for replying.
EDIT:
As requested, generated classes posted:
public partial class Scheduling
{
public int Id { get; set; }
public string Subject { get; set; }
public System.DateTime BeginDate { get; set; }
public System.DateTime EndDate { get; set; }
}
public partial class TeamScheduling : Scheduling
{
public int TeamId { get; set; }
public Nullable<int> AssignmentId { get; set; }
public virtual Team Team { get; set; }
public virtual Assignment Assignment { get; set; }
}
public partial class EntityContainer : DbContext
{
public EntityContainer()
: base("name=EntityContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Team> Teams { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<ProductType> ProductTypes { get; set; }
public DbSet<AssignmentPreference> AssignmentPreferences { get; set; }
public DbSet<Scheduling> Schedulings { get; set; }
}
As you see, the EntityContainer class does not contain
public DbSet<TeamScheduling> TeamSchedulings { get; set; }
This is expected when you use inheritance the way you have. context.Schedulings contains both Scheduling objects and TeamScheduling objects. You can get the TeamScheduling objects only by asking for context.Schedulings.OfType<TeamScheduling>(). Note that you cannot meaningfully use context.Schedulings.OfType<Scheduling>() to get the others: that will also include the TeamScheduling objects.
You could alternatively try context.Set<TeamScheduling>(), but I'm not entirely sure that will work.
If your intention is to have two tables come up, say a parent Scheduling entity as well as a child TeamScheduling entity that has a foreign key back to the Scheduling entity, consider using a Table-per-Type (TPT) mapping as discussed here.
In essence, you should modify your "OnModelCreating" method to have the following code:
modelBuilder.Entity<TeamScheduling>().ToTable("TeamScheduling");
This explicitly tells EF that you want to have the TeamScheduling subclass to be represented as its own table. Querying it via LINQ would be simple as you would be able to do something like the following:
var teamScheds = context.Set<TeamScheduling>().Where(s => s.Id == 1).FirstOrDefault();

Entity Framework Many to many through containing object

I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");

Implement a navigation property in c# class

i have 2 entities each with a relating c# class. I set up a navigation property on table A to contain a reference to many items in table B. When i make a new table A class object i need to be able to create the collection of table B objects in table A. How do i set up the navigation property in the table A c# class?
DATAMODEL:
http://bluewolftech.com/mike/mike/datamodel.jpg
Navigation properties are simple in EF. The example below shows how a navigation property would look:
public class Foo
{
public int FooId { get; set; }
public string SomeProperty { get; set; }
public virtual IEnumerable<Bar> Bars { get; set; }
}
Where Foo represents tableA and Bar represents tableB. They key word for the navigation property is virtual which enables lazy-loading by default. This is assuming you're using EF4.1 Code First.
EDIT
Off the top of my head, this should be a good starting template for you:
public class PointOfInterestContext : DbContext
{
public IDbSet<PointOfInterest> PointOfInterest { get; set; }
public IDbSet<POITag> POITag { get; set; }
public IDbSet<Tag> Tag { get; set; }
public override OnModelCreating(DbModelBuilder modelBuilder)
{
// custom mappings go here
base.OnModelCreating(modelBuilder)
}
}
public class PointOfInterest
{
// properties
public int Id { get; set; }
public string Title { get; set; }
// etc...
// navigation properties
public virtual IEnumerable<POITag> POITags { get; set; }
}
public class POITag
{
// properties
public int Id { get; set;}
public int PointOfInterestId { get; set; }
public int TagId { get; set; }
// navigation properties
public virtual PointOfInterest PointOfInterest { get; set; }
public virtual Tag Tag { get; set; }
}
public class Tag
{
// properties
public int Id { get; set; }
public string TagName { get; set; }
// etc...
// navigation properties
public virtual IEnumerable<POITags> POITags { get; set; }
}
Then you would implement the other logic in your business objects. The entities are supposed to be lightweight and at most should have data attributes. I prefer to use the fluent mappings through the OnModelCreating though.
Here are a few good references:
MSDN - EF 4.1 Code First
Code First Tutorial

Categories