EF7 Implement TPH + M2M - c#

Is there a better way to accomplish this end-goal of having easily-queryable (and Include-able) cross-sections of a related many-to-many entity stored in the same table?
I started off without implementing TPH in the join table, but that makes consuming one type or another in queries more involved, afaict.
// table Related: [Id]
public class Related
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
public List<RelatedOtherOne> RelatedOtherOnes { get; set; } = new List<RelatedOtherOne>();
public List<RelatedOtherTwo> RelatedOtherTwos { get; set; } = new List<RelatedOtherTwo>();
}
// table RelatedOther: [RelatedId, OtherId, Type]
public abstract class RelatedOther
{
public Guid RelatedId { get; set; }
public Guid OtherId { get; set; }
public Related Related { get; set; }
public Other Other { get; set; }
public abstract RelatedOtherType Type { get; }
}
public class RelatedOtherOne : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.One;
// should be unnecessary, 'Other' should be correct type
public OtherOne OtherOne { get; set; }
}
public class RelatedOtherTwo : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.Two;
// should be unnecessary, 'Other' should be correct type
public OtherTwo OtherTwo { get; set; }
}
public enum RelatedOtherType : int
{
One = 1,
Two = 2
}
// table Other: [Id, OneProp, TwoProp]
public abstract class Other
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
}
public class OtherOne : Other
{
public string OneProp { get; set; }
}
public class OtherTwo : Other
{
public string TwoProp { get; set; }
}
TPH is mapped like this
M2M is mapped like this + discriminator in HasKey()
This gets even more complicated (if not impossible?) when the 'Related' entity evolves into a TPH strategy like the 'Other'.

I have no easy solution but as I stumbled across the same problem I thought I'll share what I have so far.
I found out that I usually need to load all or many types of the relations to the classes of a TPH structure.
So I use the base many-to-many class to load the related objects. Thus this class cannot be abstract:
public class Event2Location
{
[Required]
public Event Event { get; set; }
public int EventId { get; set; }
[Required]
public Location Location { get; set; }
public int LocationId { get; set; }
public byte EntityType { get; set; }
}
The derived class only adds some properties for easier access:
public class Event2Country : Event2Location
{
[NotMapped]
public Country Country
{
get { return base.Location as Country; }
set { base.Location = value; }
}
[NotMapped]
public int CountryId
{
get { return base.LocationId; }
set { base.LocationId = value; }
}
}
In the Event class I have:
public virtual ICollection<Event2Location> Event2Locations { get; set; }
[NotMapped]
public virtual ICollection<Event2Country> Event2Countries => Event2Locations?.OfType<Event2Country>().ToList();
// I should probably add some caching here if accessed more often
[NotMapped]
public virtual ICollection<Event2City> Event2Cities => Event2Locations?.OfType<Event2City>().ToList();
So when I load the joined tables I can use
.Include(e => e.Event2Locations).ThenInclude(j => j.Location)
And I can access the relations of a specific type as needed with the NotMapped Collections.
I still use the derived Event2... classes to add a new relationship.
As you see I have added a column EntityType to the many-to-many class which I use as TPH discriminator. With this column I can also declare which types of Relations/entities I want to load if I do not want to load all.
modelBuilder.Entity<Event2Location>()
.HasDiscriminator<byte>("EntityType")
.HasValue<Event2Location>(0)
.HasValue<Event2Country>(1)
This is surely far from perfect but I finally gave up on optimizing that. First EFCore has to become more mature. Second I want to see how I actually use these structures.
PS: Actually my Location TPH structure has parent-child-relationships within it. Here I did not create a TPH structure for the relation class (as you said - not possible or at least not reasonable). I added ParentType and ChildType. Thus I can determine which relations I actually want to load. Then I fetch the related Locations of the types I need manually on the client side from the result.

Related

Independent child object with references from multiples parents in EF

