I have a little complex model, about EF cascade delete - entity-framework

Code first,and model look like this Figure:
public class Post
{
public int Id { get; set; }
public string title { get; set; }
public int fid { get; set; }
public int uid { get; set; }
public virtual lnk lnk { get; set; }
public virtual user user { get; set; }
public virtual ICollection<img> imgs { get; set; }
}
public class lnk
{
public int Id { get; set; }
public string lnkman { get; set; }
}
public class model1:Post
{
public string content { get; set; }
}
public class user
{
public int uid { get;set; }
public string Name {get;set;}
public virtual ICollection<Post> posts { get; set; }
}
public class img
{
public int id { get; set; }
public string imgUrl { get; set; }
public int pid { get; set; }
public virtual Post post { get; set; }
}
public class PostContext : DbContext
{
public DbSet<Post> posts { get; set; }
public DbSet<user> users { get; set; }
public DbSet<img> imgs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>().HasKey(i=>i.Id);
modelBuilder.Entity<lnk>().HasKey(i => i.Id);
modelBuilder.Entity<Post>().HasRequired(i => i.lnk).WithRequiredPrincipal();
modelBuilder.Entity<Post>().ToTable("Post");
modelBuilder.Entity<lnk>().ToTable("Post");
modelBuilder.Entity<model1>().ToTable("model1");
modelBuilder.Entity<user>().HasKey(i => i.uid).ToTable("user");
modelBuilder.Entity<user>().HasMany(i => i.posts)
.WithRequired(i => i.user)
.HasForeignKey(i =>i.uid).WillCascadeOnDelete(false);
modelBuilder.Entity<img>().HasKey(i => i.id).ToTable("img");
modelBuilder.Entity<Post>().HasMany(i => i.imgs)
.WithRequired(i => i.post)
.HasForeignKey(i => i.pid)
.WillCascadeOnDelete(false);
}
}
the add and list method work well. but delele like this :
using (var db = new PostContext())
{
//model1 ToDel = new model1{ Id=id };
//b.Entry(ToDel).State = System.Data.EntityState.Deleted;
var ToDel = db.users.Single(i => i.uid == id);
db.users.Remove(ToDel);
db.SaveChanges();
return id + "DE: well done!" ;
}
System.Data.SqlClient.SqlException: DELETE statement with REFERENCE constraint "FK_dbo.Post_dbo.user_uid" conflict。The conflict occurred in database "jefunfl",table "dbo.Post", column 'uid'
I want delete users with user's posts, and posts with post's imgs.
How can I do this? Can some one give any tips?
btw,my Chinese English may be funny. forgive me.
this is my first post,but I am not new guy. thanks

bypass all one day, i found it. and thank you for answer to me.
1:
i delete WillCascadeOnDelete() method,so let ef do it by Conventions.
2:
i found that ,when i delete the posts,i can't directly delete with this statement like ToDel = db.posts.Single(i => i.Id == id)
but this:
ToDel = db.posts.Include(i=>i.lnk).Single(i => i.Id == id)
post and lnk has a relatialship that is called table splitting. both mapping to the same post table. when delete posts, EF can auto delete post.imgs by conventions. what will happen when delete user ? and so i got it. my code blow:
var ToDel = db.users.Include(i => i.posts)
.Single(i => i.uid == id);
var PostToDel = db.posts.Include(i => i.lnk)
.Single(i => i.Id == ToDel.uid);
db.posts.Remove(PostToDel);
db.users.Remove(ToDel);
db.SaveChanges();
this will work well. even not perfectly. but it can do what i want without a exception.
at last,the biggest problem is post class and lnk class has a "table splitting".
when delete post we should explicitly include.
i dont know if you can understand me, my english is very ugly. i am foolish with courage.
thank u. i am happy

Related

Referenced object is not loaded from database

