Foreign Key Binding issue - c#

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

Related

automapper Many-to-Many relation mapping

Let's say, in the BookDetails page (BookForDetailsDto) we also show the authors of that book (AuthorForListingDto). And moreover, I want to show this author list together with a little info (just the name and id) on the books (BookForAuthorListingDto) of each author.
I have a simple many-to-many relation consisting of Book, Author and BookAuthor objects.
public class Book {
public int Id { get; set; }
public string Name { get; set; }
public List<BookAuthor> Authors { get; set; }
}
public class Author {
public int Id { get; set; }
public string Name { get; set; }
public List<BookAuthor> Books { get; set; }
}
public class BookAuthor {
public int BookId { get; set; }
public Book Book { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
And I have also 3 DTOs (where I am stoping an infinite loop):
public class BookForDetailsDto {
public int Id { get; set; }
public string Name { get; set; }
public List<AuthorForListingDto> Authors { get; set; }
}
public class AuthorForListingDto {
public int Id { get; set; }
public string Name { get; set; }
public List<BookForAuthorListingDto> Books { get; set; }
}
public class BookForAuthorListingDto {
public int Id { get; set; }
public string Name { get; set; }
}
Having a configuration as the following:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Book, BookForDetailsDto>();
cfg.CreateMap<BookAuthor, AuthorForListingDto>();
cfg.CreateMap<AuthorForListingDto, BookForAuthorListingDto>();
});
I'd like to perform a mapping from Book to BookForDetailsDto like this.
BookForDetailsDto BookDto = mapper.Map<BookForDetailsDto>(book);
But as a result, I get System.NullReferenceException.
It seems like, just in the first level of mapping, AutoMapper cannot get Author information from BookAuthor object.
I am searching for a configuration option but with no luck. I should say I am a newbie with automapper and if there is a simple solution I appreciate.
Note: I saw a comment which goes like "it is not a good practice to have reference in one DTO to second DTO". But I cannot figure out how to do otherwise, because ,for example, for a clickable/navigatable child_object we need at least "a key and a display_name", so a child object of type List seems inevitable.
A new day with a new head...
I changed the mappings like the following and it works as expected:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Book, BookForDetailsDto>()
.ForMember(dto => dto.Authorss, opt => opt.MapFrom(x => x.Authors.Select(a => a.Author)));
cfg.CreateMap<BookAuthor, BookForAuthorListingDto >()
.ForMember(res => res.Id, opt => opt.MapFrom(dto => dto.Book.Id))
.ForMember(res => res.Name, opt => opt.MapFrom(dto => dto.Book.Name));
});

Can't create many-to-many relationship

I have two entites with a many-to-many relationship. Company and SearchKeyword.
Here are the models:
class SearchKeyword
{
public int ID { get; set; }
public string Text { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
class Company
{
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}
I am trying to add a SearchKeyword to a company but it won't let me. I tried this:
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
It says Object reference not set to an instance of an object. I am not sure what is null. In inspector I can see c and sk both have full values. I guess I must be missing a fundamental of how the many-to-many relationship works with EF.
What am I doing wrong?
This is because SearchKeywords is null.
Either you can assign a List to it before adding a new instance
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords = new List<SearchKeyword>();
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
Or you can do it constructor method
class Company
{
public Company()
{
SearchKeywords = new List<SearchKeyword>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}

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

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

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
}

Automapper not behaving properly

I have this line of code in my global.asax
Mapper.CreateMap<Order, OrderDTO>();
These are my classes:
public class Customer
{
public string Name {get; set;}
}
public class Order
{
public int OrderId { get; set; }
public Customer Customer { get; set; }
}
public class OrderDTO
{
public int OrderId { get; set; }
public string Name { get; set; }
}
And this is my code:
Customer cust = new Customer { Name = "Jaggu" };
Order order = new Order { Customer = cust, OrderId = 123 };
OrderDTO dto = Mapper.Map<Order,OrderDTO>(order);
my dto contains OrderId but Name is null. As per documentation it should work:
https://github.com/AutoMapper/AutoMapper/wiki/Flattening
If I change my global.asax's mapping to this:
Mapper.CreateMap<Order, OrderDTO>().ForMember(dest => dest.Name,
mapping => mapping.MapFrom(order => order.Customer.Name));
it works! This make me curious. Is the doc wrong? or am I doing it wrong?
It will work if you follow the standard naming convention:
public class OrderDTO
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
}
Notice that the property is called CustomerName and not Name. When flattening the Order model into a Dto, the Customer.Name goes into CustomerName.

Categories

Resources