I'm having quite the issue right now while trying to learn Entity Framework.
Let's say I have this entity:
public class BuildingGroup {
public int ID { get; set; }
public string NameOfManager { get; set; }
public virtual ICollection<Building> Buildings { get; set; }
}
And also this entity.
public class Architect {
public int ID { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Building> BuildingsBeingWorkedOn { get; set; }
}
These two entities are completely unrelated. Here's the Building entity:
public class Building {
public int ID { get; set; }
public string Address { get; set; }
}
My problem happens when I try to add a building to, say a BuildingGroup. In my domain model, I can modify the equivalent collection of buildings, by adding, modifying or removing buildings. However, when I try to update BuildingGroup through a repository, the buildings will not be updated.
public void Update(BuildingGroup buildingGroup) {
var buildingGroupEntity = _context.BuildingGroups.Single(b => b.ID == buildingGroup.ID);
// This will not map the Building collection
context.Entry(buildingGroupEntity).CurrentValues.SetValues(buildingGroup);
// My attempt at mapping the buildings
buildingGroupEntity.Buildings.Clear();
buildingGroup.Buildings.ToList().ForEach(b => buildingGroupEntity.Buildings.Add(_context.Buildings.Single(x => x.ID == b.ID)));
_context.Entry(buildingGroupEntity).State = EntityState.Modified;
}
This fails if the building were not saved in the database prior to the call to Update(), which is normal since buildings can live independently. It must also be done for every child collection of BuildingGroup (if there were more), and for child collections of these children, well...
I have noticed other people use a foreign key constraint in the child object (here, Building), but I can't really do that since many unrelated entities can point to a building: I'd have a lot of navigation properties.
Is there a graceful way to manage referencing objects that can also live independently from those who hold references to them?
If all the entities have to exist independently, yet have relationships with each other, it's better to use many-to-many relationship.
Change your model classes as follows, the Building should contain a couple of collections for architects and groups.
public class BuildingGroup
{
public int ID { get; set; }
public string NameOfManager { get; set; }
public virtual ICollection<Building> Buildings { get; set; }
}
public class Architect
{
public int ID { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Building> BuildingsBeingWorkedOn { get; set; }
}
public class Building
{
public int ID { get; set; }
public string Address { get; set; }
public virtual ICollection<Architect> Architects { get; set; }
public virtual ICollection<BuildingGroup> BuildingGroups { get; set; }
}
If you use entity type configuration, you could define the relationship as follows:
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Building>().HasMany(it => it.Architects).WithMany(it => it.BuildingsBeingWorkedOn);
modelBuilder.Entity<Building>().HasMany(it => it.BuildingGroups).WithMany(it => it.Buildings);
base.OnModelCreating(modelBuilder);
}
}

Lazy loading not working with 1 to 0 or 1 relationship

