Suppose I have multiple unrelated object (classes) where I can not get common abstract class for (and therefore unique primary key for all such classes). I would also like to apply list of common objects to all such classes (say for example list of Comments). My first approach would be something like this:
public class Comment
{
public int Id { get; set; }
public string CommentContent { get; set; }
public virtual ICommentable CommentableObject { get; set; }
}
public interface ICommentable
{
ICollection<Comment> Comments { get; set; }
}
public class Page : ICommentable
{
public int Id { get; set; }
public string PageContent { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Post : ICommentable
{
public int Id { get; set; }
public string PostContent { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
What EF code-first did, was creating single Comments table, with multiple nullable Foreign Keys (one for each related class) : Page_Id, Post_Id.
One of alternatives is to have separate Comments table for each class: PageComments, PostComments.
Is there any other more elegant solution to this common scenario ? Which one of two alternatives above is better ?
We're talking about an IsA (Is-A, Is A) relationship here. Your options are here: http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph
they are essentially ways of mapping an is-a inheritance/implementation to an is-a database relationship. The second option (Table per Type) seems to do that in a way that is normalized, the third option I'm unfamiliar with.
In your case, if you're sure there will only be 2 ICommentable classes, you may want to stick with what you have. This will also allow you a bit more flexibility if you change your mind about them both implementing the same interface. Normalize 'till it hurts, denormalize 'till it works.
Related
Suppose the following structure of classes and relationships:
class Document
{
public List<Version> DocumentVersions { get; set; }
// Other properties
}
class Register
{
public List<Version> RegisterVersions { get; set; }
// Other properties
}
class Version
{
public int VersionNumber { get; set; }
// Other properties
}
When using EF Core, it is going to produce 3 tables, D, R and V respectively where V is going to have 2 FK, one for D and one for R.
My questions are:
Is EF Core default approach correct? Wouldn't it lead to invalid states where V has no FKs because both FKs can be nullable.
I've read this and it almost answered my first question but it leads me to another question:
How can I tell EF to follow that approach: Should I have to create a derived type of V for each of its owners? or is there any way I can map a single entity to multiple tables and tell EF which relationships belong to which table?
Maybe is worth mention that my example is oversimplified and in reality I have 6 entities using the same V entity.
So, the dilemma is:
A) Should I keep two FKs in Version or
B) build two tables DocumentVersion and RegisterVersion instead of just Version?
Well, the truth is you can do both. You just have to decide which approach suits your system better. Let's have a quick look.
Approach A
To answer your question; yes EF's default approach is correct. Among creating two FKs and building two tables, it will create two FKs. It will create an extra table only in case of intermediate table for a many to many relashionship.
I always, though, recommend that we create all FKs ourselves instead of letting EF do it for us. This way we have more control over the behavior of the relationship and can also access the FKs in our application, since they are an entity's property.
public class Version
{
[Key]
public int VersionNumber { get; set; }
public int? DocumentID { get; set; }
public virtual Document Document { get; set; }
public int? RegisterID { get; set; }
public virtual Register Register { get; set; }
//Other properties
}
Since Version has a PK, it can create records without any of the FKs having any value. If this is allowed in your business model then leave it as it is. You can later provide a UI to assign "Versions" to either "Documents" or "Registers".
If you want to add some rules in your Version table; for example each record should have at least one FK or only one FK, you can do that by overriding the ValidateEntity method of your DbContext class (or through some sql constraint in the database probably).
protected override DbEntityValidationResult ValidateEntity(
DbEntityEntry entityEntry, IDictionary<object, object> items)
{
// validate entity from annotations
var result = base.ValidateEntity(entityEntry, items);
// custom validation rules
if (entityEntry.Entity is Version &&
(entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified))
{
Version version = ((Version)entityEntry.Entity);
if (version.DocumentID == null && version.RegisterID == null)
result.ValidationErrors.Add(new DbValidationError(null, "A document or register must be specified."));
}
return result;
}
Note that you can create your own annotations to validate your entity properties. But these are restricted to a single property. If you want to add validations that combine more than one property, the ValidateEntity method is the only way I know of.
Approach B
There are two ways to implement this approach. The first is to keep the Version table and add two intermediate tables on top.
public class Document
{
public virtual List<DocumentVersion> Versions { get; set; }
// other properties
}
public class Register
{
public virtual List<RegisterVersion> Versions { get; set; }
// other properties
}
public class Version
{
[Key]
public int VersionNumber { get; set; }
//Other properties
}
public class DocumentVersion
{
public int DocumentID { get; set; }
public virtual Document Document { get; set; }
public int VersionID { get; set; }
public virtual Version Version { get; set; }
// other properties
}
public class RegisterVersion
{
public int RegisterID { get; set; }
public virtual Register Register { get; set; }
public int VersionID { get; set; }
public virtual Version Version { get; set; }
// other properties
}
This actualy allows a many-to-many relationship, but you can use it as a one-to-many.
The second way is to make Version abstract (not a database table) and build two new tables to inherit from Version:
public class Document
{
public virtual List<DocumentVersion> Versions { get; set; }
// other properties
}
public class Register
{
public virtual List<RegisterVersion> Versions { get; set; }
// other properties
}
// don't make this a DbSet
public abstract class Version
{
[Key]
public int VersionNumber { get; set; }
//Other properties
}
public class DocumentVersion : Version
{
public int DocumentID { get; set; }
public virtual Document Document { get; set; }
// other properties
}
public class RegisterVersion : Version
{
public int RegisterID { get; set; }
public virtual Register Register { get; set; }}
// other properties
}
This is a proper and clear one-to-many relationship.
Conclusion
The bottom line is that you can use any of the two approaches and with alterations that suit your needs.
I have used both approaches successfully, but I tend to prefer the second one (and with the abstract class inheritance). The first approach seems more of a way to cut down on database resources or ease of development, but modern databases are not at all stressed by a few tables more and the development could become unnecessarily complex. Further more the second approach allows to extend the functionality of the relationships by adding further properties to each connection table seperatelly. And for the 6 entities you have to deal with, it seems safer to me to go with the second approach. I have used this approach in an application with many file types and relationships and it was always very straight-forward and extendable. Those extra properties in each relashion table came very handy too.
Hope I could help,
merry coding!
I don't think this really is a one-to-many relationship, look here.
It would be a one-to-many relationship if (for example) Document had multiple (e.g. a list of) Versions.
If you want multiple entities refering to the same entity type, you could place the foreign keys explicitly in the Document and Register classes:
class Document
{
public Version DocumentVersion { get; set; }
public int DocumentVersionId { get; set; } // Or whatever datatype your ID is
// Other properties
}
class Register
{
public Version RegisterVersion { get; set; }
public int RegisterVersionId { get; set; } // Or whatever datatype your ID is
// Other properties
}
class Version
{
public int VersionNumber { get; set; }
// Other properties
}
I am using the table per hierarchy approach for achieving inheritance in entity types. I have 3 classes defined:
Room - Base class
SubMapRoom - Inherits from Room
OverviewRoom - Inherits from Room
In the DB, I just have 1 table called Room that has both the SubMapRoom and OverviewRoom columns in it. It also contains the Discriminator column for specifying which type it is.
First, I attempted to move all of the SubMapRoom columns in the Room class into the SubMapRoom class. 1 of the columns contains a foreign key to a different table called Status. After doing this, I tried specifying the foreign key relationship for the SubMapRoom entity type in OnModelCreating(). However, I get a compile error when I try to do this. In the EF Core OnModelCreating() method, I have this code (marked the line that contains the error below):
modelBuilder.Entity<SubMapRoom>(entity =>
{
entity.HasOne(d => d.UnassignedDoctorStatus)
.WithMany(p => p.Room) **ERROR HAPPENS HERE**
.HasForeignKey(d => d.UnassignedDoctorStatusId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_Room_UnassignedStatusID");
});
modelBuilder.Entity<Room>()
.HasDiscriminator<int>("RoomType")
.HasValue<SubMapRoom>(1)
.HasValue<OverviewRoom>(2);
I get this error:
Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type
I know that I can solve this by changing the other class (Status) to use the inherited type instead of the base type for the navigation property, but that seems like the wrong way to go. I feel like I am missing something here. What would be the correct way to define a foreign key relationship in an inherited entity type?
[EDIT]
Here are the classes for the 4 models I have referenced here:
public abstract class Room
{
public Room()
{
InverseLinkedRoom = new HashSet<Room>();
}
public int Id { get; set; }
public int SubMapId { get; set; }
public string MapLabel { get; set; }
public string RoomLabel { get; set; }
public int LeftCoordinate { get; set; }
public int TopCoordinate { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int? LinkedRoomId { get; set; }
public int RoomType { get; set; }
public Room LinkedRoom { get; set; }
public SubMap SubMap { get; set; }
public PatientQueue PatientQueue { get; set; }
public ICollection<Room> InverseLinkedRoom { get; set; }
}
public class SubMapRoom : Room
{
public int? UnassignedDoctorStatusId { get; set; }
public Status UnassignedDoctorStatus { get; set; }
}
// Note: Have not yet attempted to move base class members in here
public class OverviewRoom : Room
{
}
public partial class Status
{
public Status()
{
Room = new HashSet<Room>();
}
public int Id { get; set; }
public string EnumId { get; set; }
public bool Active { get; set; }
public bool IsFastBlink { get; set; }
public ICollection<Room> Room { get; set; }
}
Thanks for the help everyone. I reviewed my DB schema and decided to make some changes that make this problem go away. It actually turns out that my new schema is easier to use in the code than I originally thought. In fact, it's a lot easier. I think I was trying to overengineer this. So sometimes, the solution is to review your schema and figure out if it even makes sense in the first place. Basically, what I did was move the inherited classes to a separate table with a separate ID. Because, at the end of the day, they are logically separate types of entities and only related in terms of the data. In the code, they serve much different purposes even though they share some of the same columns.
At the end of it all, the only disadvantage of this approach is that I am violating DRY on another table (there are 5 repeated columns in it). Otherwise, a lot of other operations are easier to code than before. I am willing to live with that instead of dealing with all of this for now. Later, I can try to use Table Per Hierarchy if I am having to add tons of new columns to both tables.
The main goal is the ability to have a many to many relationship between the table Mucle and Exercise. I want an Exercise to have both a primary and a secodary muscle group.
Is it possible to have two icollections in one model and only one in the other?
If someone could help with the "fluent configuration" as well, I would appreciate it!
Here is the code I have got right now.
public class Muscle
{
public int MuscleID { get; set; }
public bool IsFront { get; set; }
public string Name { get; set; }
public virtual ICollection<Exercise> Exercises { get; set; }
}
public class Exercise
{
public int ExerciseID { get; set; }
// ExerciseCategory
public int ExerciseCategoryID { get; set; }
public DateTime CreationDate { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public virtual ExerciseCategory ExerciseCategory { get; set; }
public virtual ICollection<Muscle> Muscles { get; set; }
public virtual ICollection<Muscle> MusclesSecondary { get; set; }
}
No way to map the model you described.
To map your model (2 n-m relationship) you would need a Junction table with a discriminator and you can't do it with EF.
You have several way to change your model to make it work with EF
You create a model (a class) for the Junction table and insert a discriminator in it. Your model changes (and I think that the new model is less clear)
Why is there a Muscles and MusclesSecondary? Can it be discriminated with an attribute of Muscle? In this case you can have the attribute in Muscle and remove Exercise.MusclesSecondary Then you have only an n-m relationship that EF handles with a Junction table.
If you want this model you can add 2 collections to Muscle (for example ExcercisesMuscle and ExercisesMusclesSecondary) and a 3rd not mapped collection where you have the content of ExcercisesMuscle and ExercisesMusclesSecondary toghether. About the ExcercisesMuscle and ExercisesMusclesSecondary they can be observable collections so you can cache the content of Exercises collection in an efficient way.
I currently have a situation with code first EF6 where I need to create a many-to-many mapping (easy enough) however the generated relationship table needs to contain properties of its own. Here's a simplified example of what I have:
public class Journey : Entity
{
public string Name { get; set; }
public DateTime Start { get; set; }
public virtual ICollection<Point> Points { get; set; }
}
public class Point : Entity
{
public DbGeography GeoLocation { get; set; }
public string Name { get; set; }
public virtual ICollection<Journey> Journeys { get; set; }
}
The "Entity" class basically contains the primary key.
This would of course create a relationship table with foreign keys for the "Point" and "Journey" table primary keys. Great, but what if I want to add properties to that link table? Such as a DateTime property called ArrivalDate which holds the time of arrival at that point.
You could argue that I can add that property to the "Point" class but I want this table to hold a list of all physical points used in my application, without duplicates (a single point could be used in multiple journeys, each with different arrival dates). Therefor I need to hold this property elsewhere, the link table would be ideal here.
The only solution I can think of would be to create an actual relationship class:
public class JourneyPoint : Entity
{
public int JourneyId { get; set; }
public int PointId { get; set; }
public DateTime ArrivalTime { get; set; }
public virtual Journey Journey { get; set; }
public virtual Point Point { get; set; }
}
And then modify my Journey and Point classes to have a one-to-many relationship with the JourneyPoint class. However this just adds complication and isn't particularly semantic:
...
Journey myJourney = aMethodToGetMyJourney();
Point firstPoint = myJourney.JourneyPoints[0].Point.
...
That doesn't seems to make much sense and could confuse other developers. Is what I'm asking for possible at all?
I'm trying to implement database structure in which there are common fields i put them in a separate abstract class but i want to know if 3 classes are inheriting from same abstract class and 2 have same property name so, by default entity framework will add a numeric followed by property name in database. Is there any way to implement this separately. I've studied complex types and searched over internet but couldn't find any flexible solution. I'm sharing my code, please guide me
public abstract class GenericImpression
{
[Key]
public int ImpressionId { get; set; }
public DateTimeOffset ReportingDate { get; set; }
}
public class Impression : GenericImpression
{
public string InventorySource { get; set; }
public string Media { get; set; }
}
public class Impression21 : GenericImpression
{
public string InventorySource { get; set; }
}
Now, EF will add one table with InventorySource1 and InventorySource Column.
Use OfType<>.
Example:
_context.GenericImpressions.ofType<Impression21>().ToList()