EF6 foreign key definition - entity-framework

To define a foreign key as I know, I have only reference the field like explained in this text :
public class Book
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
public string Genre { get; set; }
// Foreign Key
public int AuthorId { get; set; }
// Navigation property
public Author Author { get; set; }
}
But for me this approach is not working and I need use a code like this :
[ForeignKey("MyFkName")]
public virtual ForeignKeyTable ForeignKeyProperty { get; set; }
public int? MyFkName{ get; set; }
What I'm doing wrong please ?

The error is that you forgot to declare your Author property virtual;
// a Book belongs to exactly one Author using Foreign Key:
public int AuthorId {get; set;}
public virtual Author Author {get; set;}
Furthermore your Author should have a reference to the Books it has:
// Every Author has zero or more Books:
public ICollection<Book> Books {get; set;}
This is all that is needed to inform entity framework that you are modelling a one-to-many relationship
That your Author property should be declared virtual makes sense. After all, your Book doesn't have the Author data readily available as data.
When addressing one of the properties in Book.Author, entity framework has to create a join query instead of returning data.
Hence the Author in the Author property isn't a real Author, but an object derived from Author that knows how to get the Author data of the Book from the database.

Related

Cascade delete in one to one relationship

I want to have cascade delete in 1:1 relationship, where i reference multiple entities to one. Problem is throws me an error on database update
Introducing FOREIGN KEY constraint 'FK_dbo.CategoryArticles_dbo.Articles_Article_Id' on table 'CategoryArticles' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
RoutingSeo entity is for storing seo friendly url in database for later usage. My problem is clearly M:N relationship between Article and Category. Is there something how can I deal with this problem?
Here are my entities of my model
public class Article : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Category> Categories { get; set; }
[Required]
public virtual RoutingSeo RoutingSeo { get; set; }
public int RoutingSeoId { get; set; }
}
public class Category : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Article> Articles { get; set; }
[Required]
public virtual RoutingSeo RoutingSeo { get; set; }
public int RoutingSeoId { get; set; }
}
public class SpecificProduct : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public RoutingSeo RoutingSeo { get; set; }
public int RoutingSeoId { get; set; }
}
public class RoutingSeo : IEntity<int>
{
public int Id { get; set; }
public string SeoRoute { get; set; }
public Article Article { get; set; }
public SpecificProduct SpecificProduct { get; set; }
public Category Category { get; set; }
}
Here is my fluent api code where i specify cascade delete
modelBuilder.Entity<Article>()
.HasRequired(x => x.RoutingSeo)
.WithOptional(x=>x.Article)
.WillCascadeOnDelete();
modelBuilder.Entity<Category>()
.HasRequired(x => x.RoutingSeo)
.WithOptional(x=>x.Category)
.WillCascadeOnDelete();
modelBuilder.Entity<SpecificProduct>()
.HasRequired(x => x.RoutingSeo)
.WithOptional(x=>x.SpecificProduct)
.WillCascadeOnDelete();
You are right, it is your many-to-many relation ship between Article and Category: one Article has zero or more Categories and every Category may be used by zero or more Articles.
If you delete an Article, its Categories can't be deleted automatically, because the Category might be used by other Articles, and even if it isn't used right now, entity framework doesn't know whether you want to use it tomorrow. After all, you specified that every Category might be used by zero or more Articles.
Similarly, if you remove a Category, entity framework can't automatically remove the Articles belonging to this category.
This differs from a one-to-many relationship. For example, if you have a one-to-many relationship of a Book and its Pages, then every Book has zero or more Pages and every Page belongs to exactly one Book.
If you remove the Book, then entity framework knows that it should automatically remove all Pages of the Book, which are all Pages with a foreign key BookId. If Entity Framework would only remove the Book, then we would have a bunch of Pages with foreign key value pointing to a non-existing Book. So in one-to-many relations, entity framework can cascade on delete.
Alas, in many-to-many this is not possible.
On the bright side, you have the advantage that you can delete the last Article of a Category, and keep the Category intact. Tomorrow you can add a new Article that uses this Category.
So if you want to remove an article, you manually have to remove it from the 'Categories` it uses:
many-to-many following the standard naming conventions:
class Article
{
public int Id {get; set;}
// an Article belongs to zero or more Categories:
public virtual ICollection<Category> Categories {get; set;}
...
}
class Category
{
public int Id {get; set;}
// a Category is used by zero or more Articles:
public virtual ICollection<Article> Articles{get; set;}
...
}
Don't forget to declare your ICollections virtual!
class MyDbContext : DbContext
{
public class DbSet<Article> Articles {get; set;}
public class DbSet<Category> Categories {get; set;}
}
You don't have to mention the junction-table, entity framework will make it automatically for you, but you won't have to use it for joins if you want Articles with their Categories, or Categories with their Articles, just use the ICollections
Note: As Categories is not the expected plural of Category, you'll have to tell entity framework the proper table name. Out of scope of this question.
Delete an Article, but keep all Categories it belongs to alive:
using (var dbContext = new MyDbContext(...))
{
Article articleToRemove = ...
dbContext.Articles.Remove(articleToRemove);
dbContext.SaveChanges();
}
Entity framework will automatically perform the proper joins, and remove the articleToRemove from every Category. However, the Categories won't be removed.
In fact, internally the Categories table doesn't change at all. All records with Article.Id will be removed from the junction table.

Entity Framework Code First existing database mapping relationship

