I'm having a problem with my Entity Framework 6.0 my set up is as follows
public Post
{
[Key]
public int Id {get;set;}
public String Name {get;set;}
public virtual List<Category> Categories {get;set;}
}
public Category
{
[Key]
public int Id {get;set;}
public string Name {get;set;}
public virtual List<Post> Posts {get;set;}
}
so the problem arises when I try to modify one of the lists like this
Posts.Categories.Remove(category);
Posts.Categories.Add(newCategory);
entities.SaveChanges();
I get the following exception, and the exception only happens when I try to modify a Post that has already been created and that has categories.
If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I'm not too sure what to do in this case, should I delete the Post from the Category as well ??? Keep in mind that by removing the Category from the list I just want to remove it from that collection not to remove the whole object from my DB. Any suggestions ?
This is my first post to StackOverflow if somebody needs more info please let me know.
The way I handle these many to many relationships is as follows: (assuming Post is an object from the DB)
var tmp = Post.Categories.Select(q => q).ToList();
//delete all links
foreach (var lab in tmp) {
Posts.Categories.Remove(lab);
}
db.SaveChanges();
//add new cats
foreach (var lblgroup in myListofNewCats) {
Post.Categories.Add(db.Categories.Single(q => q.ID=something);
}
It works out best when you submit your changes after the delete.
In case there are no changes, or in case you remove and add the same entity again without submitting in between it might throw some errors.
I'm sure there might be better solutions.
You could define your middle table and then just delete the record in that. That way, you would not be deleting the category itself, which it appears that you are doing now. I suggest that you amend your models as follows:
public Post
{
[Key]
public int Id {get;set;}
public String Name {get;set;}
//For the many to 1 relationship
public virtual ICollection <PostCategory> PostCategories{get;set;}
//You wont need this anymore
//public virtual List<Category> Categories {get;set;}
}
and...
public Category
{
[Key]
public int Id {get;set;}
public string Name {get;set;}
//For the many to 1 relationship
public virtual ICollection <PostCategory> PostCategories{get;set;}
//You wont need this anymore
//public virtual List<Post> Posts {get;set;}
}
Now create model for the new table, PostCategory, this will be the middle table. I like to use a single key rather than a double key. You get more flexibility and it is easy to use when using repositories and the out of the box controller delete methods, but you can use a double key if you like - I am not showing that here. In this method, you will need to check for duplicates yourself before adding the record to the database.
public PostCategory
{
[Key]
public int Id {get;set;}
public int PostId {get;set;}
public virtual Post Post {get;set;}
public int CategoryId {get;set;}
public virtual Category Category {get;set;}
}
Remember to define "PostCategories" in your dbcontext as well. (I presume you know how to...?)
Now when you want to delete the link between the Post and the Category, just delete the PostCategory record, like this in your controller:
//Find the record where postId is the PostId and the categoryId is the CategoryId
var postRecord = db.PostCategories.FirstOrDefault(x=>x.PostId==postId && x.CategoryId==categoryId);
if(postRecord!=null)
{
db.PostCategories.Remove(postRecord)
db.SaveChanges();
}
Adding a record is also easy. I do it like this in the controller...
//First create a record to add
PostCategory pc= new PostCategory()
//wire it up... EF adds the Id fields into the record. If you have a problem
// you can even add those.
pc.Category = category;
pc.Post = post;
//add it
db.PostCategories.Add(pc);
db.SaveChanges();
I like this method because now you can save additional stuff in your PostCategory table, such as date of Post etc. I don't like Many to Many relationships, I believe that they will sooner or later be broken down to one to many and many to one... and later when you have to "fix code" - its a pain to say the least. I hope this helps.
Related
My overall data structure is:
Story
|— VotesPool
| \— Votes[]
|— ShelfStoriers[]
|— StoryTags[]
\— Chapters[]
\— CommentsThread
\— Comments[]
Deleting is done with simple _context.Stories.Remove(Story), nothing fancy
When a Story gets deleted, Chapters do too, so do StoryTagss and ShelfStorys, but nothing more from this structure, including the dependents of Chapter. The database is filled with orphans.
What those two leftovers (Story.VotesPool and Chapter.CommentsThread) have in common, is that they're both a one-to-one relationship where the reference is kept only on the parent, e.g.
class Story {
public int Id {get;set;}
public VotesPool Votes {get;set;}
}
class Comment {
public int Id {get;set;}
public VotesPool Votes {get;set;}
}
class VotesPool {
public int Id {get;set;}
public IEnumerable<Vote> Votes {get;set;}
}
Instead of what is, I suppose, the proper way to set it up:
class Story {
public int Id {get;set;}
public VotesPool Votes {get;set;}
}
class Comment {
public int Id {get;set;}
public VotesPool Votes {get;set;}
}
class VotesPool {
public int Id {get;set;}
public IEnumerable<Vote> Votes {get;set;}
public Story? Story {get;set;}
public Comment? Comment {get;set}
}
but this solution doesn't sit well with me for two reasons:
Both VotesPool and CommentsThread are sort of transient, join tables, they're not immediately relevant. Thus, referencing parents there seems a bit pointless
Using this solution would create the necessity of updating VotesPool and CommentsThread model each time I decide to attach them to some new model. If I wanted to add CommentsThread to Story, I would have to add Story to CommentsThread as well
Here's my DbContext config where all the delete behaviors are (supposedly) set up. The rest of the code is in that repo as well. I'm using EF Core and PostgreSQL.
Is the way my data is modeled, that is the one-sided relationships the crux of the issue here? Should it be fixed by making the relationship two-sided, or is there some annotation/fluent method of handling it without changing the models themselves?
This should be really simple but I think I'm having possible issues with my model. I have been working with linq over a year and I should have this simple remove easily done. Please help! It's removing both records from the database when I only want one deleted
I have a database table with these properties.
Email, EmployeeName, StoreId
jsch#m.com,Joe Schneider,9
jsch#m.com,Joe Schneider,8
I need to delete Joe Schneider with storeId 9
So I run this simple query and remove process.
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Permissions.Remove(PersonToRemove);
db.SaveChanges();
}
I am assuming one is going to say, hey your model is not right and don't put the name as a key, but I can't just be changing the model because other parts of the app are based on this model and would cause huge breaks. Could you give me advise how to edit the linq query to not delete both records?
model
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
public string Department { get; set; }
public int? StoreId { get; set; }
public String Email { get; set; }
}
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
}
The problem is here you are defining a primary key which has no length constraint on it. (MaxLength). This leads to EF generate a column with NVARCHAR(MAX). As mentioned here VARCHAR(MAX) columns are not allowed to be primary key. So correct definition should be like below
[Table("Permissions")]
public class Permissions
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] <--
[MaxLength(255)] // <---
public String EmployeeName { get; set; }
}
Edit: You need to recreate the database in order to associated tables initialized with correct settings.
Edit 2 : Also you may need a DatabaseGenerated(DatabaseGeneratedOption.None) since its not identity column.
you can set Deleted state on individual entity like so:
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Entry(PersonToRemove).State = EntityState.Deleted; // do this instead
db.SaveChanges();
}
EF should then figure out which entity you wanted to delete
UPD
I am assuming you are using EF6 and DB-first approach. I am also assuming you've got your DB context class set up with default convention model builder. It seems EF's default object tracking based on Key will not work as your key is not unique (this is a bigger problem, but I understand you're already aware of that).
You might try circumvent that convention by adding custom model builder configuration like so:
class MyDbContext : DbContext {
public virtual DbSet<Permissions> Permissions {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Permissions>().HasKey(p => new { p.EmployeeName, p.StoreId});
}
}
since you didn't share your DbContext definition this is just a snippet but hopefully gives you some ideas to explore.
this is the API reference: https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.modelconfiguration.entitytypeconfiguration-1?view=entity-framework-6.2.0
I'm having an odd problem with a DbContext saving duplicate objects. I have a graph of objects similar to the following:
public class Customer
{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<Order> Orders {get;set;}
}
public class Order
{
public int Id {get;set;}
public DateTime OrderDate {get;set;}
public ICollection<Product> Products {get;set;}
}
public class Product
{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<Orders> Orders {get;set;}
}
public class MyContext:DbContext
{
public DbSet<Customer> Customers {get;set;}
public DbSet<Order> Orders {get;set;}
public DbSet<Product> Products {get;set;}
}
This is a contrived example of course, but if I want to save two new Customers. I add them with DbContext.Customers.Add(). The customer is completely filled out with orders and products. It saves all that data just fine. But if a second customer is added, even with a totally new DbContext instance, it will add the original customer a second time. I still have a reference to the original customer, but I did not add it to the new context instance. Yet DbContext.SaveChanges will save two customers, the original one a second time, and the new one. This will exponentially increase the number of records I'm saving. And this applies to the entire graph. Orders and Products are also duplicated.
If I manually detach the object after saving, it won't save duplicates. Since I'm adding an entire Customer at once, I have to loop through all child entities and detach those as well. If I only detach Customer, it will still duplicate previous Orders and Products. This even seems to happen if I turn off Change Tracking!
Any ideas?
I tried, as suggested, to duplicate this in a small application to make it easier to share. But I couldn't get it to happen. I went over my code for the 20th time and finally noticed something.
Let's say I added a new class Company at the top of the hierarchy. So each Company had many Customers. If I add a company, save it, but keep the reference, and then use that reference to assign to the navigation property of each of the "Customers" before I add them, it will save duplicates. Even if every save (Company, Customer 1, Customer 2, etc.) has a completely new DbContext in a using statement.
I guess for now the moral of this story is don't assign navigation properties in this instance. Instead add Id columns and use Id values.
Here's my example that reproduces the issue I'm talking about. Maybe this is something you "just don't do" but i was not aware that it would result in duplicates.
I have a teacher entity that has a child entity of ICollection called YogaClasses. When I modify the list of YogaClasses for a teacher and save, entity framework adds the new rows of YogaClasses to the table 'YogaClass' but it doesn't remove or edit the old rows. So I'm left with double the data. Shouldn't "context.SaveChanges()" in my repo be smart enough to know to delete removed classes and add the new ones (edit), not just add new ones?
In my controller I have something like this for a Teacher edit.
string userId = User.Identity.GetUserId();
Teacher teacher = teacherRepository.Find(userId);
//other code left out here
teacher.YogaClasses = GetSelectedClasses(Request.Form[2]);
// other stuff here
teacherRepository.Save();
In my reposity I have this;
public void Save()
{
context.SaveChanges();
}
Here is my teacher and YogaClass entity
public class Teacher
{
public int TeacherId { get; set; }
public virtual ICollection<YogaClass> YogaClasses { get; set; }
}
public class YogaClass
{
public int YogaClassId { get; set; }
[Index]
[Required]
public int TeacherRefId { get; set; }
[ForeignKey("TeacherRefId")]
public virtual Teacher Teacher { get; set; }
}
The problem here is the relationship has not been loaded at the time you set teacher.YogaClasses. So when you set teacher.YogaClasses, it assumes the new relationship will be added. (lazyloading is still a bit late in this case). The solution is somehow you ensure to load all the relationships first (using either Include or some fake access to trigger lazyloading first), then you set the new value normally:
//fake access to trigger lazy loading (of course this works only if
//lazy loading is enabled)
var someClasses = teacher.YogaClasses;
teacher.YogaClasses = GetSelectedClasses(Request.Form[2]);
You can also clear the old classes and add new ones with a foreach loop:
teacher.YogaClasses.Clear();
foreach(var c in GetSelectedClasses(Request.Form[2])){
teacher.YogaClasses.Add(c);
}
I have a dirty solution but it doesn't seem like it's the best one.
before I save the teacher context in my repo I can call this
public void DeleteYogaClasses(Teacher teacher)
{
foreach (var yogaClass in teacher.YogaClasses.ToList())
{
context.Entry(yogaClass).State = EntityState.Deleted;
}
context.SaveChanges();
}
you need to use Attach method of the context and pass in your existing teacher object and then make the changes and call SaveChanges method. From your code it is not clear what is your context, so hard to give working code. Here is a link explaining how to add/modify
You said you need to modify YogaClasses, so from this what I assume is a Teacher object already has some YogaClasses entries and you want to update some of those entries. What you need to do is, have a list of ids of YogaClasses that you need to modify then iterate on that ids list and in that iteration loop find the existing yogaClass and attach it to the context, modify it and then call save changes (preferablly when all the changes are done so that it is not a performance hit)
Here is a suodocode for this
UpdateTeacher(int teacherId)
{
var teacher = teacherRepository.Find(teacherId);
UpdateYoga(teacher);
}
private void UpdateYoga(Teacher teacher)
{
foreach(var yoga in teacher.YogaClasses)
{
db.Context.Attach(yoga);
yoga.YogaStyle = whatEverValue;
}
db.context.SaveChanges();
}
I'm experiencing a pretty weird issue i can't explain. I can create a new entity and link it to his foreign key, but i can't update. This is my code
My Post.cs Entity :
public class Post(){
public int Id {get;set;}
public string Title {get;set;}
...
public int PostCategoryId {get;set;}
public virtual Category Category {get;set;}
}
My Category.cs Entity :
public class Category(){
public int Id {get;set;}
public string Title {get;set;}
...
public virtual IList<Post> Posts {get;set;}
}
The Update Method in the controller :
[HttpPost]
public ActionResult EditPost(Post post) {
....
_repository.UpdatePost(post);
And the UpdatePost Method in the repository :
public void UpdatePost(Post post){
var category = _db.Category.SingleOrDefault(x => x.Id == post.PostCategoryId);
post.Category = category;
// I clearly see that the post have the category in the object
_db.Entry(post).State = EntityState.Modified;
_db.SaveChanges();
Every property of my object is updated, unless the Category.
Does anyone have a solution ?
Thank you so much, i can't sleep 'cause of that issue :)
It might be that your Category variable is named Category, which is the exact same name as the class itself. Try renaming it to category (lowercase) or to PostCategory and see if that helps. Or it might be an issue with the entity state. EntityState is usually for when you bring changes directly from the view model, not after manually making changes to an object's fields.
Actually I think I know what it is. You need to fetch the Post from the database, not the Category.
Category category = _db.Category.SingleOrDefault(x => x.Id == post.PostCategoryId);
Post p = db.Post.SingleOrDefault() //whatever code you need to find the Post in the database
p.Category = category;
db.SaveChanges();
I would still remove the EntityState line.
Also, I'm assuming you're just trying to update the SQL table's "PostCategoryId" entry? In that case all you need to do is this:
Post p = db.Post.SingleOrDefault() //whatever code you need to find the Post in the database
p.PostCategoryId = post.PostCategoryId
db.SaveChanges();
Matt S.,
Maybe i'm wrong but, if i get the post from the db, i'll have to update every field that may be updated from the user (in fact, there is 10 properties in the Post entity).
And, the PostCategoryId is uptaded, any issue about that field. Tell me if i'm wrong, but i would have to avoid to request for the Category but get it on the post. Like :
var post.Category = _db.Category.SingleOrDefault(x => x.Id == post.CategoryId)
I could create a method for that and call it every time i need to retrive the Category Entity related to the Post.
But i thought i could do update the Category "into the post" and just call post.Category when I need. Maybe it's a misunderstanding from me ?