Is inheritance of navigation properties supported? - c#

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>(); } }
}

Related

ASP.NET Core 2: What could be the code behind a "many-to-one" relationship in this case?

I'm preparing a project's data structure (code-first) in an ASP .NET Core 2 application, with the help of Entity Framework. This specific relationship I have no experience with: the user has to be able to choose diseases with checkboxes, and we have similar choices: cancer type, dietary, etc..
I have more than two tables like the ones on the picture, which will be referred from the UserKitProperties table. This table should work like a connector table, connects the user entity with other entities.
userid1 | cancertypeid1
userid2 | dietaryid1
userid1 | cancertypeid2
userid3 | dietaryid1
How should this be specified in the code, to support this relationship? I was thinking on doing a base class and maybe refer to that id. And this is the connector class..
public class PatientProperties : EntityModel
{
[Key]
public long ID { get; set; }
public long PatientID { get; set; }
[ForeignKey("PatientID")]
public Patient Patients { get; set; }
// this should be used for cancer type, dietary, etc..
public long PropertyID { get; set; }
/* Instead of using two classes' ids, maybe call the base class' id
[ForeignKey("PropertyID")]
public CancerType CancerTypes { get; set; }
[ForeignKey("PropertyID")]
public Dietary Dietaries { get; set; } */
}
Thank you in advance for your suggestions! :)
The following should work:
public class Property
{
public long PropertyId { get; set; }
}
public class CancerType : Property
{
// Your code
}
public class Dietary : Property
{
// Your code
}
public class PatientProperties : EntityModel
{
[Key]
public long ID { get; set; }
public long PatientID { get; set; }
[ForeignKey("PatientID")]
public Patient Patients { get; set; }
public long PropertyID { get; set; }
[ForeignKey("PropertyID")]
public Property Property { get; set; }
}
But as this MS doc mentions, setting up such inheritence will use a special Discriminator
column in the base class table, to represent what specific type is stored in a row.
I personally would resort to having nullable fields instead in order to not add more complexity. This doesn't enforce, however, that PatientProperties only has one property, which is a considerable minus:
public class PatientProperties : EntityModel
{
[Key]
public long ID { get; set; }
public long PatientID { get; set; }
[ForeignKey("PatientID")]
public Patient Patients { get; set; }
public long? CancerTypeID { get; set; }
[ForeignKey("CancerTypeID")]
public CancerType CancerType { get; set; }
public long? DietaryID { get; set; }
[ForeignKey("DietaryID")]
public Dietary Dietary { get; set; }
}
Instead of thinking about the database layout first, you should think about how you would represent this relationship in code. After all, you are doing a code-first approach.
There are basically two choices you could choose: Either the patient has multiple properties, one for each property type, or there is just a single collection for all properties:
public class Patient
{
// …
// option 1
public CancerType CancerType { get; set; }
public Dietary Dietary { get; set; }
public OtherProperty OtherProperty { get; set; }
// option 2
public IList<PatientProperty> Properties { get; set; }
}
Both of these options have their advantages and disadvantages. While option 1 is very explicit and enforces a single value for every type, it also requires you to have a (class) property for every (patient) property. So if you extend your model later, you will have to adjust your patient model.
Option 2 has the benefit that it can just collect everything. So you can just add properties to your patient without having to modify the model later if you introduce new properties. In addition, it would also directly support multiple selections for a single kind. On the downside, it does not verify anything on its own, so you need business logic to actually enforce your rules.
Moving onto the database, for option 2 you obviously need a link table since that is a many-to-many relationship now. Since you only have a link to the base type PatientProperty but you actually want to talk about the concrete type, you will need some kind of discriminator. Discriminators are basically just a notation to additionally store the kind of object in the database.
When storing data with inheritance, what is commonly done is “table-per-hierarchy”. That means that all types within the hierarchy of the PatientProperty base type will share the same table. A discriminator column is used to specify the type, and additional properties that some property types may have are implemented with nullable columns. This setup works out of the box with Entity Framework and is described in this chapter in the documentation.
The other approach, “table-per-type” is not supported in EF Core, so if you wanted to follow that, you would have to implement it yourself. But in your case, where the property types are mostly very similar, I would actually argue against that and actually keep them in the same table.
For option 1, as long as you only have a single property of each kind assigned to the patient, things are a bit simpler. Since you don’t have many-to-many there, you don’t actually need a link table. You just need to store the id for each linked property type in the patient model, as shown in the above UML. Doing that, you can also keep the property types as separate types that do not share a single table in the database.

Entity Framework foreign key relationship returns no data

