Cascade Delete Entity within entity - asp.net-mvc

I have been working on a project and I'm trying to get the cascade delete to kick in. I have a model below I use for comments. These comments can have replies that come off of them that call the comment class. What I'm trying to do is to make it delete all the replies that can flow off the comment.
Comment -> Reply -> Reply -> Reply -> so on.
If I'm going about this in the wrong direction, please let me know. I have tried to research into this but all I come up with is One-to-One and One-Many cascade codes. I'm using CodeFirst with MVC 4 to build my project.
Edited
public class Comment
{
// Properties
public long Id { get; set; }
[Required]
[StringLength(250, ErrorMessage = "{0} must be between {1} and {2} characters", MinimumLength = 2)]
public string Body { get; set; }
[Required]
public DateTime CreateDate { get; set; }
[Required]
[InverseProperty("Comments")]
public User Author { get; set; }
[InverseProperty("CommentCount")]
public Blog Blog { get; set; }
public bool Hidden { get; set; }
public long RepliesId { get; set; }
[InverseProperty("Replies")]
public virtual Comment Comments { get; set; }
[InverseProperty("Comments")]
public virtual ICollection<Comment> Replies { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
public Comment()
{
CreateDate = DateTime.UtcNow;
Hidden = false;
}
}
Here is my DataContextInitializer
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Comment>().HasMany(i => i.Replies)
.WithOptional(i => i.Comments)
.HasForeignKey(i => i.RepliesId)
.WillCascadeOnDelete();
}

You could enable cascade delete with something like this (i.e. you need to manually set the relationship)...
modelBuilder.Entity<Comment>()
.HasOptional(x => x.Replies)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
However, it won't do you much good - as Update-Database will fail with something like...
A foreign key constraint that has an UPDATE or a DELETE CASCADE rule,
and self-references a column in the same table, is not allowed.
i.e. that works on FK-s that are connecting different tables - but not if self-referencing.
See this post with some more relevant info - especially the comment that mentions
"you need to drop the FK and manually create it with cascade delete in
your DatabaseInitializer"
EF4 Code first - delete all children when deleting parent from db?
In short, I don't think there is a straight-forward solution - but some manual setup (initializer etc.) is required (I haven't tried it). Or try to reorganize, flatten the relationships a bit (I haven't thought much, just throwing here some pretty general approaches).
Just FYI - even though I think it's not going to get you anywhere (see above)...
public class Comment
{
// Properties
public long Id { get; set; }
//[Required]
//[StringLength(250, ErrorMessage = "{0} must be between {1} and {2} characters", MinimumLength = 2)]
public string Body { get; set; }
[Required]
public DateTime CreateDate { get; set; }
// [Required]
// [InverseProperty("Comments")]
public MyUser Author { get; set; }
// [InverseProperty("CommentCount")]
public Blog Blog { get; set; }
public bool Hidden { get; set; }
public virtual ICollection<Comment> Replies { get; set; }
public virtual ICollection<Vote> Votes { get; set; }
public Comment()
{
CreateDate = DateTime.UtcNow;
Hidden = false;
}
}
modelBuilder.Entity<Comment>()
.HasOptional(x => x.Replies)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
This should work fine if you let it 'not cascade'. Otherwise fails.

Related

Entity Framework 6 and Chained Relationships

Having some issues with relationships within EntityFramework 6.
I know that DataAnnotations or FluentApi can be used and I'm okay with using either.
Here's an example of relationship I'd like to accomplish:
Student has one ImmunizationRecord
ImmunizationRecord has Multiple ShotRecords
This seems like it would be fairly straight forward, however it doesn't seem to be working as expected.
Here's example code (Updated from actual code)
public class Student : Entity
{
[Key]
public int Id { get; set; }
public String Name { get; set; }
//...
[ForeignKey(nameof(Id))]
public virtual ImmunizationRecord ImmunizationRecord { get; set; }
}
public class ImmunizationRecord : Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Key]
[ForeignKey(nameof(Student))]
public int StudentId { get; set; }
public String Name { get; set; }
public DateTime LastUpdated { get; set; }
public virtual Student Student { get; set; }
public virtual ICollection<ShotRecord> ShotRecords { get; set; }
}
public class ShotRecord: Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
// Want this to point back to ImmunizationRecord
public int ImmunizationRecordId { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public DateTime DateOfShot { get; set; }
//...
[ForeignKey("ImmunizationRecordId")]
public virtual ImmunizationRecord ImmunizationRecord { get; set; }
}
Example fluentapi might be something like this:
modelBuilder.Entity<Student>().HasOptional(c => c.ImmunizationRecord).WithRequired(m => m.Student);
modelBuilder.Entity<Student>().HasOptional(c => c.ImmunizationRecord).WithRequired(sc => sc.Student);
modelBuilder.Entity<ImmunizationRecord>().HasMany(sc => sc.ShotRecords).WithRequired(sr => sr.ImmunizationRecord);
The Result
I suspect that I'm just missing a small piece of what needs to be done, or missing the proper way to configure these entities with a similar relationship.
With the code above and class structure, I can create a Student, and Create a ImmunizationRecord, and ShotRecords without issue.
The issue occurs when I try to retrieve the ShotRecords from The ImmunizationRecord, EntityFramework will resolve on the key on the Student.Id instead of using the key of on the ImmunizationRecord.Id.
I can go into the database and change the rows for ShotRecords and update the ImmunizationRecordId to the StudentId and they'll resolve properly. But as stated before, I want them to use the key of the ImmunizationRecord, and not the student.
Any advice and guidance would be greatly appreciated!
Thank you in advance!
(Updated to a different example to make more sense)