I have the following table design.
As can be seen here, there is a one to many relationship, with the many on the EpisodePatient side.
Then, I have the following classes.
public class EpisodeModel
{
public int ID { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public int EpisodePatientID { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
I am setting up the relationship, in Entity Framework, to be a one to 0 or many. The reason for this is, I am selecting all EpisodePatients from a View, and I want the Episode to be Lazy loaded when accessed.
This is how I am setting up my relationship.
modelBuilder.Entity<EpisodePatientModel>().HasRequired(r => r.Episode).WithOptional(o => o.EpisodePatient);
I want this to act as a One to zero or many in my code, as an Episode will always have an EpisodePatient, and vice versa.
The problem I am facing is, when I load the EpisodePatient, and try to access the Episode linked item, it is always null, and Lazy loading does not occur.
What am I doing wrong here?
UPDATE
This is how I load the original EpisodePatient items.
this.DbContext.EpisodePatients.AsNoTracking();
I re-created your model but with data annotations like below and it workes fine:
public class EpisodeModel
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public string Name { get; set; }
[Key, ForeignKey("Episode")]
public int EpisodeId { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
Try without AsNoTracking(), because if you use it your context is not tracking and you can't include more data if you need.
And try change to relation one to many.
modelBuilder.Entity<EpisodePatientModel>().HasRequired<Episode>(s => s.Episode).WithMany(s => s.EpisodePatient);

Entity Framework: Many-to-Many relations with abstract classes, modify without querying database?

With Entity Framework, if I have the following model:
class Asset {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<AssetGroup> Groups { get; set; }
}
class AssetGroup {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Asset> Assets { get; set; }
}
Adding an existing asset to a given group without having to query the database to load the asset in question is relatively straightforward:
using(var context = new MyContext()) {
AssetGroup assetGroup = context.AssetGroups.Find(groupId);
// Create a fake Asset to avoid a db query
Asset temp = context.Assets.Create();
temp.Id = assetId;
context.Assets.Attach(temp);
assetGroup.Assets.Add(temp);
context.SaveChanges();
}
However, I now have the problem that I have extended the model so that there are multiple Asset types, implemented as an inheritance hierarchy. In the process, the Asset class has been made abstract, since an asset with no specific type makes no sense:
abstract class Asset {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<AssetGroup> Groups { get; set; }
}
class SpecificAsset1 : Asset {
public string OneProperty { get; set; }
}
class SpecificAsset2 : Asset {
public string OtherProperty { get; set; }
}
The problem is that now the Create() call throws an InvalidOperationException because "Instances of abstract classes cannot be created", which of course makes sense.
So, the question is, how can I update the many-to-many relation without having to go fetch the Asset from the database?
You can use the generic Create<T> method of DbSet<T> to create a derived entity object.
var temp1 = context.Assets.Create<SpecificAsset1>();
and continue from here using temp1 as a stub entity the way you already did.

Multiple tables with same POCO class

I have an existing database where I have four identical and unrelated tables.
I want to use the same POCO class to describe all four without having to create duplicates of the same class.
This is what my context looks like so far:
class StatsContext : DbContext
{
// [MagicTableAttribute( "map_ratings_vsh" )] -- does something like this exist?
public DbSet<MapRatings> MapRatingsVSH { get; set; }
public DbSet<MapRatings> MapRatingsJump { get; set; }
// 2 more tables using same class
}
class MapRatings
{
public string SteamID { get; set; }
public string Map { get; set; }
public int Rating { get; set; }
[Column( "rated" )]
public DateTime Time { get; set; }
}
My problem is that the existing tables are named "map_ratings_vsh" and "map_ratings_jump", and I cannot use the data annotations TableAttribute because it can only be used on the class.
Is there some other way--maybe the fluent api--to describe my schema?
One way I've found to solve this is to use inheritance.
[Table("map_ratings_vsh")]
public class MapRatingsVSH : MapRatingsBase {}
[Table("map_ratings_jump")]
public class MapRatingsJump : MapRatingsBase {}
public class MapRatingsBase
{
public string SteamID { get; set; }
public string Map { get; set; }
public int Rating { get; set; }
[Column( "rated" )]
public DateTime Time { get; set; }
}
Then you can have your DbContext look like:
public class StatsContext : DbContext
{
public DbSet<MapRatingsVSH> MapRatingsVSH { get; set; }
public DbSet<MapRatingsJump> MapRatingsJump { get; set; }
}
EF shouldn't have any problem understanding that these are two different tables even though the implementation will be in the same place (MapRatingsBase)
You can use the fluent api to map some properties to one table and other properties to another table like this:
modelBuilder.Entity<TestResult>()
.Map(m =>
{
m.Properties(t => new { /* map_ratings_vsh columns */ });
m.ToTable("map_ratings_vsh");
})
.Map(m =>
{
m.Properties(t => new { /* map_ratings_jump columns */ });
m.ToTable("map_ratings_jump");
});
The (hacky) way I've handled this in the past is to return data using a stored procedure or view, and then provide a "AddEntity" or "SaveEntity" method on the DbContext implementation that persists the entity in question

EF Code First Many to many relation store additional data in link table

How do I store additional fields in the "link table" that is automagically created for me if I have two entities associated as having a many to many relationship?
I have tried going the "two 1 to many associations"-route, but I'm having a hard time with correctly configuring the cascading deletion.
Unless those extra columns are used by some functions or procedures at the database level, the extra columns in the link table will be useless since they are completely invisible at the Entity Framework level.
It sounds like you need to re-think your object model. If you absolutely need those columns, you can always add them later manually.
You will most likely need to expose the association in your domain model.
As an example, I needed to store an index (display order) against items in an many-to-many relationship (Project <> Images).
Here's the association class:
public class ProjectImage : Entity
{
public Guid ProjectId { get; set; }
public Guid ImageId { get; set; }
public virtual int DisplayIndex { get; set; }
public virtual Project Project { get; set; }
public virtual Image Image { get; set; }
}
Here's the mapping:
public class ProjectImageMap : EntityTypeConfiguration<ProjectImage>
{
public ProjectImageMap()
{
ToTable("ProjectImages");
HasKey(pi => pi.Id);
HasRequired(pi => pi.Project);
HasRequired(pi => pi.Image);
}
}
From Project Map:
HasMany(p => p.ProjectImages).WithRequired(pi => pi.Project);
Maps to the following property on project:
public virtual IList<ProjectImage> ProjectImages { get; set; }
Hope that helps
Ben
Suppose there is a many-to-many association between two types: User and Message, and the association class is defined as UserMessageLink with additional properties.
public class User {
public int Id {get;set;}
}
public class Message {
public int Id {get;set;}
}
//The many-to-many association class with additional properties
public class UserMessageLink {
[Key]
[Column("RecieverId", Order = 0)]
[ForeignKey("Reciever")]
public virtual int RecieverId { get; set; }
[Key]
[Column("MessageId", Order = 1)]
[ForeignKey("Message")]
public virtual int MessageId { get; set; }
public virtual User Reciever { get; set; }
public virtual Message Message { get; set; }
//This is an additional property
public bool IsRead { get; set; }
}

Categories

Resources