Relate Multiple Tables to Single General Purpose Table in Entity Framework Code First - entity-framework

Many times I have a general purpose entity that other entities contain a collection of. I don't want to have a new collection entity for each parent entity type that needs it but would like to re-use a single general purpose entity. For performance reasons, I also don't want to explicitly define many-to-many relationships as in this answer. The simplest example would be a collection of strings.
public class MyString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<MyString> { get; set; }
}
public class MyOtherString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<MyOtherString> { get; set; }
}
I'd really like to combine MyString and MyOtherString into a single entity:
public class GeneralPurposeString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<GeneralPurposeString> { get; set; }
}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<GeneralPurposeString> { get; set; }
}
Except now I'm going to have an additional foreign key in GeneralPurposeString for every entity that contains a collection of GeneralPurposeString.
What I would like would be a way to have an additional parent category column on the GeneralPurposeString table (but not the entity) that would specify which entity the item belongs to. I use Guid for primary keys, so the tables could look something like this:
CREATE TABLE [GeneralPurposeString]
(
[Id] uniqueidentifier NOT NULL
CONSTRAINT PK_GeneralPurposeString PRIMARY KEY,
[ParentEntityCategory] uniqueidentifier NOT NULL,
[ParentEntityId] uniqueidentifier NOT NULL,
[Value] nvarchar(MAX)
)
And some how in Code First to specify that MyEntity has a certain category, and that it's collection of GeneralPurposeString uses that category, and MyOtherEntity uses another category (Guid) for it's collections of GeneralPurposeString.
The key would be that GeneralPurposeString could be a collection in any other entity and that loading the parent entity and including the collection would automatically load without having to explicitly specify the category.
The purposes for all of this are
Allow .NET code to have GeneralPurposeString code that wasn't replicated everywhere (actual utility or business logic code). This can probably also be accomplished through inheritance and explicit mapping but that would still leave multiple tables in the database (see #2).
Have only one table in the database for GeneralPurposeString. This is more of a tidiness issue. Performance would possibly be better with multiple tables, but indexing on ParentEntityCategory/ParentEntityId and covering Value should be good performance for lookups.
Not have to explicitly code this relationship and the lookups everywhere it's needed.

I'm thinking if I can get over #2 and be OK with a separate table behind the scenes and implementing a derived class, that will be the simplest route to go.
So just:
public class GeneralPurposeString
{
public Guid Id { get; set; }
public string Value { get; set; }
}
// It's just a GeneralPurposeString with a fancy MyEntity membership pin
public class MyEntityString: GeneralPurposeString {}
public class MyEntity
{
public Guid Id { get; set; }
public virtual List<MyEntityString> Strings { get; set; }
}
// Cool GeneralPurposeStrings belong to MyOtherEntity
public class MyOtherEntityString: GeneralPurposeString {}
public class MyOtherEntity
{
public Guid Id { get; set; }
public virtual List<MyOtherEntityString> Strings { get; set; }
}
public class MyContext: DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public DbSet<MyOtherEntity> MyOtherEntities { get; set; }
}
I don't have to add the derived classes to the DbContext and the tables get named with the plural of the derived class by default, so it's actually pretty straight forward.
My previous train of thought with the Parent Category would require additional coding/annotation even if EF supported it. This uses purely convention and nothing extra needed in annotations or in OnModelCreating().
I'm not seeing any harm in extra tables at this point in time. I don't see a need (currently) to have all of the data in one table for reporting, but that really depends on the type of general purpose entity, so I may need to revisit this in the future, or I may just take the many-to-many route if I do need the data in one table.
And I can still have:
public static class GeneralPurposeStringExtensions
{
public static void SassThatHoopyFrood(this GeneralPurposeString s)
{
// do stuff
}
}

Related

Entity framework one foreign key toward two tables - code first

All,
Is it possible to use the same FK for two tables.
Probably it is not a good practice, but I have a two different classes that can be both booked:
public class Course {
public Course() {
BookingRefs = new HashSet<BookingRef>();
}
public long Id { get; set; }
public string Title { get; set; }
// other props ...
[InverseProperty(nameof(BookingRef.Course))]
public virtual ICollection<BookingRef> BookingRefs { get; set; }
}
public class GiftCard {
public GiftCard() {
BookingRefs = new HashSet<BookingRef>();
}
public long Id { get; set; }
public string Prop1 { get; set; }
public int Prop2 { get; set; }
// other props ...
[InverseProperty(nameof(BookingRef.Course))]
public virtual ICollection<BookingRef> BookingRefs { get; set; }
}
// this is the bookin reference for a Course or an GiftCard
public class BookingRef {
public BookingRef() {
}
public long Id { get; set; }
// other props ...
/// <summary>The item (usually the course but theoretically anything with a long id)</summary>
public long? ItemId { get; set; }
// maybe a generic Object?
[ForeignKey(nameof(ItemId))]
public Object GiftCard { get; set; }
// maybe 2 items possibly null?
[ForeignKey(nameof(ItemId))]
public Course Course { get; set; }
// maybe 2 items possibly null?
[ForeignKey(nameof(ItemId))]
public GiftCard GiftCard { get; set; }
}
Is it possible to use the same FK for two tables
No. The relational model doesn't allow that. You can introduce a superclass of all your bookable things and have a FK to that, but you shouldn't do that just get a single collection rather than multiple.
Think of it from the relational data perspective. How would the database know what table an "Item ID" pointed at? How would it index it?
This would be a case for using a null-able FK to each related table on the booking. These FKs do not need to reside in the entity, just the navigation properties. You can leverage .Map(x => x.MapKey) in EF6 or .HasForeignKey("") in EF Core to leverage a shadow property.
This does not enforce if you want a booking to only be associated to a course or a gift card but not both. That would need to be catered for at the application level, and I would recommend using a scheduled maintenance task to evaluate the data for violations to that rule. (Look for bookings holding both a course ID and a gift card ID for example)
You can alternatively keep the joins "loose" and evaluated by the application based on a discriminator similar to an inheritance model. (ItemId + ItemType) However you have to resolve the relationship load separately in your application based on the ItemType and lose out on any FK, indexing, and data integrity checks in the database. This could be a significant performance & maintenance cost to save adding a couple FKs.

Entity Framework : Code First Approach. Creating Entities using TPT inheritance

I am new to entity framework and I am using code first approach to create entities using TPT inheritance.
My requirement is to create the entities as per the attached diagram where ID is PK for Customers table and FK for the AddressDetails and ContactDetails table. Based on the keys I also need to create the association and navigation properties for the entities. Table Diagram
In my code I have created entities as
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int ZipCode { get; set; }
public virtual ContactDetails ContactDetails { get; set; }
public virtual AddressDetails AddressDetails { get; set; }
}
[Table("ContactDetails")]
public class ContactDetails: Customer
{
public string MobileNo { get; set; }
public string EmailId { get; set; }
}
[Table("AddressDetails")]
public class AddressDetails: Customer
{
public string BillingAddress { get; set; }
public string DeliveryAddress { get; set; }
}
My question is, have I created the association and navigation properties correctly or do I need to add them in the ContactDetails and AddressDetails class as well? Also, when I run the code the entities are getting created in the database but for the Customer table there are 2 additional columns created as AddressDetails_Id(FK,int,null) and ContactDetails_Id(FK,int,null). I think they are created because of the navigation property but I do not need these columns in the database to be created. Also the values are always null in these two columns.
Any help would be appreciated. Thanks in advance.

Entity framework two-way relationship not loading includes

