I modeled my m:m tables following the advice here code first many to many. Below is my model (and this is reflected in the database):
public class Catalog {
...
public virtual ICollection <CatalogItem> CatalogItems {get;set;}
public virtual ICollection <CatalogSubscriber> CatalogSubscribers {get;set;}
}
public class Item {
...
public virtual ICollection <CatalogItem> CatalogItems {get;set;}
}
public class Subscriber {
...
public virtual ICollection <CatalogSubscriber> CatalogSubscribers {get;set;}
}
public class CatalogItem{
[ForeignKey("Catalog")]
public Guid Catalog_Id { get; set; }
[ForeignKey("Item")]
public Guid Item_Id { get; set; }
public virtual Catalog Catalog { get; set; }
public virtual Item Item { get; set; }
}
public class CatalogSubscriber{
//similar to catalogitem
}
I am getting the error "invalid column Subscriber_Id" when trying to get catalogs, and I can see in the select statement that for some reason it thinks that Subscriber_Id is a column in Catalog (it isn't). I tried this solution, but to no avail. Funny thing is that CatalogItems were working perfectly, till I added the m:m on CatalogSubscribers. It doesn't seem to have a problem with CatalogItems at all. What am I doing wrong here?
Normally you don't even need the CatalogItem or CatalogSubscriber classes. EF will infer them for you, so long as the only fields are the two foreign keys.
public class Catalog {
...
public virtual ICollection<Item> Items {get;set;}
public virtual ICollection<Subscriber> Subscribers {get;set;}
}
public class Item {
...
public virtual ICollection<Catalog> Catalogs {get;set;}
}
public class Subscriber {
...
public virtual ICollection<Catalog> Catalogs {get;set;}
}
After reading both the links in your question, both answers there are correct. Except you've tried to manually create a custom cross reference table, which you don't need if it only contains the foreign key ids. In that case, you don't create the class at all, and use the Fluent API in your second link to tell EF that you have a Many to Many relationship between Catalog and Item, and another one between Catalog and Subscriber. Like this (I think -- I normally go database first out of habit):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Catalog>().HasMany(m => m.Items).WithMany();
modelBuilder.Entity<Catalog>().HasMany(m => m.Subscribers).WithMany();
}
Related
I recently delete a column ConversationId from my tables. When I start to debug my service and try to save I am getting an error:
Invalid column name 'ConversationId'.
Code:
public class AstootContext : DbContext
{
public AstootContext(DbContextOptions<AstootContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public DbSet<ServiceRequest> ServiceRequests { get; set; }
}
And my entity looks like this:
public class ServiceRequest
{
public int Id { get; set; }
public int SenderUserId { get; set; }
public int PriceTypeId { get; set; }
public decimal Price { get; set; }
public bool IsAccepted { get; set; }
public DateTime Created { get; set; }
public int MessageId { get; set; }
}
All references to ConversationId were removed from the code, I've rebuilt, yet I'm still getting this error and I don't understand why.
This is my SQL Server table as you can see there is no ConversationId:
Is there a secret cache that I need to delete or something I have to run to update this?
EF Core is code based ORM, with the most important here being the M - Mapper. It doesn't matter what the actual database structure is, the important is what EF *thinks** it is based on your code model (entity classes and their properties, combined with data annotations, fluent configuration and set of conventions).
So the problem should originate from code. Since you've removed the explicit property, it should be caused by shadow property. And as explained in the documentation link, shadow properties are usually introduced by convention from relationships:
Shadow properties can be created by convention when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced.
The documentation also explains the naming rules applied in different scenarios.
A shadow property called ConversationId can be introduced in a several ways, but according to the provided information, the most likely cause is to have an entity class called Conversation defining one-to-many relationship with ServiceRequest by having a collection type navigation property:
public class Conversation
{
public int Id { get; set; }
// ...
public ICollection<ServiceRequest> ServiceRequests { get; set; }
}
Which according to your comment was indeed the case.
For completeness, here are some other possible scenarios generating such property:
(1) No collection navigation property in Conversation, reference navigation property in ServiceRequest:
public class Conversation
{
public int Id { get; set; }
// ...
}
public class ServiceRequest
{
// ...
public Conversation Conversation { get; set; }
}
(2) No navigation properties in Conversation and ServiceRequest, fluent configuration:
modelBuilder.Entity<Conversation>()
.HasMany<ServiceRequest>();
or
modelBuilder.Entity<ServiceRequest>()
.HasOne<Conversation>();
or variations of the above.
(3) No relationship involved, shadow property created through fluent configuration:
modelBuilder.Entity<ServiceRequest>()
.Property<int>("ConversationId");
I have the following class:
public class Delivery
{
// Primary key, and one-to-many relation with Customer
public int DeliveryID { get; set; }
public virtual int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
// Properties
string Description { get; set; }
}
Can someone explain why they Customer information is coded with virtual. What does it mean?
Judging by the comments, you are learning Entity Framework?
virtual here would mean you are trying to use lazy loading - when related items like Customer can be loaded by EF automatically
http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
For example, when using the Princess entity class defined below, the related unicorns will be loaded the first time the Unicorns navigation property is accessed:
public class Princess
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Unicorn> Unicorns { get; set; }
}
Can someone explain why they Customer information is coded with virtual. What does it mean?
The virtual keyword means that a super class derived from this base class (i.e. Delivery) can override that method.
If the method was not marked as virtual then it would not be possible to override that method.
Guess you are using EF.
What happens when you make a NavigationProperty virtual is that EF dynamically creates a derived class.
That class implements functionality that allows for lazy loading and other tasks like maintaining relations that EF performs for you.
Just to get the idea your sample class dynamically becomes something like this:
public class DynamicEFDelivery : Delivery
{
public override Customer Customer
{
get
{
return // go to the DB and actually get the customer
}
set
{
// attach the given customer to the current entity within the current context
// afterwards set the Property value
}
}
}
You can easily see this while debugging, the actual instance types of your EF classes have very weird names, since they are generated on the fly.
Having difficulty finding relevant search results...
Given this model:
public abstract class A
{
public int ID { get; set; }
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}
public class B : A
{
}
public class C : A
{
}
public class Customer
{
public int ID { get; set; }
public virtual ICollection<B> Bs { get; set; }
public virtual ICollection<C> Cs { get; set; }
}
With this configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>().ToTable("As");
modelBuilder.Entity<B>().ToTable("Bs");
modelBuilder.Entity<C>().ToTable("Cs");
base.OnModelCreating(modelBuilder);
}
I get this result in the DB:
Question:
Is inheritance of navigation properties not supported? If I add public string SomeSharedProperty { get; set; } to A then as I would expect the column for that property only showed up in the As table.
What reason is there for the Customer_ID column in the Bs and Cs table? Is there any way to tell EF to not map that inherited property?
Thanks!
First off, inheritance is supported. But it seems in this specific instance not as you would expect.
Since Relational DBs do not support inheritance as we know it from object oriented programming there has to be some kind of transformation in order to make it happen.
Here is a series of blog post covering the issue in detail:
Table per hierarchy
Table per type
Table per class
It also tries to give guidelines when to use which of the strategies.
UPDATE
Apparently this is more tricky than it seemed at first glance. What you see is most likely due to a circular reference: A -> B -> Customer -> Bs.
The CustomerID columns of Bs/Cs are NOT the inherited ones from the As Table. It is in fact the representation of the relation properties specified on the Customer class:
public virtual ICollection<B> Bs { get; set; }
results in a nullable CustomerID column on table B.
public virtual ICollection<C> Cs { get; set; }
results in a nullable CustomerID column on table C.
So those nullable columns are used to represent the relation Customer -> Bs and Customer -> Cs. Their appearance has nothing to do with the Customer property on the A class.
You can easily check this by removing the navigation properties of the customer class. Then the result is what you would expect: A CustomerID column on the A table and no CustomerID column on B / C Table.
So in order to solve this you need to specifically tell EF how to resolve the circular reference. Not sure this is possible though, I'm afraid you will need to omit the Bs/Cs properties on the Customer and write a LINQ query instead to retrieve the info.
If you need those properties on the Customer class you can do is something like this:
public class Customer
{
public int ID { get; set; }
// this is necessary to have access to the related Bs/Cs
// also it cant be private otherwise EF will not overload it properly
public virtual ICollection<A> As { get; set; }
public IEnumerable<B> Bs { get { return this.As.OfType<B>(); } }
public IEnumerable<C> Cs { get { return this.As.OfType<C>(); } }
}
Using MVC EF4.1, I am trying to link a table (TableMaster) to TableChildOne (relationship one-to-zero-or-one) and also to TableChildTwo (also one-to-zero-or-one).
TableChildOne and TableChildTwo are not directly linked.
TablechildOne and TableChildTwo needs to share the primary key of TableMaster (I read this is not possible, any workarounds?)
I am including an image to make this a bit more clear, not sure if there should be foreign keys added somewhere, this is not an actual model created by the code, but is what i would like. not sure if there should be foreign keys somewhere?
image : http://www.davidsmit.za.net/img/untitled.png
My code below compiles, but when trying to add a controller, I get the error :
"An item with the same key has already been added"
public class TableMaster
{
public int TableMasterID { get; set; }
public DateTime ReportDate { get; set; }
public virtual TableChildOne TableChildOne { get; set; }
public virtual TableChildTwo TableChildTwo { get; set; }
}
public class TableChildOne
{
[Key]
public int TableMasterID { get; set; }
public String Description_1 { get; set; }
public virtual TableMaster TableMaster { get; set; }
}
public class TableChildTwo
{
[Key]
public int TableMasterID { get; set; }
public String Description_2 { get; set; }
public virtual TableMaster TableMaster { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TableMaster>()
.HasOptional(p => p.TableChildOne).WithRequired(p => p.TableMaster);
modelBuilder.Entity<TableMaster>()
.HasOptional(p => p.TableChildTwo).WithRequired(p => p.TableMaster);
}
When I remove the second table completely, it works fine.
I used the below link as an example (tables OfficeAssignment and Student), which shows how to link a table one-to-zero-or-one. But I have trouble adding another table with the same linkage:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
Any help will be appreciated.
Thanks
appelmeester
Could you give more background about why you want to do this? If you are sharing the primary key across three tables you are partitioning data. What development scenario are you trying to address. It sounds like you might be wanting to map an object inheritance, is that right?
If you truly only have a couple of Descriptions, then this is really just one table.
EDIT:
Cool. Because the business context of this request is a bit vague, I can't quite understand still, sorry. If you have a TableMaster and then some child tables, then this sounds like an inheritance tree. So with EF, you can choose many different strategies to model this (TPH, TPT etc). For this, I would suggest looking into TPT because this might allow you to get the granularity for how you want to clean up the data. Also, you get the benefit that the tables will be created, by default, largely like you have specified. Check this out for reference.
I have two classes defined as such:
public class Questionnaire
{
public int QuestionnaireID { get; set; }
public string Title { get; set; }
public bool Active { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<Vendor> Vendors { get; set; }
}
public class Vendor
{
public int VendorID { get; set; }
public string VendorName { get; set; }
public virtual ICollection<Questionnaire> OpenQuestionnaires { get; set; }
public virtual ICollection<Questionnaire> SubmittedQuestionnaires { get; set; }
public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}
I beleive this is the correct way to establish a many-to-many relationship between these classes, and when the project is built, I would expect three tables to be created.
However, when I attempt to to relate one Questionnaire to two different Vendors, I receive the following error when attempting to save the changes (context.SaveChanges()):
*Multiplicity constraint violated. The role 'Vendor_OpenQuestionnaires_Source' of the relationship 'QuestionnaireApp.Models.Vendor_OpenQuestionnaires' has multiplicity 1 or 0..1.*
If I assign a Questionnaire to only one Vendor, save the changes and then assign it to another and again save changes I no longer get the error; however the Questionaire is then related only to the last Vendor to which it was assigned, indicating that (at best) there is a one-to-many relationship being created.
I'm hoping that there is something wrong with the way I'm declaring the many-to-many relationship between these classes, or perhaps there is something I need to add to the context class to "encourage" the relationsip, but perhaps many-to-many relationships like this are not supported, or cannot be created using "Code First"?
Thank you for your time,
Jason
If you don't have any Fluent API code your expected mapping relies on EF Code First conventions. The convention which you expect to kick in here is the AssociationInverseDiscoveryConvention. Now if you look in Intellisense (and probably also documentation) it says about this convention:
Convention to detect navigation properties to be inverses of each
other when only one pair of navigation properties exists between the
related types.
Now, that's the problem: You don't have only "one pair" of navigation properties between Questionnaire and Vendor. You have two collections in Vendor refering to Questionnaire and one collection in Questionnaire refering to Vendor. The result is that this convention doesn't get applied and EF maps actually three one-to-many relationships with only one end exposed as navigation property in the model.
Moreover the mapping you want to achieve is not possible with your model: You cannot map the one end Questionnaire.Vendors to the two ends Vendor.OpenQuestionnaires and Vendor.SubmittedQuestionnaires.
One workaround is to change your model the following way:
public class Vendor
{
public int VendorID { get; set; }
public string VendorName { get; set; }
[NotMapped]
public IEnumerable<Questionnaire> OpenQuestionnaires
{
get { return Questionnaires.Where(q => q.IsActive); }
}
[NotMapped]
public IEnumerable<Questionnaire> SubmittedQuestionnaires
{
get { return Questionnaires.Where(q => !q.IsActive); }
}
public virtual ICollection<Questionnaire> Questionnaires { get; set; }
public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}
Now Vendor.Questionnaires is mapped to Questionnaire.Vendors (AssociationInverseDiscoveryConvention should detect this) and the helper properties OpenQuestionnaires and SubmittedQuestionnaires allow you to pull out the selected items. (I'm not sure if IsActive is your distinguishing flag. Otherwise you have to introduce some new flag.)
The [NotMapped] attribute is just here to make it explicite. It is probably not necessary because EF won't map IEnumerable collections and readonly properties with only a getter anyway.
Go figure, after an hour or so of searching, I go and find the exact answer 30 seconds after I post my question.
The solution was to add the following to the context class:
modelBuilder.Entity<Vendor>()
.HasMany<Questionnaire>(x => x.OpenQuestionnaires)
.WithMany(x => x.Vendors)
.Map(x =>
{
x.MapLeftKey("vID");
x.MapRightKey("qID");
x.ToTable("VendorQuestionnaires");
});
I found the answer by reading this Stack Overflow post: EF Code First Many-to-Many not working