I want to delete a table row that is split on two entities.
If I try to delete the main entity, I get an error if before I don\'t load the related other entity using
Replace your override method with this code and check
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>() // one-to-one
.ToTable("Products")
.HasKey(e => e.Id)
.HasRequired(e => e.Info)
.WithRequiredDependent(e => e.Product);
modelBuilder.Entity<ProductInfo>() // map to the same table Products
.ToTable("Products")
.HasKey(e => e.Id);
//add this code
modelBuilder.Entity<Product>()
.HasRequired(p => p.Photo) // "Photo" defined in Product class for ProductPhoto class's object name
.WithRequiredPrincipal(c => c.Product);// "Product" defined in ProductPhoto class for Product's class object name
base.OnModelCreating(modelBuilder);
}
Possibly load the product like so:
var product = context.Products.Include(x => x.Photo).First();
Saves a line but will still load the photo from the db.
Hi the path to solve as Gert Arnold suggest is to have in memory stubs one for the entity and another for related sub entities I had pasted a new code with that works (see comments)
I posted the solution with the idea suggested by Gert Arnold, perhaps the code could be optimized, I tried to make it as much generic that I can.
If your entity contains any concurrency tokens, these properties are also used to construct the DELETE statement. You can still use the stub entity approach, but you will need to set values for the concurrency token properties as well.
Quoted from: “Programming Entity Framework: DbContext by Julia Lerman and Rowan Miller (O’Reilly). Copyright 2012 Julia Lerman and Rowan Miller, 978-1-449-31296-1.”
using System;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
namespace Split
{
class Program
{
static void Main()
{
Database.SetInitializer(new DropCreateDatabaseAlways<DataContext>());
const int id = 1;
const string split = "Info"; // contract: if the entity being delete has an Info property then the row has been splitted
using (var context = new DataContext()) // Add
{
var product = new Product
{
Name = "my Article 1",
Info = new ProductInfo { PhotoUrl = "http://myphoto.jpg" } // when adding an entity the subEntity MUST BE included on the graph
};
context.Products.Add(product);
context.SaveChanges();
}
using (var context = new DataContext())
{
var product = context.Products.Find(id);
context.Entry(product).Reference(e => e.Info).Load(); // when adding an entity the subEntity COULD BE OR NOT included on the graph, no need to include it if we are not going to modify it
product.Name = "MY ARTICULE 1";
product.Info.PhotoUrl = "HTTP://MYPHOTO.JPG";
context.Entry(product).State = EntityState.Modified;
context.SaveChanges();
}
using (var context = new DataContext())
{
PropertyInfo propertyInfo;
context.Products.Find(id); // uncoment bring it to memory and test with entity in memory
var entity = context.Products.Local.FirstOrDefault(e => e.Id == id);
context.Entry(entity).Reference(e => e.Info).Load();
if (entity != null) // there is a entity already yet in memory
{
propertyInfo = entity.GetType().GetProperty(split); // contract
if (propertyInfo != null)
{
var subEntity = propertyInfo.GetValue(entity); // get subEntity from entity Info property
context.Entry(subEntity).State = EntityState.Detached; // remove sub entity from ChangeTracker API
propertyInfo.SetValue(entity, null); // remove subEntity and relationship
}
context.Entry(entity).State = EntityState.Detached; // remove entity from ChangeTracker API
}
entity = new Product { Id = id }; // new entity stub
propertyInfo = entity.GetType().GetProperty(split); // contract:
if (propertyInfo != null)
{
propertyInfo.SetValue(entity, null); // remove subEntity and and relationship
var subEntity = Activator.CreateInstance(propertyInfo.PropertyType); // create a new subEntity stub
subEntity.GetType().GetProperty("Id").SetValue(subEntity, id); // set the foreinkey relation
context.Entry(subEntity).State = EntityState.Deleted; // mark as deleted on context
}
context.Entry(entity).State = EntityState.Deleted; // delete the entity
context.SaveChanges();
}
}
}
class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ProductInfo Info { get; set; }
}
class ProductInfo
{
public virtual int Id { get; set; }
public virtual string PhotoUrl { get; set; }
public virtual Product Product { get; set; }
}
class DataContext : DbContext
{
public DataContext()
: base("name=DefaultConnection")
{
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public DbSet<Product> Products { get; set; }
public DbSet<ProductInfo> ProductInfos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>() // one-to-one
.ToTable("Products")
.HasKey(e => e.Id)
.HasRequired(e => e.Info)
.WithRequiredDependent(e => e.Product);
modelBuilder.Entity<ProductInfo>() // map to the same table Products
.ToTable("Products")
.HasKey(e => e.Id);
base.OnModelCreating(modelBuilder);
}
}
}
Try to add Cascade Delete rule to the model. You MUST have a corresponding DELETE rule in the database to avoid loading of the dependents into memory as noted here.
I know this isn't the "correct" answer, but with all the hassle of getting table-splitting working and getting errors on deleting, I just went with a good old SQL command:
DbContext.Database.ExecuteSqlCommand($"DELETE FROM Products WHERE Id = {id}");
Always an option when you're spending hours trying to get a small feature working!
The best way to do this is by using a stub entity: an entity object that's only got an Id value:
var product = context.Products.First();
var photo = new ProductPhoto { ProductId = product.ProductId }; // Stub
context.Entry(photo).State = System.Data.Entity.EntityState.Deleted;
context.Products.Remove(product);
context.SaveChanges();
If you know a Product
's Id you can even delete both the Product
and its ProductPhoto
by only creating two stubs.