Click here to Skip to main content
15,881,709 members
Articles / Web Development / ASP.NET / ASP.NET Core
Tip/Trick

Entity Framework Core Triggers in Action (Unofficial Package)

Rate me:
Please Sign up or sign in to vote.
4.50/5 (4 votes)
18 Apr 2018CPOL3 min read 16.7K   6  
This article shows you how to work with database triggers in entity framework core using a third party library.

Recently, I have had this problem of updating a field of an entity that is dependent on another field of another entity. Confused already? Let me give you an example: so, I’ve these Inventory and Item entities where there exists a many-to-many relation between them. So, of course there should also be a relation table between them, i.e., InventoryItem. The problem I was having actually is I have had this one field called CurrentQuantity on the Item entity and other called IssuedQuantity on the relation table (InventoryItem). Thus, as you might have already guessed that I’ve had to somehow update the CurrentQuantity field of the Item entity based on the IssuedQuantity field of the InventoryItem entity.

And now for the solution: my path crossed with this beautiful package named EntityFrameworkCore.Triggers created by Nick Strupat. Using this package, you can enable support for Triggers in your project that is using Entity Framework Core or Entity Framework 6+. For the demo, I have used Entity Framework Core with ASP.NET Core.

Below is how I used the package to easily solve my problem.

Install the package via Nuget with the following command:

Install-Package EntityFrameworkCore.Triggers

Here are the entities used for the demo:

JavaScript
public class Inventory
{
    public int Id { get; set; }
    public string Moniker { get; set; }

    public List<InventoryItem> InventoryItems { get; set; }
}

public class Item
{
    public int Id { get; set; }
    public decimal CostPerUnit { get; set; }
    public int CurrentQuantity { get; set; } = 0;
    public List<InventoryItem> InventoryItems { get; set; }
}

public class InventoryItem
{
    public int Id { get; set; }
    public int IssuedQuantity { get; set; }

    public int ItemId { get; set; }
    public Item Item { get; set; }

    public int InventoryId { get; set; }
    public Inventory Inventory { get; set; }
}

I have used those entities to declare DbSet<T> in the ApplicationDbContext file:

JavaScript
public class ApplicationDbContext : DbContext
{
    public DbSet<Item> Items { get; set; }
    public DbSet<Inventory> Inventories { get; set; }
    public DbSet<InventoryItem> InventoryItemRelation { get; set; }
}

I used MVC scaffolding for generating controllers and their respective views for these entities. I won’t show the code for them here. Please download the repository to have a better look at those.

The next thing to do is to create an Insert and a Delete trigger for updating the value of CurrentQuantity field respective to the value of IssuedQuantity field.

Here go the triggers in the overridden SaveChangesAsync method of ApplicationDbContext:

JavaScript
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, 
         CancellationToken cancellationToken = default(CancellationToken))
{
    Triggers<InventoryItem>.Inserting += 
             entry => entry.Entity.Item.CurrentQuantity += entry.Entity.IssuedQuantity;
    Triggers<InventoryItem>.Deleting += entry => 
             entry.Entity.Item.CurrentQuantity -= entry.Entity.IssuedQuantity;

    return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, 
             acceptAllChangesOnSuccess: true, cancellationToken: cancellationToken);
}
Note:
The creator of this libary just notified me that if I register the triggers in the SaveChanges method, each of the handlers will be called +1 times. Hence, what should we really do is register the triggers in their own entity class. And to make sure they run once we should put them in a static constructor. So the InventoryItem entity class should be as following.
public class InventoryItem
{
    public int Id { get; set; }
    public int IssuedQuantity { get; set; }

    public int ItemId { get; set; }
    public Item Item { get; set; }

    public int InventoryId { get; set; }
    public Inventory Inventory { get; set; }
    
    static InventoryItem()
    {
        Triggers<InventoryItem>.Inserting += 
             entry => entry.Entity.Item.CurrentQuantity += entry.Entity.IssuedQuantity;
        Triggers<InventoryItem>.Deleting += entry => 
             entry.Entity.Item.CurrentQuantity -= entry.Entity.IssuedQuantity;
    }
}

Everything will work smoothly now. If you update the value of the IssuedQuantity field while managing relation between Item and Inventory, it will also update the CurrentQuantity field. Here, I’m only listening for the Insert and Delete trigger for the sake of simplicity but you have the Update trigger also for use if you want.

Don’t forget calling the SaveChangesWithTriggersAsync in your SaveChangesAsync method. Otherwise, the triggers won’t get registered.

By the way, you have to modify the Create and Delete action generated by the default scaffolding engine for InventoryItem entity like the following:

JavaScript
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 
    Create([Bind("Id,IssuedQuantity,ItemId,InventoryId")] InventoryItem inventoryItem)
{
    if (ModelState.IsValid)
    {
        var item = _context.Items.Find(inventoryItem.ItemId);
        var inventory = _context.Inventories.Find(inventoryItem.InventoryId);

        inventoryItem.Item = item;
        inventoryItem.Inventory = inventory;

        _context.Add(inventoryItem);
        await _context.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    ViewData["InventoryId"] = 
       new SelectList(_context.Inventories, "Id", "Id", inventoryItem.InventoryId);
    ViewData["ItemId"] = new SelectList(_context.Items, "Id", "Id", inventoryItem.ItemId);
    return View(inventoryItem);
}

// POST: InventoryItems/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    var inventoryItem = await _context.InventoryItemRelation.SingleOrDefaultAsync(m => m.Id == id);

    var item = _context.Items.Find(inventoryItem.ItemId);
    var inventory = _context.Inventories.Find(inventoryItem.InventoryId);

    inventoryItem.Item = item;
    inventoryItem.Inventory = inventory;

    _context.InventoryItemRelation.Remove(inventoryItem);
    await _context.SaveChangesAsync();
    return RedirectToAction("Index");
}

Nothing fancy here, just got the appropriate Item and Inventory references using the InventoryId and ItemId fields available inside the inventoryItem parameter. Then, attach the references back to the inventoryItem parameter again so that we don’t get any null reference errors.

I’ve covered the very basics here. To know more about the package and other available configurations, refer to this github repository. It is open source, yay!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Geek Hour
Bangladesh Bangladesh
Tech Enthusiast | Contributing Author on Microsoft Docs | Github Country Leader (C# and Typescript)

A .NET and JavaScript enthusiast. Likes to work with different technologies on different platforms. Loves the logic and structure of coding and always strives to write more elegant and efficient code. Passionate about design patterns and applying Software Engineering best practices.

I'm a young coder who did exceedingly well in my education and was offered an internship by Microsoft. I've tried a range of things and realized that what I need is a super creative challenge. I'm hungry for a real role in a challenging startup, something I can stick with for years

Comments and Discussions

 
-- There are no messages in this forum --