Foreign Keys And MVC4 Code First

Trying to learn MVC and im a bit stuck on foreign keys. Trying to make a simple facebook style posting system.
Users, Posts, And Comments.
There can be many Users, Each User Can Have Many Posts To Their Wall by all other users, each post can have many comments.
The Posts To Comments relationship works fine (i presume due to the naming convention). However the users to posts relationship does not seem to work, when trying to add a user i get the error.
The INSERT statement conflicted with the FOREIGN KEY constraint MVC
What am i doing wrong here, could someone point me in the right direction please. (Also i know my DB structure sucks, im just messing about trying to learn MVC here).
USER
namespace Conference.Models
{
public class User
{
public Int32 UserID { get; set; }
public string Username { get; set; }
[ForeignKey("PostedToID")]
public virtual List<Post> Posts { get; set; }
}
}
POST
public class Post
{
[Key]
public Int32 PostID { get; set; }
//to get the name of the user who posted the post
public Int32 UserID { get; set; }
//to get the wall the post was posted to
public Int32 PostedToID { get; set; }
public DateTime PostDateTime { get; set; }
[Required(ErrorMessage = "{0} Is Required")]
[Display(Name = "Post Content")]
public String PostContent { get; set; }
public virtual List<Comment> Comments { get; set; }
}
}
COMMENT
public class Comment
{
public Int32 CommentID { get; set; }
//to get the id of the user that posted the comment
public Int32 UserID { get; set; }
public Int32 PostID { get; set; }
public DateTime CommentDateTime { get; set; }
[Required(ErrorMessage = "{0} Is Required")]
[Display(Name = "Comment Content")]
public String CommentContent { get; set; }
}
You can always set the FK relation explicitly. Remove ForeignKey attribute for Posts in User class.
// Foreign key to User
[ForeignKey("User")]
public Int32 PostedToID { get; set; }
Read this.

Code First Many-Many Error (may cause cycles or multiple cascade paths)

I have looked at so many questions and tried every solution available but it seems not to work with my specific scenario. I am trying to add a code-first many to many relationship for the below classes and receive the following error:
Introducing FOREIGN KEY constraint
'FK_dbo.AgentPoolAgents_dbo.Agents_Agent_Id' on table
'AgentPoolAgents' may cause cycles or multiple cascade paths. Specify
ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN
KEY constraints. Could not create constraint. See previous errors.
Agent
public class Agent
{
public virtual int Id { get; set; }
public virtual string AgentName { get; set; }
public virtual int UserId { get; set; }
public virtual bool Available { get; set; }
public virtual DateTime CreatedTime { get; set; }
public virtual ICollection<AgentPool> AgentPools { get; set; }
public Agent()
{
}
}
AgentPool
public class AgentPool
{
public virtual int Id { get; set; }
[Required]
[Display(Name = "Agent pool name")]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 3)]
public virtual string PoolName { get; set; }
public virtual int UserId { get; set; }
public virtual DateTime CreatedTime { get; set; }
public virtual DateTime ModifiedTime { get; set; }
public virtual ICollection<Agent> Agents { get; set; }
public AgentPool()
{
}
}
UserId is a foreign key to a UserProfile class to identify the owner of agents and agentpools.
The error appears when running a migration using the update-database package manager console command.
Any help is much appreciated!
You don't need every field to be virtual. Not sure what you're trying to achieve by doing this. Use the 'virtual' keyword to eager load navigation properties, e.g. Keep ICollection Agents virtual.
In answer to your question, look at: this question.

Entity Framework 4 CTP 5 POCO - Many-to-many or Lookup table?

