EntityFrameworkCore Populate Property With Polymorphic Association - c#

I have a table Venue which can have multiple Media items on it. Media can be for any number of different tables, so we have 2 properties on it, MediaType to specify which table it's for (content, venue, venueCategory, contact), and MediaTypeID for specifying which item in that table it's for.
How can we populate myVenue.Media when we load our Venues?
We have the following which seems close...
MyDBContext.cs:
builder.Entity<Venue>()
.HasMany<Media>(x => x.Medias)
.WithOne(m => m.Venue)
.HasForeignKey(m => m.MediaTypeID);
Media.cs looks something like:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MediaID { get; set; }
public string URL { get; set; }
public int MediaType { get; set; }
public int MediaTypeID { get; set; }
public Venue Venue { get; set; }
Venue.cs has:
public virtual ICollection<Media> Medias { get; set; }
And then to use the code we're using:
db.Venue.Include(x => x.Medias)
The issue with this is that it doesn't compare on the Media's MediaType property, so we could end up taking in Contact, or Venue Media if they have the same ID.
We could compare in a .Where() after every .Include, but it seems there should surely be a way to do it just the once in the DBContext?
To summarize, my ideal usage would be to specify:
builder.Entity<Venue>()
.HasMany<Media>(x => x.Medias)
.WithOne(m => m.Venue)
.HasForeignKey(m => m.MediaTypeID && m.MediaType==2);
UPDATE:
From feedback in comments it seems this is not possible as EF "only supports associations which can be expressed by db FK relationship. – Ivan Stoev"

Related

One-To-One relationship with FK distinct from PK

I have 2 tables in database: ReceivedGoods and ReceivedGoodsProperties
ReceivedGoods contains ReceivingId as PK and must have its extending data in ReceivedGoodsProperties which contains ReceivingId as FK referencing to the ReceivedGoods's ReceivingId. Current ReceivedGoodsProperties, however, has its own PK Id and is therefore distinct from FK. So I have following:
public class ReceivedGoods
{
...
public int ReceivingId { get; set; }
...
public virtual ReceivedGoodsProperties properties { get; set; }
}
public class ReceivedGoodsProperties
{
...
public int Id { get; set; } // This is PK
public int ReceivingId { get; set; } // This is FK
...
public virtual ReceivedGoods goods { get; set; }
}
I would like to get ReceivedGoods object and have properties automatically loaded as well but I am not able to figure out, how to set up this within EF.
I've tried something like this (from the ReceivedGoodsProperties side mapping):
this.HasRequired(p => p.goods)
.WithRequiredDependent(d => d.properties)
.Map(m => m.MapKey("ReceivingId"));
but I am ending up with following error:
ReceivingId: Name: Each property name in a type must be unique. Property
name 'ReceivingId' is already defined.
When commenting out ReceivingId in ReceivedGoodsProperties, upper exception is not thrown, ReceivedGoods is loaded correctly except the properties property.
Can somebody explain me, how to do one-to-one mapping in situation like this?
Could you try:
public class ReceivedGoods
{
...
public int ReceivingId { get; set; }
...
public virtual ReceivedGoodsProperties properties { get; set; }
}
public class ReceivedGoodsProperties
{
...
public int Id { get; set; } // This is PK
[ForeignKey( "goods " )]
public int ReceivingId { get; set; } // This is FK
...
[Required]
public virtual ReceivedGoods goods { get; set; }
}
BTW, in C# the standard guidelines is to PascalCase members, so Goods and Properties
Try defining the relationship this way:
this.HasRequired(p => p.goods)
.WithRequiredDependent(p => p.properties)
.HasForeignKey(p => p.ReceivingId);
If you follow the standard EF naming conventions, it can usually figure out these relationships on its own. You only really run in to trouble when your navigation property names don't correspond to the class name, or if you have multiple FKs to the same destination in the source table.
If you want the navigation properties to get filled out "automatically", use the Include extension method on the query, as in:context.Goods.Include(g=>g.properties). You don't have to declare them as virtual unless you want to make use of lazy loading.
You may need to come at this from the other entity:
this.HasRequired(p => p.properties)
.WithRequiredPrincipal(p => p.goods)
.HasForeignKey(p => p.ReceivingId);

EF6 - One table to hold all contacts against all other tables