I have an existing database which I would like to use Entity Framework Code First against in the most simple way possible. It is only a small database.
I have created simple POCO classes which mirror the database tables:
e.g.
public class Author
{
[Key]
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public virtual ICollection<Books> Books { get; set; }
}
public class Books
{
[Key]
public int BookID { get; set; }
public string BookName { get; set; }
public int AuthorID { get; set; }
}
And a DbContext as follows:
public class Entities : DbContext
{
public Entities(string connString)
: base(connString)
{
}
public DbSet<Author> Authors { get; set; }
public DbSet<Books> Books { get; set; }
When I run my application, and select the first Author from my database, the AuthorID and AuthorName properties are populated correctly. However, the collection of Books is not populated. Instead there is an exception of type 'System.Data.EntityCommandExecutionException', and an inner exception of: 'Invalid column name 'Author_AuthorID'.
How can I establish correctly the link between Author and Books? (i.e. one to many, one Author can have many Books). I have created the Code First very simply - no migrations or auto-generation in any way, and would like to keep it as simple as this.
Many thanks for any help,
Martin
Fluent API Approach :
modelBuilder.Entity<Author>().HasMany(a => a.Books).WithRequired().HasForeignKey(b => b.AuthorID);
update :
with this fluent API you don't have to add property Author in Class Books
if you want to set Books to not required, you must set property AuthorID to int?
modelBuilder.Entity<Author>().HasMany(a => a.Books).HasForeignKey(b => b.AuthorID);
Books class :
public class Books
{
[Key]
public int BookID { get; set; }
public string BookName { get; set; }
public int? AuthorID { get; set; }
}
Add Author property (without it single AuthorID property not matter at context of relation with Authors table.):
public class Books
{
[Key]
public int BookID { get; set; }
public string BookName { get; set; }
public int AuthorID { get; set; }
//add this(attribute is not needed if you use EF6 or higher):
[ForeignKey("AuthorID")]
public virtual Author Author { get; set; }
}
Well, this is embarassing, I found an answer minutes after my post. I will post the answer, rather than deleting my question in case it helps anyone else:
public class Books
{
[Key]
public int BookID { get; set; }
public string BookName { get; set; }
[ForeignKey("Author")]
public int AuthorID { get; set; }
public Author Author { get; set; }
}

Entity Framework foreign key object name change

I have an annoying problem that i can't seem to solve. Lets say i have a database with two tables.
Student
INT Id
NVARCHAR(30) Name
INT PrimaryTeacherId
INT SecondaryTeacherId
Teacher
INT Id
NVARCHAR(30) Name
Now when i set foreign key for PrimaryTeacherId and SecondaryTeacherId and use DatabaseFirst mapping in my project i get something like this for Student table
public partial class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int PrimaryTeacherId { get; set; }
public int SecondaryTeacherId { get; set; }
public virtual Teacher Teacher { get; set; }
public virtual Teacher Teacher1 { get; set; }
}
Note the virtual part of the class and their names, Teacher and Teacher1. No matter how i call my FKs entity framework will just override it and set increment names. That's ok if i have one or two keys to the same table but when there is more it's easy to get lost and code looks kinda annoying having object names with numbers in them. I know i can change generated classes name in my solution but when i update model changes will be lost. I'm also using Metadata partial classes for generated classes (mostly for validation and display attributes), can i change name there maybe?
TLDR: I would like to have Teacher and Teacher1 have custom names, so something like this would be awesome
public partial class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int PrimaryTeacherId { get; set; }
public int SecondaryTeacherId { get; set; }
public virtual Teacher PrimaryTeacher { get; set; }
public virtual Teacher SecondaryTeacher { get; set; }
}

Adding "id" properties where there are navigation properties (EF)

If I have a navigation property (ie: virtual), for example:
public virtual User Author { get; set; }
And I want to have also the id of that User, ie:
public int AuthorId { get; set; }
How can I tell EF that this "AuthorId" must be related to the Author property?
I don't like the idea of this being automatic (EF magically deducing this).
Because one could have multiple references to the same table:
public virtual User Author { get; set; }
public int AuthorId { get; set; }
public virtual User Receiver { get; set; }
public int ReceiverId { get; set; }
Entity framework will assume that the AuthorID is the FK of the Author class. See this blog post for the details.
You could also explicitly tell EF, by using data annotations:
[ForeignKey("Author")]
public int AuthorId {get;set;}
or by using fluent mappings:
modelBuilder.Entity<YourClass>().HasRequired(p => p.Author)
.WithMany()
.HasForeignKey(p => p.AuthorId);

EF CTP4 Missing columns in generated table

I'm having an issue that i just can't seem to figure out. Lets say I have 2 Entities defined in my domain; Person and Document. Below is the definition for Document :
public class Document
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Title { get; set; }
public DateTime Date { get; set; }
public virtual Person Owner{ get; set; }
public virtual Person AssignedTo { get; set; }
}
Now, when EF CTP4 creates the SQL table on initialize, there is only one field mapping to a Person.Id being Owner_id. Whatever i try, the field for AssignedTo is never created.
Anything that could solve this?
Regards,
avsomeren
Your code perfectly created the desired schema in the database for me:
If you don't get this schema in you DB then my guess is that something is not right with the rest of your object model. Could you post your full object model please?
Another Solution:
While your current Document class will give you the desired results, but you can still take advantage of the Conventions for Code First and explicitly specify the FKs for your navigation properties:
public class Document
{
public int Id { get; set; }
[Required][StringLength(255)]
public string Title { get; set; }
public DateTime Date { get; set; }
public int OwnerID { get; set; }
public int AssignedToID { get; set; }
public virtual Person Owner { get; set; }
public virtual Person AssignedTo { get; set; }
}
Code First will now infer that any property named <navigation property name><primary key property name> (e.g. OwnerID), with the same data type as the primary key (int), represents a foreign key for the relationship.
This essentially results to the same DB schema plus you have the FKs on your Document object as well as navigation properties which gives you ultimate flexibility to work with your model.