I'm building a personal blog app with Entity Framework 4 CTP 5 POCO only, where a Post can have many Tags and a Tag can be in many Posts. My question is whether to build a many-to-many model, or to have a lookup table.
At first I was trying to use many-to-many, but I don't know how to do insertion, each time a new post is posted (with many tags selected), I'm not sure what to do so that the tags should be associated with the post (and wouldn't insert a new tag if the tag name already exists.)
I then try to build a Lookup table like so:
Post
public class Post
{
public int Id { get; set; }
[Required]
[StringLength(512, ErrorMessage = "Title can't exceed 512 characters")]
public string Title { get; set; }
[Required]
[AllowHtml]
public string Content { get; set; }
public string FriendlyUrl { get; set; }
public DateTime PostedDate { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual ICollection<PostTagLookup> PostTagLookups { get; set; }
}
Tag
public class Tag
{
public int Id { get; set; }
[Required]
[StringLength(25, ErrorMessage = "Tag name can't exceed 25 characters.")]
public string Name { get; set; }
public string FriendlyName { get; set; }
public virtual ICollection<PostTagLookup> PostTagLookups { get; set; }
}
PostTagsLookup
public class PostTagLookup
{
public int PostId { get; set; }
public int TagId { get; set; }
}
The problem is I'm not sure how EF are going to handle lookup table (how will I get the Tags of a Post, or a collection of the Posts when I select a Tag). And with the code below, I got an error saying PostTagLookup doesn't have an Id key:
var getPosts = _post.GetLatestPosts(3).ToList();
var posts = from post in getPosts
select new PostModel
{
Id = post.Id,
Title = post.Title,
Content = post.Content,
FriendlyUrl = post.FriendlyUrl,
PostedDate = post.PostedDate,
IsActive = post.IsActive,
NumberOfComments = post.Comments.Count(),
PostTags = post.PostTagLookups.Where(p => p.PostId == post.Id).ToList()
};
Any suggestion on how to accomplish this task? Thank you very much!
I think your model should work as-is with a slight tweak: add an ID column/field to the PostTagLookup entity.
public class PostTagLookup
{
[Key]
[DatabaseGenerated(DatabaseGenerationOption.Identity)]
public int PostTagLookupId { get; set; }
//etc.
}
However, I'm not sure why you wouldn't want EF to handle the underlying many-to-many on it's own. When you have a new Post object, for example, all you have to do is add any associated Tags to the instantiated Post's Tags collection before calling SaveChanges() on your context. Did this not work for you?
I've struggled with it for the past couple days and decided to go with the Lookup table as intended, and in the Lookup table, also have references to Post and Tag model as such:
public class PostTagLookup
{
public int Id { get; set; }
public int PostId { get; set; }
public int TagId { get; set; }
public virtual Post Post { get; set; }
public virtual Tag Tag { get; set; }
}
Might not be the best way but it's working :) Thanks all for looking.

EF CTP4: How to tell EF a column is NOT identity

I have a code-first, POCO project in which I am trying to adjust an existing database so that it syncs up with what EF is expecting, given my existing model.
I have these entities:
public class FlaggedDate
{
[Key]
public long scheduledDayID { get; set; }
[Required]
public DateTime date { get; set; }
[StringLength(50)]
[Required]
public string dateStatus { get; set; }
[Required]
public bool isVisit { get; set; }
[Required]
public bool hasAvailableSlots { get; set; }
[Required]
public bool hasInterviewsScheduled { get; set; }
// navigation properties
public ICollection<ScheduledSchool> scheduledSchool { get; set; }
public ICollection<Interview> interviews { get; set; }
public ICollection<PartialDayAvailableBlock> partialDayAvailableBlocks { get; set; }
public Visit visit { get; set; }
public ICollection<Event> events { get; set; }
}
and
public class Visit
{
[Key]
public long flaggedDateScheduledDayID { get; set; }
[Required]
public bool isFullDay { get; set; }
// navigation property
public FlaggedDate flaggedDate { get; set; }
}
The relationship between these two is 1 : 0|1 -- i.e., FlaggedDate will exist but it may or may not have a corresponding single Visit object.
EF thinks, based on this model, that FlaggedDate should have an extra field, visit_flaggedDateScheduledDayID, which is nullable. I finally realized why: it thinks the Visit field, flaggedDateScheduledDayID, is an identity column. It's not supposed to be an identity column; it's supposed to be a foreign key that connects to FlaggedDate.
I think it does this by convention: I remember reading something to the effect that in CTP4, any field that is a single key and is int or long is assumed to be an identity column.
Is there any way I can tell EF that this is NOT an identity column? I tried fiddling with the Fluent API, but it's a mystery to me, and there are no data annotations that you can use for this.
Or, alternatively, is there any way I can fiddle with the navigation properties to get this to come out right?
If you're using mapping files with fluent API
this.Property(t => t.Id)
.HasColumnName("Site_ID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
I would imagine it should also work declaratively
[HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)]
although I didn't try that.
I discovered I can override the identity behavior with this code:
modelBuilder.Entity<Visit>().Property(v => v.flaggedDateScheduledDayID).StoreGeneratedPattern = System.Data.Metadata.Edm.StoreGeneratedPattern.None;
However, it is still not making it a foreign key. I guess that's a different question, though. It seems setting the StoreGeneratedPattern to None is the way to override the default behavior.

Resources