What is my mistake in implementing Foreign Key with EF 6? - c#

I am currently in the process of learning ASP.NET MVC 5 with EF 6. Right now I am stuck with declaring a foreign key with Fluent API and then seeding data to the declared tables.
Here's the code I have:
Models:
public class Person
{
public int Id { get; set; }
public ICollection<Anime> DirectedAnimes { get; set; }
}
public class Anime
{
public int Id { get; set; }
public int DirectorId { get; set; }
public Person Director { get; set; }
}
public class AnimeDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Anime> Animes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Anime>()
.HasRequired(a => a.Director)
.WithMany(p => p.DirectedAnimes)
.HasForeignKey(p => p.DirectorId);
base.OnModelCreating(modelBuilder);
}
}
Seeding data:
public class AnimeInitializer : DropCreateDatabaseAlways<AnimeDbContext>
{
protected override void Seed(AnimeDbContext context)
{
var persons = new List<Person>
{
new Person { Id = 1 },
new Person { Id = 2 }
};
var animes = new List<Anime>
{
new Anime { DirectorId = 1 },
new Anime { DirectorId = 2 }
};
persons.ForEach(p => context.Persons.Add(p));
context.SaveChanges();
animes.ForEach(a => context.Animes.Add(a));
context.SaveChanges();
}
}
But when I fetch Anime objects they have expected DirectorId values, but their Director properties arenull:
var director = (new AnimeDbContext()).Animes.First().Director; //null
var directorId = (new AnimeDbContext()).Animes.First().DirectorId; //1
Entity framework knows about the foreign key though, because adding new Anime {DirectorId = 3} in the Seed method results in a runtime error.
I am pretty sure that my mistake is very dumb and is caused by me not following examples precisely, but I've been struggling with this problem for a while already and still can't figure it out. I would very much appreciate some help.

Your navigation-property is not virtual and thus cannot be overridden by the DynamicProxy.
Change it like this:
public class Person
{
public int Id { get; set; }
public virtual ICollection<Anime> DirectedAnimes { get; set; }
}
public class Anime
{
public int Id { get; set; }
public int DirectorId { get; set; }
public virtual Person Director { get; set; }
}

Related

Can't add item with existing Id to the database