Okay, I know this one is weird, but I'm trying to have all my contacts in one table; each one will relate to another table using that other table's name and an ID within that table. For example, contact FRED relates to table "Company" with CompanyID 3, whereas contact BARNEY relates to table "Accountant" with AccountantID 21.
public class Contact: DbContext
{
[Key, Column(Order = 0)]
public string TableName { get; set; }
[Key, Column(Order = 1)]
public int ReferenceID { get; set; }
public string ContactName { get; set; }
}
public class Company: DbContext
{
[Key]
public int CompanyID { get; set; }
public string CompanyName { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Accountant: DbContext
{
[Key]
public int AccountantID { get; set; }
public string AccountantName { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
So a foreign key won't work, but the composite key (TableName/ReferenceID) will be unique, so the Company can contain a list of associated Contacts (those having a TableName of "Company" and a ReferenceID that matches the CompanyID). It feels like I'll have to set up the modelBuilder something like this, but I'm really not sure how it would work in this particular case...
modelBuilder.Entity<Company>()
.WithMany(e => e.Contacts)
.HasOptional(d => new { "Company", d.ReferenceID } )
.WillCascadeOnDelete(true);
I'm going to call myself an EntityFrameworkNewbie, so please forgive any obvious oversights. Thanks.
It's not something you'll be able to do in EF in the way you're trying to, there are a couple of ways around it, my suggestion would be many to many relationships (see http://msdn.microsoft.com/en-gb/data/jj591620.aspx):
modelBuilder.Entity<Company>()
.HasMany(t => t.Contacts)
.WithMany(t => t.Companies)
.Map(m =>
{
m.ToTable("CompanyContacts");
m.MapLeftKey("ContactID");
m.MapRightKey("CompanyID");
});
modelBuilder.Entity<Accountant>()
.HasMany(t => t.Contacts)
.WithMany(t => t.Accountants)
.Map(m =>
{
m.ToTable("AccountantContacts");
m.MapLeftKey("ContactID");
m.MapRightKey("AccountantID");
});
etc.
Technically in db terms this means you could have one person be a contact for multiple customers, or a person be a contact for a customer and an accountant at the same time. It's one of these situations where there isn't a correct answer.
Another option would be TablePerType inheritance (so you would create a CompanyContact and an AccountantContact object, both of which inherit from contact (http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt)
Don't do this. This is not a good relational design, and will not give you the referential integrity that is the main benefit of using a relational database in the first place.
If a each related entity can only have one contact, just put the contactId on the related entity. If the related entity can have more than one contact, create a m:n table that properly represents this (e.g. a CompanyContact table).

User has roles for systems - how to (object-)model ternary relation?

I've got the following Entities:
Privilege ( Id, Name )
Role ( Id, Name, ICollection<Privilege> )
System ( Id, Name )
User ( Id, Name, Pass, ? )
Now I want to model "A user may have for each of zero or more systems zero or more roles", e.g.:
IDictionary<System, ICollection<Role>> SystemRoles { get; set; }
Is this possible with ASP.NET EntityFramework? If yes, how? What attributes do I have to set?
Been looking around for quite some time now, however I don't find anything useful on the net for "entity framework code first ternary relation"
Can you point me to some nice link where this is covered in detail? Or maybe give me a hint how to model it / which attributes I can put on the dictionary?
Additional question: If the IDictionary solution works somehow, is there any change to get change tracking proxy performance? IDictionary is not an ICollection...
A user may have for each of zero or more systems zero or more roles
You will need an entity that describes the relationship between the three:
public class UserSystemRole
{
public int UserId { get; set; }
public int SystemId { get; set; }
public int RoleId { get; set; }
public User User { get; set; }
public System System { get; set; }
public Role Role { get; set; }
}
I would create a composite primary key from all three properties because each combination may only occur once and must the unique. Each part of the key is a foreign key for the respective navigation property User, System and Role.
Then the other entities would have collections refering to this "link entity":
public class User
{
public int UserId { get; set; }
//...
public ICollection<UserSystemRole> UserSystemRoles { get; set; }
}
public class System
{
public int SystemId { get; set; }
//...
public ICollection<UserSystemRole> UserSystemRoles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
//...
public ICollection<UserSystemRole> UserSystemRoles { get; set; }
}
And then the mapping with Fluent API would look like this:
modelBuilder.Entity<UserSystemRole>()
.HasKey(usr => new { usr.UserId, usr.SystemId, usr.RoleId });
modelBuilder.Entity<UserSystemRole>()
.HasRequired(usr => usr.User)
.WithMany(u => u.UserSystemRoles)
.HasForeignKey(usr => usr.UserId);
modelBuilder.Entity<UserSystemRole>()
.HasRequired(usr => usr.System)
.WithMany(s => s.UserSystemRoles)
.HasForeignKey(usr => usr.SystemId);
modelBuilder.Entity<UserSystemRole>()
.HasRequired(usr => usr.Role)
.WithMany(r => r.UserSystemRoles)
.HasForeignKey(usr => usr.RoleId);
You can remove one of the collection properties if you don't need them (use WithMany() without parameter then).
Edit
In order to get a dictionary for a user you could introduce a helper property (readonly and not mapped to the database) like so:
public class User
{
public int UserId { get; set; }
//...
public ICollection<UserSystemRole> UserSystemRoles { get; set; }
public IDictionary<System, IEnumerable<Role>> SystemRoles
{
get
{
return UserSystemRoles
.GroupBy(usr => usr.System)
.ToDictionary(g => g.Key, g => g.Select(usr => usr.Role));
}
}
}
Note that you need to load the UserSystemRoles property with eager loading first before you can access this dictionary. Or alternatively mark the UserSystemRoles property as virtual to enable lazy loading.

EF 4.1 Code First: Each property name in a type must be unique error on Lookup Table association

This is my first attempting at creating my own EF model, and I'm finding myself stuck attempting to create a lookup table association using Code First so I can access:
myProduct.Category.AltCategoryID
I have setup models and mappings as I understand to be correct, but continue to get
error 0019: Each property name in a type must be unique. Property name 'CategoryID' was already defined
The following models are represented in my code:
[Table("Product", Schema="mySchema")]
public class Product {
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)]
public int ProductID { get; set; }
public int CategoryID { get; set; }
public virtual Category Category { get; set; }
}
[Table("Category", Schema="mySchema")]
public class Category {
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)]
public int CategoryID { get; set; }
public string Name { get; set; }
public int AltCategoryID { get; set; }
}
I have specified the associations with:
modelBuilder.Entity<Product>()
.HasOptional(p => p.Category)
.WithRequired()
.Map(m => m.MapKey("CategoryID"));
I've tried a few other things, including adding the [ForeignKey] annotation, but that results in an error containing a reference to the ProductID field.
You are looking for:
modelBuilder.Entity<Product>()
// Product must have category (CategoryId is not nullable)
.HasRequired(p => p.Category)
// Category can have many products
.WithMany()
// Product exposes FK to category
.HasForeignKey(p => p.CategoryID);

Entity Framework Code First: How can I create a One-to-Many AND a One-to-One relationship between two tables?

Here is my Model:
public class Customer
{
public int ID { get; set; }
public int MailingAddressID { get; set; }
public virtual Address MailingAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
A customer can have any number of addresses, however only one of those addresses can be a mailing address.
I can get the One to One relationship and the One to Many working just fine if I only use one, but when I try and introduce both I get multiple CustomerID keys (CustomerID1, CustomerID2, CustomerID3) on the Addresses table. I'm really tearing my hair out over this one.
In order to map the One to One relationship I am using the method described here http://weblogs.asp.net/manavi/archive/2011/01/23/associations-in-ef-code-first-ctp5-part-3-one-to-one-foreign-key-associations.aspx
I've struggled with this for almost the entire day and of course I wait to ask here just before finally figuring it out!
In addition to implementing the One to One as demonstrated in that blog, I also then needed to use the fluent api in order to specify the Many to Many since the convention alone wasn't enough with the One to One relationship present.
modelBuilder.Entity<Customer>().HasRequired(x => x.PrimaryMailingAddress)
.WithMany()
.HasForeignKey(x => x.PrimaryMailingAddressID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Address>()
.HasRequired(x => x.Customer)
.WithMany(x => x.Addresses)
.HasForeignKey(x => x.CustomerID);
And here is the final model in the database:
I know you're trying to figure out the Entity Framework way of doing this, but if I were designing this I would recommend not even wiring up MailingAddress to the database. Just make it a calculated property like this:
public MailingAddress {
get {
return Addresses.Where(a => a.IsPrimaryMailing).FirstOrDefault();
}
}

Categories