I have two tables order and order_lines. They both have primary/foreign key relationship. However, when I try to retrieve data, then I don't see any data from my navigational property.
[Table("order")]
public class order : BaseModel
{
public order()
{
this.OrderLines = new List<order_lines>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public double? total { get; set; }
public List<order_lines> OrderLines { get; set; }
}
[Table("order_lines")]
public class order_lines : BaseModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public int? ordernum { get; set; }
[MaxLength(1000)]
public string sku { get; set; }
[ForeignKey("id")]
public order Order { get; set; }
}
And I am using the Linq query shown below to obtain data. I would like to see OrderLines property populated, but it returns 0 count.
var data = (from o in Context.order
select o).ToList();
The cause of your problem is that you forgot to define a property for your foreign key. Because of the foreign key attribute, I guess it will be in your order_lines table.
I noticed that you deviate from the entity framework code first conventions. Are there good reasons for this? These deviations make your code bigger, more difficult to understand, more difficult to maintain and more error prone if changed by others.
The major deviation is that you omitted a property for the foreign key. Another one is that you defined your one-to-many relation as a List<order_lines] instead of an ICollection<order_lines]. Are you sure that MyOrder.Order_Lines[4] has a defined meaning?
Besides, are you certain that the result of your query is a real List? Could it be that internally the database query would return something that is similar to a List, but in fact is something else, for instance an array? Do you really want to limit yourself and users of your code to a List?
Furthermore your one-to-many references OrderLines and Order are not declared virtual. They will not be columns in your tables.
Finally you don't stick to your own naming conventions nor to the ones commonly used for C#. Class order_lines has a plural identifier, class order has a singular identifier. order_lines is in small letters, while your reference to the collection of order_lines starts with a capital. Besides OrderLines is suddenly without an underscore
Although this is allowed, and your compiler won't complain, these deviations require several attributes or fluent API to make it work. Besides it will make if more difficult for others to read your code and understand the relations between your tables.
So my advise would be: stick to the conventions, only deviate if you really need to. Not because 'you forgot'.
If you stick to the conventions, a lot of code and most of the attributes you use are not needed.
class Order : BaseModel
{
public int Id { get; set; }
// every Order has zero or more OrderLines
public virtual ICollection <OrderLines> OrderLines { get; set; }
public double? Total { get; set; }
}
public class OrderLines : BaseModel
{
public int id { get; set; }
// every OrderLine belongs to exactly one Order, using foreign key
public int OrderId {get; set;}
public virtual Order Order { get; set; }
public int? Ordernum { get; set; }
[MaxLength(1000)]
public string Sku { get; set; }
}
Because I stuck to the conventions this is all that entity framework needs to understand your one-to-many relation. It knows which properties are your primary keys and which ones are the foreign keys. The only attribute that is left is the maximum length for Sku
Note that I changed your List to an ICollection. Furthermore I removed the constructor. In queries your constructor would create a list that would immediately be replaced by the result from the query: what a waste!
Finally I used proper names for identifiers.
Introducing a new order with several OrderLines would be done as follows:
var addedOrder = myDbContext.Orders.Add(new Order()
{
Total = calculatedTotal, // or null
OrderLines = new OrderLines[]
{
new OrderLine() {Sku = ...; ...},
new OrderLine() {Sku = ...; ...},
new OrderLine() {Sku = ...; ...},
},
});
Note that in this example I created an array of OrderLines. I defined an ICollection. Therefore I could also have used a ListOrderLine>, or (let's do crazy) use the values of a Dictionary<int, OrderLine>, or the ToList() of some query.
I could also have added the OrderLines like this:
var addedOrder = myDbContext.Orders.Add(new Order()
{
OrderLines = new List<OrderLine>(),
});
addedOrder.OrderLines.Add(new OrderLine() {...};
Or:
var addedOrder = myDbContext.OrderLines.Add(new OrderLine()
{
Order = addedOrder,
...
});
All made possible because the references between Orders and OrderLines are declared as virtual and as ICollection.
You set you foreign-key to your pk. I think it should be "orderId" not "id".
[ForeignKey("orderId")] // Select you column with you FKey
public order Order { get; set; }
And your List needs to refer to the Property. Add the attribute [InverseProperty("Order")] and Change the type to ICollection:
[InverseProperty("Order")] // Select the property-name of the Other class
public ICollection<order_lines> OrderLines { get; set; }
if you are not using the standard naming conventions, then you MUST do the following within your context to solve this problem.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Orders>()
.HasKey(e => e.CaregiverCode)
.HasMany(e => e.OrderLines)
.WithOptional(e => e.Orders)
.HasForeignKey(e => e.ordernum);
}

ef 6 many to many relationship giving invalid column error

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();
}

Helper / intermediate class has no columns in database (Entity Framework)

First off, I'm new to the Entity Framework and am migrating an existing project from a database framework that I wrote myself so I have a fair amount of flexibility in the solution I choose.
From what I've researched so far everything appears to be set up correctly. However, when my database is constructed, the table for a helper class I wrote has no columns in it (outside of its primary key). The most simplified version of the classes are included below with their relationships defined in the fluent API.
Classes
public class Concept
{
public long ID { get; set; }
[Index(IsUnique = true), MaxLength(255)]
public string Name { get; set; }
}
public class Tag
{
public long ID { get; set; }
public virtual Content Subject { get; set; }
public virtual Concept Concept { get; set; }
}
public class Helper
{
public long ID { get; set; }
public virtual Content Subject { get; set; }
public virtual List<Tag> Instances { get; set; }
// Helper functionality
}
public class Content
{
public long ID { get; set; }
public virtual Helper Helper { get; set; }
public Content() { Helper = new Helper() { Subject = this }; }
}
Context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasRequired(t => t.Concept);
modelBuilder.Entity<Tag>()
.HasRequired(t => t.Subject);
modelBuilder.Entity<Helper>()
.HasRequired(t => t.Subject)
.WithRequiredDependent(c => c.Helper);
modelBuilder.Entity<Helper>()
.HasMany(t => t.Instances);
modelBuilder.Entity<Content>()
.HasRequired(c => c.Helper)
.WithRequiredPrincipal();
base.OnModelCreating(modelBuilder);
}
Program.cs
static void Main(string[] args)
{
Content content = null;
using (var context = new Context())
{
content = context.Content.Find(1);
if (content == null)
{
content = new Content();
context.Content.Add(content);
context.Helper.Add(content.Helper);
context.SaveChanges();
}
}
}
It's also worth mentioning that when the data is saved, the Helper is assigned an ID but on loading the parent class (Content) the second time around, the Helper is not lazy loaded as I would expect from the 'virtual' keyword. I suspect that this is caused by the same issue causing the absence of data in the table.
I have tried both the data annotation and fluent API approaches that EF provides but it seems that there is something fundamental that I am misunderstanding. I would like to retain this helper class as it helps organize the code far better.
As I have spent a fair amount of time researching these relationships / APIs, and scouring Google / SO without found anything to solve this issue in particular any help would be greatly appreciated!
Updated: Solution
Thanks to a question in the comments, I realized that I was expecting to see the keys of a many-to-many relationship in the tables for the entity types themselves (i.e. in the Helpers table). However, in a many-to-many relationship, the keys will always be placed in a separate table (concatenation of type names) which was not being previously created.
By adding '.WithMany();' to the Helper section of the OnModelCreating function as below
modelBuilder.Entity<Helper>()
.HasMany(t => t.Instances)
.WithMany();
the many-to-many relationship became properly defined and the HelperTags table generated as expected. This is due to the fact that the many-to-many relationship is one way (Helpers always refer to Tags, Tags never refer to Helpers). This is also why the 'WithMany' does not have any arguments (since no Helper properties exist in the Tag class). Fixing this simple oversight solved the problem!
You are probably working harder than you need to in the on ModelCreate. You should probably redesign your classes use Identifiers, like this:
public class Tag
{
public long Id { get; set; }
public long SubjectId { get; set; }
public long ConceptId { get; set; }
public virtual Content Subject { get; set; }
public virtual Concept Concept { get; set; }
}
You need to keep the ID names the EXACT same as the object names + Id and EF will magically link everything up. If you don't want them required then make the id nullable (C# 6 == long? SubjectId).
Also, I have changed the ID -> Id; I have no idea if this matters. At one point I remember having to do that to get things working (it was YEARS ago) and I have been doing it that way ever since.
Consider reading:
Entity Framework Code First Conventions
relationship Convention
In addition to navigation properties, we recommend that you include
foreign key properties on the types that represent dependent objects.
Any property with the same data type as the principal primary key
property and with a name that follows one of the following formats
represents a foreign key for the relationship:
<navigation property name><principal primary key property name>
<principal class name><primary key property name>
<principal primary key property name>
If multiple matches are found then precedence is given in the order
listed above.
Foreign key detection is not case sensitive.
Sample Code from MSDN:
In the following example the navigation properties and a foreign key are used to define the relationship between the Department and Course classes.
public class Department
{
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }
// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
// Primary key
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
}

Why would I need to use a virtual modifier in a c# class?

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.

Categories

Resources