NickStrupat / EntityFramework.Triggers

Adds events for entity inserting, inserted, updating, updated, deleting, and deleted
MIT License
373 stars 46 forks source link

Can't save changes in a trigger #19

Closed Nc32 closed 6 years ago

Nc32 commented 6 years ago

I have 2 DbSet, one is Product another is ProductRecord. And I want to record every changes of Product into ProductRecord. here is my code.

  Triggers<Product>.Inserted += entry =>
  {
      ProductRecord.AddRecord(entry.Entity, ProductRecordOperationType.Insert);
  }
  Triggers<Product>.Updated += entry =>
  {
      ProductRecord.AddRecord(entry.Entity, ProductRecordOperationType.Update);
  }

  public static void AddRecord(Product P, ProductRecordOperationType opt)
  {
      using(var db = new ApplicationDbContext())
      {
          var rcd = ProductRecord.FromProduct(P);
          rcd.Operation = opt;
          rcd.Id = Guid.NewGuid().ToString();
          db.PushProductRecord.Add(rcd);

          db.SaveChanges(); // ERROR HAPPENS HERE
      }
  }

Error Message says that I'm tring to add an entity with Id=null

And I also tried add an Inserting Trigger to ProductRecord to ensure every record has ID, still dosent work.

NickStrupat commented 6 years ago

Could you post the rest of the relevant code? I can't tell for sure what's happening here without seeing your DbContext class.

One thing that stands out to me is that you're making another instance of ApplicationDbContext. This library is designed to work well in most scenarios by using the context instance which has triggered the event. You can access the context instance at entry.Context.

Nc32 commented 6 years ago

I change my code to

Triggers<Product>.Inserted += entry =>
{
    var db = entry.Context;

    var recd = new ProductRecord();
    recd.Id = Guid.NewGuid().ToString();
    recd.Date = DateTime.Now;
    recd.Operation = ProductRecordOperationType.Insert;

    db.Entry(recd).State = System.Data.Entity.EntityState.Added;
    db.SaveChanges();
};

But the problem is the same.

Here is my DbContext, which is also an IdentityDbContext from WebAPI template. It works fine until add Triggers.

Now there are two things changed. One is this issue,the other is EF can't generate Id(I have to manually generate Product.Id and PushProductRecord.Id, Product can be saved to Database) Are they relate to a same problem?

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
    public ApplicationDbContext()
        : base("name=ApplicationContext")
    {
        this.Configuration.AutoDetectChangesEnabled = false;
    }

    public virtual DbSet<Product> Product { get; set; }
    public virtual DbSet<ProductRecord> PushProductRecord { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>().Map(m =>
        {
            m.MapInheritedProperties();
            m.ToTable("Product");
        });

        base.OnModelCreating(modelBuilder);
    }

    #region Override EF 6 SaveChange to enable triggers
    public override Int32 SaveChanges()
    {
        return this.SaveChangesWithTriggers(base.SaveChanges);
    }
    public override Task<Int32> SaveChangesAsync(CancellationToken cancellationToken)
    {
        return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, cancellationToken);
    }
    #endregion

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}
Nc32 commented 6 years ago

Finally figure out the problem is using MapInheritedProperties. The problem is "After install EntityFramework.Triggers or one of its dependencies, use MapInheritedProperties will cause error"

you can try this

public class test 
    {
        public int id { get; set; }
    }

    public class context : DbContext
    {
        public context() : base("DefaultConnection")
        { }
        public virtual DbSet<test> tests { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<test>().Map(m =>
            {
                m.MapInheritedProperties();
            });

            base.OnModelCreating(modelBuilder);
        }

    }

It works fine with only EntityFramework. But after install EntityTriggers(or one of its dependencies, i didnt test), errors happen. Since all these dependencies are maintance by you,should I leave this issue as a bug report or open a new one in somewhere else?

NickStrupat commented 6 years ago

Let's keep the discussion in this issue until we reproduce the bug. Thank you for your effort. Could you post your whole test program? The one which reproduces the error?

Nc32 commented 6 years ago
public class test
    {
        [Key]
        // [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int id { get; set; }
        public string name { get; set; }
        static test()
        {
            Triggers<test>.Inserted += entry =>
            {
                var db = entry.Context;
                var entity = entry.Entity;
                var rec = new testRecord()
                {
                    id = 1,
                    name = entity.name,
                    opt = 1
                };

                db.Entry(rec).State = EntityState.Added;
                db.SaveChanges();
            };
        }
    }
    public class testRecord : test
    {
        public int opt { get; set; }
    }
    public class context : DbContext
    {
        public virtual DbSet<test> tests { get; set; }
        public virtual DbSet<testRecord> testRecords { get; set; }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<testRecord>().Map(m =>
            {
                m.MapInheritedProperties()
                .ToTable("testRecord");
            });

            base.OnModelCreating(modelBuilder);
        }

        #region Override EF 6 SaveChange to enable triggers
        public override Int32 SaveChanges()
        {
            return this.SaveChangesWithTriggers(base.SaveChanges);
        }
        public override Task<Int32> SaveChangesAsync(CancellationToken cancellationToken)
        {
            return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, cancellationToken);
        }
        #endregion
    }
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new context())
            {
                var t = new test()
                {
                    // id = 0,
                    name = "test Entity"
                };
                db.tests.Add(t);
                db.SaveChanges();
            }
            Console.WriteLine("done");
        }
    }

To reproduce the same bug,follow these steps 1.create a new console application, paste the codes above. 2.nuget install entity.triggers 3.Enable-Migrations, Update-database, create a table without DatabaseGeneratedAttribute. 4.uncomment DatabaseGeneratedAttribute, Update-databse. 5.Run console.

I did'n try to do this with only EF, I think CodeFirst is able to update-database correctly.

NickStrupat commented 6 years ago

So i get the error, but if i comment-out the trigger code, i still get the error. Are you sure the error is caused by this library?

SergeyIlyin commented 6 years ago

Maby this helps: With post triggers I use next constraction without execute SaveChange in triggers

while (dbContext.ChangeTracker.HasChanges())
{
   dbContext.SaveChanges();
}