I tried many different examples here, but i can't seem to figure out what i'm doing wrong.
I have a table with a history table to it. I have removed many of the fields to make it easier to watch. After my migration it works fine if i watch in PHPMyAdmin and watch the primaryKey there.
I want to be able to go context.ProductArtifactDocumentState.Histories so i can get all linked histories.
DBContext
//Composite Key
builder.Entity<ProductArtifactDocumentStateHistory>()
.HasKey(k => new { k.Version, k.ProductArtifactDocumentStateId});
Table
[Table("ProductArtifactDocumentsState")]
public partial class ProductArtifactDocumentState : BaseEntity
{
public virtual ICollection<ProductArtifactDocumentStateHistory> ProductArtifactDocumentStateHistories { get; set; }
}
History Table
[Table("ProductArtifactDocumentsState_History")]
public partial class ProductArtifactDocumentStateHistory
{
[Column("ProductArtifactDocumentStateId")]
public int ProductArtifactDocumentStateId { get; set; }
public virtual ProductArtifactDocumentState ProductArtifactDocumentState { get; set; }
public int Version { get; set; }
}
The error i get:
System.InvalidOperationException: 'The entity type 'ProductArtifactDocumentStateHistory' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.'
Why do you need all this headache? Are you trying to save 2 bytes on a primary key? But after this all your ef code will be a nightmare. Just add Id field
public partial class ProductArtifactDocumentStateHistory
{
[Key]
public int Id {get; set;}
.....
}
Related
This is an existing .NET Core 3.1 project I inherited.
I have a class referring to a database table
public class SupportContract
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public int SupportContractStatusId { get; set; }
public virtual SupportContractStatus SupportContractStatus { get; set; }
}
and a sub table with a foreign key
public class SupportContractStatus
{
public int Id { get; set; }
public string SupportContractStatusName { get; set; }
}
This works fine I can get
supportContract.SupportContractStatus.SupportContractStatusName
But if I rename SupportContractStatusId to ContractStatusId in C# and the database, I get an error "SupportContractStatusId missing".
I cannot find any link between the column SupportContractStatusId and table SupportContractStatus anywhere in code nor is there any mention of the foreign key.
There is no link in the DbContext either.
Is this naming convention assumed by Entity Framework? How does the framework know of the foreign key?
Yes, the naming convention that EF expects by default is based on the class name, not the property name. It will look for ClassNameId or ClassName_Id. You can link the FK either through annotation or configuration.
I.e.
public int ContractStatusId { get; set; }
[ForeignKey("ContractStatusId")]
public virtual SupportContractStatus ContractStatus { get; set; }
Configuration is done through IEntityTypeConfiguration implementations or by implementing the OnModelCreating method in the DbContext and configuring the relationship within the modelBuilder. For occasional deviations from convention, the attribute approach can generally cover everything.
I´m currently learning to use EF and I have the following relationships:
An Alert has 1 to n occurences.
Each occurence can have 0 to n values (additional information).
public class Alert
{
// PK
public int AlertId { get; set; }
// Attributes
public int CurrentAlertLevel { get; set; }
public DateTime TimeRaised { get; set; }
public DateTime TimeLastRaised { get; set; }
// Some other attributes ommitted...
// Relations
public ICollection<AlertOccurrence> Occurrences { get; set; }
}
public class AlertOccurrence
{
// Relations which are part of the primary key
public int AlertId { get; set; }
// Attributes
public int Ordinal { get; set; }
// some ommited attributes
// Relations
public ICollection<AlertDetailValue> AlertDetailValues { get; set; }
}
public class AlertDetailValue
{
public int AlertDetailValueId { get; set; }
public int Order { get; set; }
public string Value { get; set; }
}
In the DB Context OnModelCreating I´m setting the combined PK for AlertOccurence:
modelBuilder.Entity<AlertOccurrence>().HasKey(ao => new {ao.AlertId, ao.Ordinal});
While it seems that this is working - what I would actually like to archive is the same relationship without the need to have the AlertDetailValueId as PK. The table that EF generates also includes AlertOccurrenceAlertId and AlertOccurrenceOrdinal which seems a waste of space to me.
So what I would like to do is:
Have a combined primary key for AlertDetailValue consisting of AlertDetailValue.Order and the (already combined) PK of AlertOccurence instead of the "artificial" AlertDetailValueId. Is that even possible ?
Part of my problem might be that the PK defined using the fluent api is not part of the data classes. So probably another question to ask would be: Is there a way to use a key defined in fluent api in a entity class ?
Or do I need to include AlertOccurrenceAlertId and AlertOccurrenceOrdinal in my entity class AlertDetailValue - but how do I link them then ?
As I said I´m still trying to get my head around EF so while there might be better ways to do this I´m interested in this special kind of relation / combined(combined) PK even if it might be academic... Any help would be highly appreciated.
Trying to explain what I try to do and what my problem is - and taking a good shower - helped me to ask different questions to google and focus more on the foreign key.
It´s not that I did not try to google it before... I just asked the wrong questions..
So I found this:
Mapping composite foreign key to composite primary key where the foreign key is also a primary key
(While trying the new approch #atiyar also hinted on the missing foreign key...)
My new solution was to change AlertDetailValue to intentionally include the parts that the Occurence PK is build of:
public class AlertDetailValue
{
// relations will be set up in fluent api in OnModelCreating of db context
public int AlertOccurenceAlertId { get; set; }
public int AlertOccurenceOrdinal { get; set; }
public int Order { get; set; }
public string Value { get; set; }
}
And then to tell EF that there is a combined PK and also a combined foreign key:
modelBuilder.Entity<AlertDetailValue>().HasKey(adv => new { adv.AlertOccurenceAlertId, adv.AlertOccurenceOrdinal, adv.Order });
modelBuilder.Entity<AlertOccurrence>().HasMany<AlertDetailValue>(adv => adv.AlertDetailValues).WithOne()
.HasForeignKey(adv => new {adv.AlertOccurenceAlertId, adv.AlertOccurenceOrdinal});
Here is my Entity:
public class StackImage: ICollection<StackFile>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
private IList<StackFile> StackFiles { get; set; } = new List<StackFile>();
public StackImage()
{
}
[...] // Implementation of ICollection
}
public class StackFile
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
public string Url { get; set; }
public int Position { get; set; }
public StackFile(){}
}
stackImage.Add(new StackFile(url));
stackImage= await _stackImageRepository.UpdateAsync(stackImage);
await _unitOfWork.SaveChangesAsync();
In this sample after UpdateAsync, the StackImage Id is not generated (stackImage.Id == default) but the StackFile Id is correctly generated (stackImage[0].Id == default)
Did you already noticed this problem? My guess is, EF Core see StackImage as a list and doesn't try to generate a new Guid. How to fix this issue?
EDIT:
From what I can read on the web and by responses I received, It seems not possible to do it. If someone has the solution, please let us know :)
It seems to me that you want to design a database with (at least) two tables. A table with StackImages and a table with StackFiles.
You want to design a one-to-many relation between StackImages and StackFiles: every StackImage has zero or more StackFiles, every StackFile belongs to exactly one StackImage. In a database this is implemented using a foreign key.
Hence, it is not true that a StackImage is a StackFile. However, you can say that a StackImage has some StackFiles.
Following the entity framework code first conventions your classes should be similar to:
class StackImage
{
public Guid Id {get; set;}
...
// every StackImage has zero or more StackFiles (one-to-many):
public virtual ICollection<StackFile> StackFiles {get; set;}
}
class StackFile
{
public Guid Id {get; set;}
...
// every StackFile belongs to exactly one StackImage, using foreign key:
public Guid StackImageId {get; set;}
public virtual StackImage StackImage {get; set;}
}
finally the DbContext:
class MyDbcontext : DbContext
{
public DbSet<StackImage> StackImages {get; set;}
public DbSet<StackFile> StackFiles {get; set;}
}
Note the use of virtual properties to express the relations between the tables. As the foreign key StackImageId is supposed to be a real column, it is not virtual
In entity framework the columns of a table are represented by non-virtual properties,
the virtual properties represent the relations between the tables.
Because I followed the conventions, there is no need for attributes, nor fluent API. Entity framework detects the one-to-many collection and creates the proper tables for you. Only if you want different identifiers for your tables or columns you'll need fluent API or attributes.
I'm trying to solve one puzzle, but with no luck so far.
I have an article (or blog post) and comment entities, they both have content. In order to support lazy loading for content (there is no need to load the content when I need to display a list of articles or comments) I decided to move content to separate table and organize one-to-one mapping. Here is an example of what I think:
public class Content {
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// a bunch of scalar properties, like content type and so on
}
public class BlogArticle {
[Key]
public int ID { get; set; }
public int ContentID { get; set; }
[ForeignKey(nameof(ContentID)]
public virtual Content Text { get; set; }
// other properties related to BlogArticle
}
public class Comment {
[Key]
public int ID { get; set; }
public int ContentID { get; set; }
[ForeignKey(nameof(ContentID)]
public virtual Content Text { get; set; }
// other properties related to comment
}
<...>
From first look it seems ok: I can create blog articles, comments and attach content (at first I insert content, obviously). Update works as well. However, deletion doesn't work: when I delete blog article or comment, content is not deleted (but I want to delete it when blog article or comment are deleted, not opposite).
From what I understand my biggest issue because of relationship direction: in my case, Content entity is principal end and BlogArticle and Comment are dependent ends. In order to solve the puzzle, I need to change principal/dependent relationship. Again, from what I understand in order to change relationship direction I need to have a foreign key in Content entity and use fluent API to describe who is parent (principal) and who is child (dependent) in one-to-one relationship. Since many tables (there might be other entities with content property) are pointing to Content table, it doesn't seem very easy. Am I correct in my understanding?
One possible solution I could imagine is to create multiple foreign keys in Content table and point to each related table:
public class Content {
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// foreign keys
public int BlogArticleID { get; set; }
public int CommentID { get; set; }
public int WebWidgetID { get; set; }
// other foreign keys if necessary
}
probably, foreign keys must be nullable (because only single foreign key is used at once). Then use Entity Framework fluent API to describe relationship directions and organize cascade delete. For me it looks ugly, but I have no other ideas.
My question: is my proposed solution good/reliable? Are there other options I can look at?
Thanks in advance!
All your thoughts are correct. And your proposed solution is the only way with traditional relational design. The drawback of course is the need of multiple mutually exclusive nullable FKs.
The other options I see are as follows:
(1) Using EF inheritance for the entities holding Content. e.g.
public abstract class EntityWithContent
{
[Key]
public int ID { get; set; }
public virtual Content Text { get; set; }
}
public class BlogArticle : EntityWithContent
{
// other specific properties
}
public class Comment : EntityWithContent
{
// other specific properties
}
and configured one-to-one relationship between Content (dependent) and EntityWithContent (principal) using either shared PK association or FK association.
But since EF Core currently supports only TPH strategy (i.e. all the derived entities share one and the same table with union of all fields), I won't recommend it.
(2) Making Content owned type.
This is closer to the intent, but unfortunately EF Core currently always loads the owned entity data along with the owner data (even if they are configured to be provided by different database tables), which is against your original goal, so I won't suggest that either.
(3) Using table splitting feature.
If the main goal is simple to support controlled (lazy/eager/explicit) loading and the Content is always required, then this might be the best solution so far.
It would require a bit more configuration, but at the end it will give you the original table design (single table per entity) with the desired loading behavior:
Model:
public abstract class Content
{
[Key]
public int ID { get; set; }
public string RawContent { get; set; }
// a bunch of scalar properties, like content type and so on
}
public class BlogArticle
{
[Key]
public int ID { get; set; }
public virtual BlogArticleContent Text { get; set; }
// other properties related to BlogArticle
}
public class BlogArticleContent : Content
{
}
public class Comment
{
[Key]
public int ID { get; set; }
public virtual CommentContent Text { get; set; }
// other properties related to comment
}
public class CommentContent : Content
{
}
Note that here Content class is not part of EF inheritance hierarchy, but simple base class with the common properties (abstract modifier is not strongly necessary). The actual derived classes might or might not define their own properties.
Configuration:
modelBuilder.Entity<BlogArticle>().ToTable("BlogArticles");
modelBuilder.Entity<BlogArticle>()
.HasOne(e => e.Text)
.WithOne()
.HasForeignKey<BlogArticleContent>(e => e.ID);
modelBuilder.Entity<BlogArticleContent>().ToTable("BlogArticles");
modelBuilder.Entity<Comment>().ToTable("Comments");
modelBuilder.Entity<Comment>()
.HasOne(e => e.Text)
.WithOne()
.HasForeignKey<CommentContent>(e => e.ID);
modelBuilder.Entity<CommentContent>().ToTable("Comments");
I have the following models:
public class SomeForm
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public IList<FacilityContactInformation> OriginatingFacilities { get; set; }
public IList<FacilityContactInformation> DestinationFacilities { get; set; }
}
public class FacilityContactInformation
{
public FacilityContactInformation()
{
Id = -1;
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(50)]
public string Owner { get; set; }
}
I am using automatic migrations to gen and re-gen the database schema.
This generates the error "Foreign key 'FK_dbo.FacilityContactInformations_dbo.SomeForm_SomeForm_Id ' references invalid column 'SomeForm_Id' in referencing table 'FacilityContactInformations'.
Could not create constraint. See previous errors.
I suspect the root cause is that EF tries to generate a FK FK_dbo.FacilityContactInformations_dbo.SomeForm_SomeForm_Id for both lists
Is there any way to keep using automatic migrations, but get this to generate a FK that works? It would seem like the FK should include the list name and generate two properties on FacilityContactInformations OR should generate an intermediate table to join on.
When you have 2 navigational properties that link to the same class, you should override OnModelCreating method of your dbcontext class. Then add this code into the OnModelCreating:
modelBuilder.Entity<SomeForm>
.Hasmany<FacilityContactInformation>(x => x.OriginatingFacilities);
modelBuilder.Entity<SomeForm>
.Hasmany<FacilityContactInformation>(x => x.DestinationFacilities);
This is because EF cannot determine the correct keys if the nav. prop. link to the same class.