I have been playing with Entity Core classes such as DbContext and had the following error trying to save an object:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
I basically have my erd with a many to many such as
comment comment_category category
id comment_id id
text category_id name
the comment_category table is a combo primary key to map comments to categories
retrieving data is fine but when I try to save it complains about the relationship
the models I am most interested in look like
public class Comment
{
[Key]
public int Comment_Id {get;set;}
public string Text {get;set;}
public virtual List<Category> Categories { get; set; }
}
public class Comment_Category
{
[Key, Column(Order = 0)]
public int Comment_Id {get;set;}
[Key, Column(Order = 2)]
public int Factor_Id {get;set;}
}
And its used such as
#Comments have Categories with Category Id filled and Comment Id null
List<Comment> comments = getComments();
using(dbContext db = new dbContext())
{
foreach( Comment c in comments)
db.Comments.add(c);
db.SaveChanges();
}
I am not entirely sure why it can find it easily enough but has a hard time saving. The only difference I can think of is that the comments I am saving are new so they only have Comment Categories with no Comment id just category ids. I assumed that it would save the comment and assign the comment_id to the comment_category table so I am not sure how to accomplish this
I realize that perhaps my approach is wrong in that I am using the mapping table as opposed to the actual entity for categories so if anyone knows a better way please share.
Thanks!
The easiest way to do this without a lot of ceremony is to also have a collection of Comments on Category and let Entity Framework infer the M:M relationship. It will automatically create a CategoryComments table with primary and foreign keys.
So for the model we simply have:
public class Comment
{
[Key]
public int Comment_Id { get; set; }
public string Text { get; set; }
public virtual List<Category> Categories { get; set; }
}
public class Category
{
[Key]
public int Category_Id { get; set; }
public string Name { get; set; }
public virtual List<Comment> Comments { get; set; }
}
Usage would be something like:
public class MyDbContext : DbContext
{
public DbSet<Comment> Comments { get; set; }
public DbSet<Category> Categories { get; set; }
}
using (var db = new MyDbContext())
{
var blue = new Category { Name = "Blue" };
var red = new Category { Name = "Red" };
db.Categories.Add(blue);
db.Categories.Add(red);
db.Comments.Add(new Comment
{
Text = "Hi",
Categories = new List<Category> { blue }
});
db.SaveChanges();
}
Related
I have the following entities:
public class User
{
public User()
{
UserName = new Name();
UserEmail = new Email();
}
[Key]
public int Gid { get; set; }
public Name UserName { get; set; }
public Email UserEmail { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public int UserId { get; set; }
public string Content { get; set; }
public string ImageUri { get; set; }
public virtual User Author { get; set; }
}
public class Reaction
{
public int ReactionId { get; set; }
public string Text { get; set; }
public string IconUri { get; set; }
}
One user can have many posts, and one post can have many reactions. The problem is that a reaction should store a reference to its post and the user which reacted. I could make a one to many relationship between users and posts just fine.
How can i map this relationship using Entity Framework?
Addition after comment at the end
If you follow the entity framework code-first conventions for a one-to-many relationship, you don't have to add any attributes nor use fluent API to tell entity framework what you want.
Only if you want different table names, property types, column names, or other specialties about the relations between tables you'll need attributes or fluent API.
You problem is caused because you ommited some of the one-to-many definitions in the class definitions
Your user:
public class User
{
public int Id { get; set; }
// every user has zero or more Posts (one-to-many)
public virtual ICollection<Post> Posts { get; set; }
...
}
The Post:
public class Post
{
public int Id { get; set; }
// every Post belongs to exactly one User using foreign key
public int UserId { get; set; }
public virtual Post Post {get; set;}
// every Post has zero or more Reactins (one-to-many)
public virtual IColleciton<Reaction> Reactions {get; set;}
...
}
Reactions on this Post:
public class Reaction
{
public int Id { get; set; }
// every Reaction belongs to exactly one Post using foreign Key:
public int PostId {get; set;}
public virtual Post Post {get; set; }
...
}
And finally your DbContext:
public MyDbContext : DbContext
{
public DbSet<User> Users {get; set;}
public DbSet<Post> Posts {get; set;}
public DbSet<Reaction> Reactions {get; set;}
}
This is really all that is needed for entity framework to understand that you want the one-to-many relationships and to find out which properties should become foreign key. Entity framework also understands that you can't have a Reaction without a Post. If you try to Remove a Post, all its Reactions will be removed First.
I changed some items to let it be more compliant to the code-first conventinos.
Proper pluralizaion. One Post, many Posts
All IDs are named Id (although your Id is also according to convention). I use this, so it is always clear for every class what the primary key is, even if the class changes name.
All items that won't become columns (like the ICollections) are virtual
all one-to-many have a foreign key with a property name according to the conventions
No classes have a constructor that instantiates members. After all, if you'd do a query the members would be instantiated and immediately replaced by the result of the query
One of the advantages of the ICollections it that you don't need a fairly difficulte left outer join on foreign keys if you want a User-with-his-Posts.
To get all old Users with all or some of their Posts you can use the ICollection. Entity Framework will translate this in the proper Left outer join for you:
var oldUsersWithManyReactions = myDbContext.Users
.Where(user => user.BirthDay < new DateTime(2040, 1, 1))
.Select(user => new
{
// Select only the user properties you plan to use
Id = user.Id,
FullName = user.Name.First + User.Name.Last,
// select the Posts of this User:
RecentPosts = user.Posts
.Where(post.PublicationDate > DateTime.UtcNow.AddDay(-7))
.Select(post => new
{
// again select only the Properties you want to use:
Title = post.Title,
PublicationDate = post.PublicationDate,
ReactionCount = post.Reactions.Count(),
}),
}),
}),
});
Addition after comment
If you want a "User with all his Reactions" use SelectMany. This is in fact a LINQ question and has nothing to do with Entity-Framework
var usersWithAllTheirReactions = myDbContext.Users
.Where (user => ...)
.Select(user => new
{
Id = user.Id,
Name = ...,
AllReactions = user.Posts
.SelectMany(post => post.Reactions)
.Where(reaction => ...)
.Select(reaction => new
{
ReactionDate = reaction.Date,
Text = reaction.Text,
}),
}),
});
I have a domain called Item and a domain called Category. I want to add the PK of the category to the Item but I am not sure how to do this in EF 6
public class Item
{
[Key]
public Guid Id { get; set; } = GuidCombGenerator.GenerateComb();
public string Name { get; set; }
public Category Category { get; set; }
}
public class Category
{
[Key]
public Guid Id { get; set; } = GuidCombGenerator.GenerateComb();
public string Name { get; set; }
public virtual ICollection<Item> Items { get; set; } = new List<Item>();
}
I thought I could do this
var item = new Item
{
CategoryId = dto.CategoryId ,
Name = dto.Name.Trim(),
};
dbContext.StorageItems.Add(item);
dbContext.SaveChanges();
I added another property called CategoryId and filled this in with the correct Id. However this goes and make a new Category entry instead of just linking the 2 up.
I thought I could do this but maybe I am mixing it up with NHibernate.
I then tried this (did not think this would work):
var item = new item
{
Category = new Category { Id = dto.CategoryId, },
Name = dto.Name.Trim(),
};
dbContext.StorageItems.Add(item);
dbContext.SaveChanges();
It does not like this at all.
The only option I can think of is going to the db and getting the Category object back and then adding it to Item but I would like to prevent trips to the db just to get back the id I already know.
Mark your Category virtual, create the CategoryId property, and then specify it's the foreign key for Category.
public class Item
{
[Key]
public Guid Id { get; set; };
public string Name { get; set; }
public Guid CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
}
With this, your first code (assign the Id directly) should work unless you "touch" the Category property or reference.
For this to work, make sure you haven't disabled proxies on the context
Also, make sure you don't set your Id properties to anything custom, let EF do its job
I have a very simple question. I am new to ASP.NET MVC and very much confused about relationships while following code-first technique.
I have two model classes. I want to describe it as one person can have many courses.
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
}
public class Course
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CourseId { get; set; }
// public virtual ICollection<Course> Courses{ get; set; }
/* I removed above line from model because it was not creating any Course Field in Db table of Person and added a third table */
}
In order to make a relationship I created another model class that contains Id of persons and repeating Id's of the course
public class ModelJoin
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ModelJoinId { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
public virtual Person Person{ get; set; }
//One to many relationships
public virtual ICollection<Course> Courses{ get; set; }
}
So model join will have only two properties. I want to ask how we achieve this in a best way.
Value of courses will always be null so we can not add any course in it. Where in the code we will assign it a object?
There are a lot of questions on stackoverflow but no one describes it from scratch.
Is there any tutorial for add update delete tables with foreign keys.
Any help would be appreciated.
I think you need many to many relationship as one student can be enrolled for many courses and one course can be taken by many students.
Look at this:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
The course collection should be in the Person class, try this :
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CourseId { get; set; }
}
So for example if you want to add a person with a course.
var person = new Person();
var courses = new List<Course>();
courses.Add(new Course());
person.Courses = courses;
dbContext.Persons.Add(person);
dbContext.SaveChanges();
I have a Vacation and a list of Countries. I whish to bind these together using a many-to-many relationship. I have a code-first Vacations model and Countries model. Both the individual tables aswell as the join table are successfully generated.
However, when I try to add a country to a vacation (or vice versa) the join table remains empty. I am able to successfully add the individual vacations aswell as the countries.
Models
public class Vacations
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int VacationId { get; set; }
public string ProductId { get; set; }
public string Name { get; set; }
public string Price { get; set; }
public virtual ICollection<Countries> Countries { get; set; }
public Vacations()
{
Countries = new List<Countries>();
}
}
public class Countries
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CountryID { get; set; }
public string CountryName { get; set; }
public virtual ICollection<Vacations> Vacations { get; set; }
public Countries()
{
Vacations = new List<Vacations>();
}
}
public class MyContext : DbContext
{
public MyContext()
: base("myconn")
{
}
public DbSet<Vacations> Vacations { get; set; }
public DbSet<Countries> Countries { get; set; }
}
Insert the Vacation
Vacations vacation = database.Vacations.Add(new Vacations
{
Name = vacationData.Name,
Description = vacationData.Description,
});
database.SaveChanges();
// to make sure the key is in the database to refrence
foreach (string country in AllMyCountries)
{
Countries countries = database.Countries.Add(new Countries
{
CountryName = country
});
countries.Vacations.Add(vacation);
vacation.Countries.Add(countries);
}
database.SaveChanges();
I have also tried just adding to one entity, and adding more calls to SaveChanges() inbetween.
Interesting problem in that your sample code all looks perfectly okay. Infact, I put a quick test together around this code and Entity Framework created the mapping table vacationscountries as expected and populated it correctly.
Just on the off-change, can you confirm that you are looking in the mapping table created by Entity Framework and not a custom mapping table? The only reason I mention it is that theres no way (that I know of) to map a many-to-many relationship to a custom junction table using Data Annotations.
If thats not the case, then the next thing I would do is to trace out the sql being generated by Entity Framework - either using your native database tracing tooling (e.g. Sql Profiler), a third party tool like EFProf, or through a logging interceptor ( http://msdn.microsoft.com/en-gb/data/dn469464.aspx ). Hopefully that will give some lower-level insights into the problem.
I re-created your Models but made some adjustments that generate the same results. I manually created the junction table VacationsCountries with the following code:
public class VacationsCountries
{
[Key, Column(Order = 0)]
public int VacationId { get; set; }
public virtual Vacations Vacation { get; set; }
[Key, Column(Order = 1)]
public int CountryId { get; set; }
public virtual Countries Country { get; set; }
}
I also added this line of code to the MyContext class:
public DbSet<VacationsCountries> VacationsCountries { get; set; }
Instead of using:
countries.Vacations.Add(vacation);
vacation.Countries.Add(countries);
I use:
VacationsCountries vc = database.VacationsCountries.Add(new VacationsCountries
{
Country = countries,
Vacation = vacation
});
and then call database.SaveChanges();
I checked the database and the entries were added to the VacationsCountries table.
At the moment I’m mapping my existing DB tables to new EF 4.5 model via Fluent Api. I’ve got a questing on how to map following db structure to classes.
Students(pk Id...)
Courses (pk Id...)
Students_Courses (pk Id, fk StudentId, fk CourseId)
Comments (pk Id, fk Students_Courses Id, comment, dateposted...)
The idea is that I may have many reviews per student_course pair. What is the best way to represent my classes in this scenario?
The problem here is that I’ll probably need to map Comment entity to Students_Courses table and somehow determine to which Student and Course (in terms of classes) this comment belongs.
Any suggestions on the design?
Thanks!
Maybe you'd like to use Entity Framework Power Tools to reverse-engineer your data model into a "code -first" model with DbContext API.
You will see that something like a StudentCourse class will be generated that looks like this:
public class StudentCourse
{
public StudentCourse()
{
this.Comments = new List<Comment>();
}
public int StudentCourseId { get; set; }
public int StudentId { get; set; }
public int CourseId { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
and Comment looking like this:
public class Comment
{
public int CommentId { get; set; }
public int StudentCourseId { get; set; }
public virtual StudentCourse StudentCourse { get; set; }
...
}
So comments are related with students and courses through StudentCourse