This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.
Three code-first objects, related to one another.
public class Schedule
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public decimal Rate { get; set; }
public Type Type { get; set; }
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
}
When I query this, I want all related types, so:
Schedule currentSchedule = _Context.Schedules
.Include("Charges.Type")
.Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
.First();
In C#, this has been working fine.
The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.
So I change it to this:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:
Unable to determine the principal end of an association between the
types 'Core.Charge' and 'Core.Type'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations
Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:
public class Type
{
[Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.
What's going on, and what have I done wrong?
You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.
Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.
If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public int ChargeId { get; set; }
[ForeignKey("ChargeId")]
public virtual Charge Charge { get; set; }
}

EF 5 Code First using Inheritence in the class

I am getting Error when trying to run this code.
Unable to determine the principal end of an association between the
types 'AddressBook.DAL.Models.User' and 'AddressBook.DAL.Models.User'.
The principal end of this association must be explicitly configured
using either the relationship fluent API or data annotations.
The objective is that i am creating baseClass that has commonfield for all the tables.
IF i don't use base class everything works fine.
namespace AddressBook.DAL.Models
{
public class BaseTable
{
[Required]
public DateTime DateCreated { get; set; }
[Required]
public DateTime DateLastUpdatedOn { get; set; }
[Required]
public virtual int CreatedByUserId { get; set; }
[ForeignKey("CreatedByUserId")]
public virtual User CreatedByUser { get; set; }
[Required]
public virtual int UpdatedByUserId { get; set; }
[ForeignKey("UpdatedByUserId")]
public virtual User UpdatedByUser { get; set; }
[Required]
public RowStatus RowStatus { get; set; }
}
public enum RowStatus
{
NewlyCreated,
Modified,
Deleted
}
}
namespace AddressBook.DAL.Models
{
public class User : BaseTable
{
[Key]
public int UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Password { get; set; }
}
}
You need to provide mapping information to EF. The following article describes code-first strategies for different EF entity inheritance models (table-per-type, table-per-hierarchy, etc.). Not all the scenarios are directly what you are doing here, but pay attention to the mapping code because that's what you need to consider (and it's good info in case you want to use inheritance for other scenarios). Note that inheritance does have limitations and costs when it comes to ORMs, particularly with polymorphic associations (which makes the TPC scenario somewhat difficult to manage). http://weblogs.asp.net/manavi/archive/2010/12/24/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph.aspx
The other way EF can handle this kind of scenario is by aggregating a complex type into a "fake" compositional relationship. In other words, even though your audit fields are part of some transactional entity table, you can split them out into a common complex type which can be associated to any other entity that contains those same fields. The difference here is that you'd actually be encapsulting those fields into another type. So for example, if you moved your audit fields into an "Audit" complext type, you would have something like:
User.Audit.DateCreated
instead of
User.DateCreated
In any case, you still need to provide the appropriate mapping information.
This article here explains how to do this: http://weblogs.asp.net/manavi/archive/2010/12/11/entity-association-mapping-with-code-first-part-1-one-to-one-associations.aspx

How to map foreign keys between TPH TPT objects - Entity Framework Code First

We have the following set of objects.
public class Form
{
public int FormId { get; set; }
public DateTime DateCreatedOn { get; set; }
public string Status { get; set; }
}
// This is using TPT inheritance from Form
[Table("FormA")]
public class FormA : Form
{
public string ExtraInfoA { get; set; }
public virtual ICollection<Child> Children
}
// This is using TPT inheritance from Form
[Table("FormB")]
public class FormB : Form
{
public string ExtraInfoB { get; set; }
public virtual ICollection<Adult> Adults
}
public class Person
{
public int PersonId { get; set; }
public int FormId
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
// This is using TPH inheritance from Person
public class Adult : Person
{
public int HowManyCars { get; set; }
public string NationalInsuranceNo { get; set; }
[ForeignKey("FormId")]
public virtual FormB FormB { get; set; }
}
// This is using TPH inheritance from Person
public class Child : Person
{
public int HowManyToys { get; set; }
public string SchoolName { get; set; }
[ForeignKey("FormId")]
public virtual FormA FormA { get; set; }
}
This creates 3 tables for the forms Form, FormA, and FormB, all with the appropriate fields in them. It also creates 1 table for Person.
The problem is When we rely on the convention and don't specify the ForeignKey attribute the Person table contains 2 additional foreign key columns.
However when we do specify the ForeignKey attribute (as in the code above) we get the following error message.
`The foreign key component 'FormId' is not a declared property on type 'Child'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.`
FormId is definitely a property of Child so I'm not sure what is going wrong.
Our real world situation is a lot more complicated that the situation above so I'd like to get it right now rather tham have multiple foreign keys.
Any help is very much appreciated.
You cannot define foreign key in the parent entity and navigation property in the child entity. They must both be defined in the same entity. What you are trying to do is even not valid in the database because you cannot have conditional foreign key constraint on the column - constraints to both FormA and FormB will be applied for every record and you will never be able to insert any record (because it would always violate constraint to FormA or FormB).
In short: You need either single navigation property in parent or separate foreign key for every child.