I have a comparsion list. I can add a product to it, but when I try to add another product to this list, I'm getting error:
The instance of entity type 'ProductToCompare' cannot be tracked because another instance with the key value '{ProductComparsionId: 13}' is already being tracked
What I am doing wrong?
Models:
public class ProductComparsion
{
public int Id { get; set; }
public int? UserId { get; set; }
public Guid SessionId { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public ICollection<ProductToCompare> ProductsToCompare { get; set; }
}
public class ProductToCompare
{
public int ProductComparsionId { get; set; }
public ProductComparsion ProductComparsion { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
EF:
public class AppDbContext : CmsDbContextBase, ILocalizedDbContext
{
public DbSet<ProductComparsion> ProductsComparsion { get; set; }
public DbSet<ProductToCompare> ProductsToCompare { get; set; }
}
public class AppDbContextModelProvider : ModelProvider
{
protected override void OnModelCreating(DbContext dbContext, ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductComparsion>(typeBuiler =>
{
typeBuiler.ToTable(nameof(AppDbContext.ProductsComparsion));
typeBuiler.HasKey(z => z.Id);
});
modelBuilder.Entity<ProductToCompare>(typeBuilder =>
{
typeBuilder.ToTable(nameof(AppDbContext.ProductsToCompare));
typeBuilder.HasKey(z => z.ProductComparsionId);
typeBuilder.HasOne(z => z.ProductComparsion).WithMany(z => z.ProductsToCompare).HasForeignKey(z => z.ProductComparsionId);
});
}
}
Adding data to DB:
public async Task<ProductComparsionVM> AddProductToComparsionList(List<int> productIds, int listId = 0)
{
var comparsionList = await _dbContext.ProductsComparsion
.AsNoTracking()
.Include(z => z.ProductsToCompare)
.FirstOrDefaultAsync(z => z.Id.Equals(listId));
Guid sessionId = default;
Guid.TryParse(_httpContextAccessor.HttpContext.Session.Id, out sessionId);
var products = _dbContext.Products.Include(z => z.ProductCategories);
foreach (var productId in productIds)
{
comparsionList.ProductsToCompare.Add(new ProductToCompare { ProductId = productId });
comparsionList.SessionId = sessionId;
var user = _userManager.GetUserAsync(_httpContextAccessor.HttpContext.User).GetAwaiter().GetResult();
comparsionList.UserId = user == null ? null : (int?)user.Id;
}
await _dbContext.AddAsync(comparsionList);
await _dbContext.SaveChangesAsync();
return null;
}
Edit 1
The key on the ProductToCompare model is configured as ProductComparisonId, and there is also a relationship configured between the ProductToCompare and the ProductComparison models on that property.
So when you add a ProductToCompare instance to ProductComparison.ProductsToCompare, the ProductToCompare instance is getting the ProductComparison instance's ProductComparisonId. Once you add more than one ProdcutToCompare to a ProductComparison you have two instances of ProductToCompare with the same ProductComparisonId... the same key. This is why you're getting the error.
Add an Id to ProductToCompare and make that the key, or maybe make the key composite between ProductId and ProductComparisonId to fix it.

Foreign Key Binding issue

Hi I have a list of people that also have a List of contacts.
I'm trying to link my Contact Methods so that it'll be populated with the correct Person_Id
Example
Id Name Method Value Person_Id
1 John Phone 7777777777 2
2 Joan Phone 8888888888 8
3 Jack Phone 9999999999 9
Currently it displays Person_Id as all nulls, I believe I didn't create my ContactMethod Class correctly. If I can get help establishing a proper foreign key. I think that's my issue.
// Primary Keys -------------------------------------------------------
public int Id { get; set; }
// Associations -------------------------------------------------------
public virtual Person Person { get; set; }
// Fields -------------------------------------------------------------
public string Name {get; set;
public string Value { get; set; }
I populate the information through a migration script, here's a snippet
var person = people.Find(x => x.ContactBase_GUID == contactBaseGuid);
contactMethods.AddRange(channels.Select(pair => new { pair, method = reader.Get<string>(pair.Key) })
.Where(x => x.method != null)
.Select(x => new ContactMethod { Value = x.method, Person = person }));
Working Method not utilizing foreign keys.
ContactMethod Class
// Associations -------------------------------------------------------
public int? Person_Id { get; set; }
MigrationScript
var person = people.Find(x => x.ContactBase_GUID == contactBaseGuid);
contactMethods.AddRange(channels.Select(pair => new { pair, method = reader.Get<string>(pair.Key) })
.Where(x => x.method != null)
.Select(x => new ContactMethod { Value = x.method, Person = person.Id }));
I'm going to suppose that you have a model like this:
public class Contact
{
public int Id { get; set; }
public virtual Person Person { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
Now,to achieve the escenario that you want, you need to configure a one-to many relationship between Contact and Person. There are two ways to do that, using Data Annotations or using Fluent Api. I'm going to use Fluent Api in this case. The easy way is override the OnModelCreating method of your Context to configure the relationship, for example, at this way:
public class YourContext : DbContext
{
//...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.Contacts)
.WithRequired(c => c.Person)
.Map(f => f.MapKey("Person_Id"));
}
}
As you can see, I'm specifying a PK that is not declared in your Contact class, that is the escenario that you want. Now with this configuration, you could do this, for example:
Person john=new Person(){Name = "John"};
var contactList = new List<Contact>() {new Contact(){Name = "Phone",Value = "555-444-333",Person = john},
new Contact() { Name = "email", Value = "john#gmail.com",Person = john}};
using (YourContext db = new YourContext())
{
db.Contacts.AddRange(contactList);
db.SaveChanges();
}
Update
If you want to do the same configuration using Data Annotations, your model would be like this:
public class Contact
{
[Key]
public int Id { get; set; }
[InverseProperty("Contacts")]
public virtual Person Person { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
public class Person
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
If you want to use a FK related to Person in your Contact class, you could do this:
public class Contact
{
[Key]
public int Id { get; set; }
public int? Person_Id { get; set; }
[ForeignKey("Person_Id"), InverseProperty("Contacts")]
public virtual Person Person { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
This way you can use directly the Person_Id FK in your second migration script:
var person = people.Find(x => x.ContactBase_GUID == contactBaseGuid);
contactMethods.AddRange(channels.Select(pair => new { pair, method = reader.Get<string>(pair.Key) })
.Where(x => x.method != null)
.Select(x => new ContactMethod { Value = x.method, Person_Id = person.Id }));

EntityFramework inheritance using existent base class instance

I am using TPT strategy in my model.
Here's follow my Context:
public class MyTestContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Seller> Sellers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Person>().ToTable("Persons");
modelBuilder.Entity<Seller>().ToTable("Sellers");
base.OnModelCreating(modelBuilder);
}
}
And here my entities:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Seller : Person
{
public decimal Comissao { get; set; }
}
I am trying to reuse an already existent database register.
I have 2 CRUD's, one for Person, and another to Seller.
Scenario:
I have 10 persons in my database and 0 sellers, like this seed:
using (var ctx = new MyTestContext())
{
Func<int, Person> selector = i => new Person
{
Name = string.Format("Person {0}", i)
};
var persons = Enumerable.Range(1, 10).Select(selector);
ctx.Persons.AddRange(persons);
ctx.SaveChanges();
}
I want to add new Seller, but I want to reuse alredy existent Person in database:
using (var ctx = new MyTestContext())
{
var seller = new Seller { Id = 1, Comissao = 10 };
ctx.Sellers.Add(seller);
ctx.SaveChanges();
}
When Entity Framework save, he create a new Person, and a new Seller.
How can I use inheritance strategy and be able to use the Id that I assigned in Seller instead to create a new Person and use this new Id?
You can't use TPT for one to one-or-zero relationship.
Because to be able to have one to one-or-zero relationship. You need to have Seller property on Person and Person property on Seller.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Seller Seller { get; set; }
}
public class Seller : Person
{
public decimal Comissao { get; set; }
public Person Person { get; set; }
}
But above code doesn't work, because Seller property on Person will also be inherited to Seller and it will be considered as another relationship.
Solution
Have another abstract class called BasePerson
Both Person and Seller are derived from BasePerson
Use Table Per Concrete Class mapping
Here are the classes
public abstract class BasePerson
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Person : BasePerson
{
public Seller Seller { get; set; }
}
public class Seller : BasePerson
{
public decimal Comissao { get; set; }
public Person Person { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Seller> Sellers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configures one to one-or-zero relationship.
modelBuilder.Entity<Seller>().HasRequired(x => x.Person).WithRequiredDependent(x => x.Seller);
}
}
Sample Usage
using (var context = new AppContext())
{
context.People.Add(new Person { Id = 1 });
context.SaveChanges();
}
using (var context = new AppContext())
{
context.Sellers.Add(new Seller { Id = 1 });
context.SaveChanges();
}
using (var context = new AppContext())
{
// Throws DBUpdateException, Person Id = 2 doesn't exist.
context.Sellers.Add(new Seller { Id = 2 });
context.SaveChanges();
}

Many-many relationship in Entity Framework Code First and using the "virtual" keyword to access each other

This excerpt code successfully creates a many-many relationship with an explicit Junction table that has additional data within it.
PROBLEM: I want to be able to access Courses from Student and vice versa,
(therefore the commented virtual property. But if I uncomment it, it causes an error (see below))
If I don't explicitly create a junction table (no additional data), the virtual keyword works though as EF creates a junction table by convention.
QUESTION:How can I make Student access Courses without going through Enrollments? Or is that not possible? If it's not possible, then what's the best way to go about this?
(a beginner in EF and C#)
public class Student
{
[Key]
public int StudentId { get; set; }
public string StudentName { get; set; }
//public virtual Course Courses { get; set; }
}
public class Course
{
[Key]
public int CourseId { get; set; }
public string CourseName { get; set; }
//public virtual Student Students { get; set; }
}
public class Enrollment
{
[Key]
public int EnrollmentId { get; set; }
public Student Student { get; set; }
public Course Course { get; set; }
public string Grade { get; set; }
}
public class ManyMany : DbContext, IContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public void Run()
{
Database.SetInitializer(new DropCreateDatabaseAlways<ManyMany1>());
this.Courses.Add(new Course() {CourseName = "English"});
this.SaveChanges();
}
}
WHEN I UNCOMMENT public virtual...
ERROR: "Unable to determine the principal end of an association between the types 'EF.Course' and 'EF.Student'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations."
public class Student
{
public virtual int StudentId { get; set; }
public virtual string StudentName { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course
{
public virtual int CourseId { get; set; }
public virtual string CourseName { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Enrollment
{
public virtual int StudentId { get; set; }
public virtual int CourseId { get; set; }
public virtual string Grade { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
public class ManyMany : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasKey(student => student.StudentId);
modelBuilder.Entity<Course>()
.HasKey(course => course.CourseId);
modelBuilder.Entity<Enrollment>()
.HasKey(enrollment => new { enrollment.StudentId, enrollment.CourseId } );
modelBuilder.Entity<Student>()
.HasMany(student => student.Enrollments)
.WithRequired(enrollment => enrollment.Student)
.HasForeignKey(enrollment => enrollment.StudentId);
modelBuilder.Entity<Course>()
.HasMany(course => course.Enrollments)
.WithRequired(enrollment => enrollment.Course)
.HasForeignKey(enrollment => enrollment.CourseId);
}
}
Older question concerning this, answer and more info here: Entity Framework CodeFirst many to many relationship with additional information.
EDIT:
Usage example:
var context = new ManyMany();
var physicsCourse = new Course() { CourseName = "Physics" };
var mathCourse = new Course() { CourseName = "Math" };
var studentJohn = new Student() { StudentName = "John Doe" };
var studentJane = new Student() { StudentName = "Jane Doe" };
var physicsCourseEnrollmentJohn = new Enrollment() { Student = studentJohn, Course = physicsCourse };
var mathCourseEnrollmentJohn = new Enrollment() { Student = studentJohn, Course = mathCourse };
var physicsCourseEnrollmentJane = new Enrollment() { Student = studentJane, Course = physicsCourse };
context.Courses.Add(physicsCourse);
context.Courses.Add(mathCourse);
context.Students.Add(studentJohn);
context.Students.Add(studentJane);
studentJohn.Enrollments.Add(physicsCourseEnrollmentJohn);
studentJohn.Enrollments.Add(mathCourseEnrollmentJohn);
studentJane.Enrollments.Add(physicsCourseEnrollmentJane);
physicsCourse.Enrollments.Add(physicsCourseEnrollmentJohn);
mathCourse.Enrollments.Add(mathCourseEnrollmentJohn);
physicsCourse.Enrollments.Add(physicsCourseEnrollmentJane);
context.Enrollments.Add(physicsCourseEnrollmentJohn);
context.Enrollments.Add(mathCourseEnrollmentJohn);
context.Enrollments.Add(physicsCourseEnrollmentJane);
context.SaveChanges();
var johnsEnrollments = context.Students.Where(student => student.StudentId == studentJohn.StudentId).Single().Enrollments;
MessageBox.Show(string.Format("Student John has enrolled in {0} courses.", johnsEnrollments.Count));
var janesEnrollments = context.Students.Where(student => student.StudentId == studentJane.StudentId).Single().Enrollments;
MessageBox.Show(string.Format("Student Jane has enrolled in {0} courses.", janesEnrollments.Count));
Entity Framework can't automatically determine 'many-to-many' relations because they are expressed with the help of additional tables in SQL (in your case it is Enrollment table). You can specify mappings directly in OnModelCreating method:
public class YourDbContext : DbContext
{
....
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasMany(x => x.Courses).WithMany(x => x.Students)
.Map(m =>
{
m.ToTable("Enrollment"); // Relationship table name
m.MapLeftKey("StudentID"); // Name of column for student IDs
m.MapRightKey("CourseID"); // Name of column for course IDs
});
}
}
Also, take a note that if an entity have many other entities, use collection for relationship:
public class Student
{
....
public virtual ICollection<Course> Courses { get; set; } // Many courses
}
public class Course
{
....
public virtual ICollection<Student> Students { get; set; } // Many students
}

EF4 Code only mapping inheritance

I've got the following model and I want ShiftRequest and MissionRequest to have a single table in the DB.
public class RequestBase
{
public int Id { get; set; }
public DateTime? RequestDate { get; set; }
public int UserId { get; set; }
public virtual ICollection<Notification> Notifications { get; set; }
}
public class ShiftRequest : RequestBase
{
public virtual Column Column { get; set; }
}
public class MissionRequest : RequestBase
{
public virtual Mission Mission { get; set; }
}
I've tried to do it in the override void OnModelCreating(ModelBuilder modelBuilder) method but only one RequestBases table is created:
modelBuilder.Entity<ShiftRequest>().MapSingleType().ToTable("dbo.ShiftRequests");
modelBuilder.Entity<MissionRequest>().MapSingleType().ToTable("dbo.MissionRequest");
What am I doing wrong?
EDIT
Column and Mission are also entities in my model, is that acceptable?
Check the section about TPH in this article. If Mission and Column are complex types you will also find there how to map them. Generally you have to use MapHiearchy and Case methods instead of MapSingleType.
Edit:
Here is the example:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
namespace EFTest
{
public class RequestBase
{
public int Id { get; set; }
public DateTime? RequestedDate { get; set; }
public int UserId { get; set; }
}
public class Mission
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<MissionRequest> MissionRequests { get; set; }
}
public class Column
{
public string Name { get; set; }
}
public class MissionRequest : RequestBase
{
public virtual Mission Mission { get; set; }
}
public class ShiftRequest : RequestBase
{
public Column Column { get; set; }
}
public class TestContext : DbContext
{
public DbSet<RequestBase> Requests { get; set; }
public DbSet<Mission> Missions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ContainerName = "EFTest";
modelBuilder.IncludeMetadataInDatabase = false;
// Example of complex type mapping. First you have to define
// complex type. Than you can access type properties in
// MapHiearchy.
var columnType = modelBuilder.ComplexType<Column>();
columnType.Property(c => c.Name).HasMaxLength(50);
modelBuilder.Entity<Mission>()
.Property(m => m.Id)
.IsIdentity();
modelBuilder.Entity<Mission>()
.HasKey(m => m.Id)
.MapSingleType(m => new { m.Id, m.Name })
.ToTable("dbo.Missions");
modelBuilder.Entity<RequestBase>()
.Property(r => r.Id)
.IsIdentity();
// You map multiple entities to single table. You have to
// add some discriminator to differ entity type in the table.
modelBuilder.Entity<RequestBase>()
.HasKey(r => r.Id)
.MapHierarchy()
.Case<RequestBase>(r => new { r.Id, r.RequestedDate, r.UserId, Discriminator = 0 })
.Case<MissionRequest>(m => new { MissionId = m.Mission.Id, Discriminator = 1 })
.Case<ShiftRequest>(s => new { ColumnName = s.Column.Name, Discriminator = 2 })
.ToTable("dbo.Requests");
}
}
}
Edit 2:
I updated example. Now Mission is entity instead of complex type.

Categories

Resources