This the table structure I have:
#region Tables
public class WorkoutProfile
{
public WorkoutProfile()
{
WorkoutExercises = new List<WorkoutExercise>();
}
[Key]
public int ProfileId { get; set; }
public string Name { get; set; }
public int Sets { get; set; }
public int RestAfterSetInSeconds { get; set; }
public virtual User User { get; set; }
public virtual ICollection<WorkoutExercise> WorkoutExercises { get; set; }
}
public class WorkoutExercise
{
[Key]
public int WorkoutId { get; set; }
public virtual Exercise Exercise { get; set; }
public int Order { get; set; }
public int WorkoutTimeInSeconds { get; set; }
public int RestAfterInSeconds { get; set; }
}
public class Exercise
{
[Key]
public long ExerciseId { get; set; }
public string Title { get; set; }
public string Visualisation { get; set; }
public bool IsDefault { get; set; } // Is exersice should be included when user first registers
}
public class User
{
[Key]
public long UserId { get; set; }
public string Email { get; set; }
public DateTime Registered { get; set; }
}
#endregion Tables
In the repository class I run the following linq query:
return context
.WorkoutProfiles.Include(w => w.WorkoutExercises)
.Where(q => q.User.UserId == userId && q.ProfileId == profileId)
.FirstOrDefault();
and I receive the good and old "Object reference not set to an instance of an object". When examining the result, see that Exercises property in WorkoutExercises is null.
This is how the database is created using code first approach:
So, the question is: why Exercises not included in WorkoutExercises object? Do I need to include it somehow? I am using .NET Core 2
The simple answer would be no lazy loading in EFCore. Not Released yet but if you want to dabble with alpha code, its in the repository. Based on your classes there are no collections for exercises in WorkoutExcercise.
Then you need to ThenInclude(w => w.Exercises) following your Include clause since EFCore doesn't do lazy loading.
I found a solution following this post
Altered my code as following:
var top = context
.Set<WorkoutProfile>()
.Where(q => q.ProfileId == profileId && q.User.UserId == userId)
.Include(q => q.WorkoutExercises)
.SingleOrDefault();
context
.Entry(top)
.Collection(e => e.WorkoutExercises)
.Query()
.OfType<WorkoutExercise>()
.Include(e => e.Exercise)
.Load();
And it worked

How to create multiple Many-to-Many relationships using the same join table [EF7/Core]

Is it possible to create 2 M:M relationships using the same join table?
I have the following situation and am receiving the exception:
Unhandled Exception: System.InvalidOperationException: Cannot create a relationship between 'ApplicationUser.ExpertTags' and 'UserTag.User', because there already is a relationship between 'ApplicationUser.StudyTags' and 'UserTag.User'. Navigation properties can only participate in a single relationship
In Tag:
public class Tag {
public Tag() {
Users = new List<UserTag>();
}
public int TagId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<UserTag> Users { get; set; }
In ApplicationUser:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
StudyTags = new HashSet<UserTag>();
ExpertTags = new HashSet<UserTag>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public ICollection<UserTag> StudyTags { get; set; }
public ICollection<UserTag> ExpertTags { get; set; }
}
In UserTag (CLR join):
public class UserTag
{
public string UserId { get; set; }
public ApplicationUser User { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
In ApplicationDbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserTag>()
.HasKey(x => new { x.UserId, x.TagId });
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.StudyTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.User)
.WithMany(u => u.ExpertTags)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(ut => ut.Tag)
.WithMany(t => t.Users)
.HasForeignKey(ut => ut.TagId);
}
Do I need to create separate CLR classes? Something like UserStudyTag and UserExpertTag?
Thanks!
Step down to SQL DB. You want to have table UserTag with one UserId field. How EF should guess, which records in this table are related to StudyTags and which to ExpertTags collections?
You should duplicate something.
Either split UserTag to two tables (UserStudyTag and UserExpertTag), or make two UserId fields in UserTag, say ExpertUserId and StudyUserId. Both nullable, with only one having some value in each record.

Entity Framework circular dependency for last entity

Please consider the following entities
public class What {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Track> Tracks { get; set; }
public int? LastTrackId { get; set; }]
public Track LastTrack { get; set; }
}
public class Track {
public Track(string what, DateTime dt, TrackThatGeoposition pos) {
What = new What { Name = what, LastTrack = this };
}
public int Id { get; set; }
public int WhatId { get; set; }
public What What { get; set; }
}
I use the following to configure the entities:
builder.HasKey(x => x.Id);
builder.HasMany(x => x.Tracks).
WithOne(y => y.What).HasForeignKey(y => y.WhatId);
builder.Property(x => x.Name).HasMaxLength(100);
builder.HasOne(x => x.LastTrack).
WithMany().HasForeignKey(x => x.LastTrackId);
Has you can see there is a wanted circular reference:
What.LastTrack <-> Track.What
when I try to add a Track to the context (on SaveChanges in fact):
Track t = new Track("truc", Datetime.Now, pos);
ctx.Tracks.Add(t);
ctx.SaveChanges();
I get the following error:
Unable to save changes because a circular dependency was detected in the data to be saved: ''What' {'LastTrackId'} -> 'Track' {'Id'}, 'Track' {'WhatId'} -> 'What' {'Id'}'.
I would like to say... yes, I know but...
Is such a configuration doable with EF Core ?
This is what I like to call the favored child problem: a parent has multiple children, but one of them is extra special. This causes problems in real life... and in data processing.
In your class model, What (is that a sensible name, by the way?) has Tracks as children, but one of these, LastTrack is the special child to which What keeps a reference.
When both What and Tracks are created in one transaction, EF will try to use the generated What.Id to insert the new Tracks with WhatId. But before it can save What it needs the generated Id of the last Track. Since SQL databases can't insert records simultaneously, this circular reference can't be established in one isolated transaction.
You need one transaction to save What and its Tracks and a subsequent transaction to set What.LastTrackId.
To do this in one database transaction you can wrap the code in a TransactionScope:
using(var ts = new TransactionScope())
{
// do the stuff
ts.Complete();
}
If an exception occurs, ts.Complete(); won't happen and a rollback will occur when the TransactionScope is disposed.
I encountered the same problem, but i solved it differently.
In my case, it was about a list of status and a reference to the last status. So with the following case :
public class What {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Status> StatusList { get; set; }
public int? LastStatusId { get; set; }
public Status LastStatus { get; set; }
public void AddStatus(Status s)
{
StatusList.Add(s);
LastStatus = s;
}
}
public class Status{
public int Id { get; set; }
public int WhatId { get; set; }
public What What { get; set; }
}
In my program, i changed my code to use StatusList as an history that doesn't include the lastStatus, so :
public class What {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Status> StatusHistory { get; set; }
public int? LastStatusId { get; set; }
public Status LastStatus { get; set; }
public void AddStatus(Status s)
{
if(LastStatus) StatusList.Add(LastStatus);
LastStatus = s;
}
public List<Status> GetStatusList(Status s) // If needed, a method, not a property because i got an error with lazyLoading
{
return new List<Status>(StatusHistory) { LastStatus}; // List of all status (history + last)
}
}
public class Status{
public int Id { get; set; }
public int? WhatId { get; set; }
public What What { get; set; }
}
and don't forget to put in your context IsRequired(false) on the foreignKey :
builder.HasMany(x => x.Status).
WithOne(y => y.What).HasForeignKey(y => y.WhatId).IsRequired(false);
Like this, no more circular reference.

Entity Framework query returns one record, but its supposed to return more

As title say, i got problem with a query in Asp.net mvc 3 EF.
I got 2 tables with 1 to many relationship.
table1 Users int user_ID string username
table2 Friends int friendshipID int user_ID int friend_ID
The controller:
// // GET: /User/Details/5
public ViewResult Details(int id)
{
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
//Also for each friend get the User:
foreach (var friend in user.Friends.ToList())
{
friend.User = db.Users.Find(friend.friend_ID);
}
return View(user);
}
The view "details":
#model Social2.Models.User
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<div class="display-field">
#foreach (var friend in #Model.Friends)
{
#friend.User.username;
}
</div>
Context:
public partial class ASPNETDBEntities : DbContext
{
public ASPNETDBEntities()
: base("name=ASPNETDBEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<aspnet_Users> aspnet_Users { get; set; }
public DbSet<Friend> Friends { get; set; }
public DbSet<User> Users { get; set; }
}
user model:
public partial class User
{
public User()
{
this.Friends = new HashSet<Friend>();
}
[Key]
public int user_ID { get; set; }
public System.Guid user_UniqueID { get; set; }
public string username { get; set; }
public virtual aspnet_Users aspnet_Users { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
friend model
public partial class Friend
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual User User { get; set; }
}
The problem is, when i go to ~/user/details/1, the view show only one(the last one) friend.For every user it shows their last friend. How to show them all ?
Your database must have two relationships defined like so:
If you create an Entity Model from this schema you also get two one-to-many relationships. And when you apply the DBContext T4 template to this model you should get POCO classes like so:
public partial class Users
{
public Users()
{
this.Friends = new HashSet<Friends>();
this.Friends1 = new HashSet<Friends>();
}
public int user_ID { get; set; }
public string username { get; set; }
public virtual ICollection<Friends> Friends { get; set; }
public virtual ICollection<Friends> Friends1 { get; set; }
}
public partial class Friends
{
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
public virtual Users Users { get; set; }
public virtual Users Users1 { get; set; }
}
Users.Friends and Friends.Users form a pair for the first relationship and Users.Friends1 and Friends.Users1 are a pair for the second relationship. (You can rename the navigation properties in the model designer to make the names more meaningful.) Your query would look like this then (you can include a "second level" and don't need the loop as you did in your example):
public ViewResult Details(int id)
{
// important to use User1 in Include, not User
User user = db.Users.Include("Friends.User1")
.FirstOrDefault(u => u.user_ID == id);
return View(user);
}
With DbContext you can also use the strongly typed version of Include:
Include(u => u.Friends.Select(f => f.User1))
I think the problem is mapping of the class Friend.
try change to:
public partial class Friend
{
[Key]
public int friendship_ID { get; set; }
public int user_fr_ID { get; set; }
public int friend_ID { get; set; }
[ForeignKey("friend_ID")]
public virtual User User { get; set; }
}
I think the problem is here
User user = db.Users.Include("Friends").FirstOrDefault(u => u.user_ID == id);
FirstOrDefault gives only one record.

Filtering related entities with Entity framework

I'm looking for the best way to to load and filter related child entities. I have something that works, but I'm unsure if it's the best or even the right way to achieve what I want. Working code example below. Pros and cons would be great! Thanks!
public Site Find(int siteID)
{
// Can't use include here, not possible to filter related (child) entities
// return _context.Sites.Where(x => x.ID == siteID)
// .Include("SiteLoggers")
// .Where(x => x.Deleted == false)
// .FirstOrDefault();
var site = _context.Sites.Where(x => x.ID == siteID).FirstOrDefault();
if(site != null)
{
site.SiteLoggers = site.SiteLoggers.Where(x => x.SiteID == siteID &&
x.Deleted == false)
.ToList();
}
return site;
}
EDIT:
Added POCOS
public class Site
{
public int ID { get; set; }
public int LocationID { get; set; }
public bool Active { get; set; }
public bool Deleted { get; set; }
public string Name { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<SiteLogger> SiteLoggers { get; set; }
public virtual ICollection<LinkDcSite> DcSiteLinks { get; set; }
}
public class SiteLogger
{
public int ID { get; set; }
public int UID { get; set; }
public int SiteID { get; set; }
public int LocationID { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public bool Deleted { get; set; }
public virtual Site Site { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<SiteLoggerSensor> SiteLoggerSensors { get; set; }
public virtual ICollection<LinkLoggerSiteLogger> LinkLoggerSiteLogger { get; set; }
}
Your method is fine I think you have just extra checking for x.SiteID == siteID:
....
site.SiteLoggers = site.SiteLoggers.Where(x => !x.Deleted).ToList();
....
Also if you searching by ID means you are sure there is no two element with same ID, so it's better to use SingleOrDefault instead of FirstOrDefault, to throw an exception in the case there are more than one item with one ID.
var site = _context.Sites.Where(x => x.ID == siteID).SingleOrDefault();
You can do that with a simple query:
var site = _context.SiteLoggers.Where(sl => sl.SiteId = siteId && !sl.Deleted).ToList();
If there's a relation between SiteLoggers and Sites, you don't need to chek that the site exists.