2

I'm currently developing a system that generate and manage thumbnails for images in a site. Basically to improve loading time, creating smaller images before so it doesn't need any preprocessing for each request or load the original big image.

The creation is currently working fine but I'm having trouble to remove the images (original and generated). When I remove direct from the database it works just fine with a simple delete, but when I'm doing it by code it doesn't work.

My Image table is split in two classes


public class Image(){
  public int Id{ get; set;}
  public int? MainImageId{ get; set;}; 
  public string Description{ get; set;}
  public virtual Image MainImage{ get; set;}
  public virtual ImageContent Content{ get; set;}
  public virtual ICollection<Image> Variations{ get; set;}
}
public class ImageContent(){
  public int ImageId { get; set; }
  public byte[] Raw { get; set; }
  public virtual Image Image { get; set; }
}

They are build as

 modelBuilder.Entity<Image>()
  .HasRequired(x => x.Content)
  .WithRequiredDependent(x => x.Image);

modelBuilder.Entity<Image>()
  .ToTable("Images");            

modelBuilder.Entity<ImageContent>()
  .HasKey(x => x.ImageId)
  .ToTable("Images");

modelBuilder.Entity<Image>()
  .HasMany(i => i.Variations)
  .WithOptional(i => i.MainImage)
  .HasForeignKey(v => v.MainImageId);

And my remove function is

public void Delete(int id){
  var image = context.Set<Image>().Include("Variations").FirstOrDefault(i => i.Id == id);

  if (image != null) {

    foreach(Image variation in image.Variations) {
      context.Set<Image>().Remove(variation);
    }

    context.Set<Image>().Remove(image);

    context.SaveChanges();
  }

}

But when I run the Delete function I get an DbUpdateException with the message Invalid data encountered. A required relationship is missing. Examine StateEntries to determine the source of the constraint violation.

The error is thrown when context.SaveChanges() runs.

I've already tried to include all the dependents like Content and Variations and can't find any other reason why this error is being thrown.

Previously the class didn't have a self reference and everything worked just fine. Does anybody know what I can do to fix this?

  • Try https://stackoverflow.com/questions/8170975/entity-framework-split-table-delete or https://stackoverflow.com/questions/28257790/error-deleting-a-table-row-splitted-on-multiple-entities post. – Sandeep Ingale May 24 '19 at 19:04

1 Answers1

1
 modelBuilder.Entity<Image>()
  .ToTable("Images");            

 modelBuilder.Entity<ImageContent>()
  .HasKey(x => x.ImageId)
  .ToTable("Images");

What you do here is called table splitting, that is, there's one table in the database, Images, that is "split" into two entities, Image and ImageContent. When you delete an entity pair that comprises a split table, one way to do it is to delete the principal entity while the dependent entity is included. The principal entity is defined in the mapping...

 modelBuilder.Entity<Image>()
  .HasRequired(x => x.Content)
  .WithRequiredDependent(x => x.Image);

Which says: Image is dependent, so ImageContent is the principal.

And there's the problem.

The line...

 context.Set<Image>().Remove(image);

...deletes the dependent entity.

Now you could easily fix this by including Content and also marking it for deletion (ignoring Variations for now):

using System.Data.Entity;
...

var image = context.Set<Image>().Include(i => i.Content)
                   .Single(i => i.Id == id);
context.Entry(image).State = EntityState.Deleted;
context.Entry(image.Content).State = EntityState.Deleted;

But I think the model makes more sense when Image is the principal entity:

modelBuilder.Entity<Image>()
  .HasRequired(x => x.Content)
  .WithRequiredPrincipal(x => x.Image);

Now an image can be deleted without explicitly deleting Content:

var image = context.Set<Image>().Include(i => i.Content)
                   .Single(i => i.Id == id);
context.Entry(image).State = EntityState.Deleted;
// or context.Set<Image>().Remove(image);

This also makes it easier to delete the variations:

var image = context.Set<Image>()
                   .Include(i => i.Content)
                   .Include(i => i.Variations.Select(v => v.Content))
                   .Single(i => i.Id == id);

foreach (var variation in image.Variations)
{
    context.Set<Image>().Remove(variation);
}
context.Set<Image>().Remove(image);

As a bonus, this also enables you to make the deletions less expensive by only fetching Images from the database without their (large) contents and deleting the contents by stub entities. I'll show you how to do it just for one image:

var img1 = context.Set<Image>(). Single(i => i.Id == 1);
img1.Content = new ImageContent { ImageId = img1.Id }; // Stub entity
context.Entry(img1.Content).State = Entity.EntityState.Unchanged; // Attach to context
context.Set<Image>().Remove(img1);
context.SaveChanges();
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291