简体   繁体   English

删除在多个实体上拆分的表行时出错

[英]Error deleting a table row splitted on multiple entities

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 context.Entry(...).Reference .. 如果我尝试删除主实体,如果在我不使用context.Entry(...).Reference加载相关的其他实体之前,我会收到错误。

Is a bit silly to retrieve the related entities before, when I am going to delete the full row? 在我要删除整行之前检索相关实体有点傻吗?

I got the following error if I keep commented the context.Entry(...) line 如果我继续注释context.Entry(...)行,我会收到以下错误

Invalid data encountered. 遇到无效数据。 A required relationship is missing. 缺少必需的关系。 Examine State Entries to determine the source of the constraint violation. 检查状态条目以确定约束违规的来源。

I add the code below. 我在下面添加代码。 Could please someone help me in deleting split entities without having to "load" the related ones before? 可以请某人帮我删除拆分实体而不必“加载”之前的相关实体吗?

 using System.Data.Entity;
 using System.Linq;

 namespace Split
 {
   class Program
   {
     static void Main(string[] args)
     {
        using (var context = new DataContext())
        {
            var product = new Product()
            {
                Name = "my Article",
                Photo = new ProductPhoto() { PhotoUrl = "http://myfoto.jpg" }
            };

            context.Products.Add(product);
            context.SaveChanges();
        }

        using (var context = new DataContext())
        {
            var product = context.Products.First();
            //context.Entry(product).Reference(e => e.Photo).Load();
            context.Products.Remove(product);
            context.SaveChanges();
        }
     }
   }

  class Product
  {
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual ProductPhoto Photo { get; set; }
  }

  class ProductPhoto
  {
    public virtual int ProductId { 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<ProductPhoto> ProductPhotos { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .ToTable("Products")
            .HasKey(e => e.Id)
            .HasRequired(e => e.Photo)
            .WithRequiredPrincipal(e => e.Product);

        modelBuilder.Entity<ProductPhoto>()
            .ToTable("Products")
            .HasKey(e => e.ProductId);

        base.OnModelCreating(modelBuilder);
     }
   }
}

The best way to do this is by using a stub entity : an entity object that's only got an Id value: 执行此操作的最佳方法是使用存根实体 :只获得Id值的实体对象:

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. 如果您知道Product的ID,您甚至可以通过仅创建两个存根来删除Product及其ProductPhoto

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. 保存一行但仍会从数据库加载照片。

Try to add Cascade Delete rule to the model. 尝试将Cascade Delete规则添加到模型中。 You MUST have a corresponding DELETE rule in the database to avoid loading of the dependents into memory as noted here. 您必须在数据库中有相应的DELETE规则,以避免将依赖项加载到内存中,如此处所述。

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) 嗨,要解决的路径是Gert Arnold所建议的是在内存存根中有一个用于实体而另一个用于相关的子实体我粘贴了一个新的代码(见注释)

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. 我发布了Gert Arnold提出的想法的解决方案,也许代码可以优化,我试图尽可能多地使用它。

If your entity contains any concurrency tokens, these properties are also used to construct the DELETE statement. 如果您的实体包含任何并发令牌,则这些属性也用于构造DELETE语句。 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). 引用自:“编程实体框架:由Julia Lerman和Rowan Miller(O'Reilly)编写的DbContext。 Copyright 2012 Julia Lerman and Rowan Miller, 978-1-449-31296-1.” 版权所有2012 Julia Lerman和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);
        }

    }
}

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);
}

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: 我知道这不是“正确的”答案,但是由于所有麻烦的分区工作和删除错误,我只是使用了一个很好的旧SQL命令:

DbContext.Database.ExecuteSqlCommand($"DELETE FROM Products WHERE Id = {id}");

Always an option when you're spending hours trying to get a small feature working! 当你花费数小时试图让一个小功能工作时,总是一个选项!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM