Share via


Entity Framework Core: HasValueGenerator using C#

Introduction

 The Entity Framework Core Fluent API ValueGeneratedOnAdd provides a method to indicate that the value for the selected property will be generated whenever a new entity is added to the database or an existing one is modified. This means the property should not be included in INSERT statements when SQL is generated by Entity Framework Core. This is implemented using PropertyBuilder<TProperty>.HasValueGenerator. In the following an example is shown how to create an auto incrementing alphanumeric account number when a new record is added to a customer table.

Narrative

Each time a new customer is added to the customer table, the last used account number is read from a table and incremented by 1. Note there is no need for currency issues as the fictitious application has one person adding customers. The last account number is stored in a table with one record which is read followed by incrementing in code then saved back to the same record.

Implementation

Create a class method to increment the account number found here. Create a class to which Entity Framework Core uses to generate, in this case the incremented account number located here which implements ValueGenerator class. Although the class used to return the next account number as presented is capable of 40,000 accounts there needs to be assertion to handle then no more values can be generated which is done by checking for null and then reset to the first account number. This means in an application in the wild this needs to have code to check for this and handle as business requirements are setup.

To inform Entity Framework Core, in the dbContext in OnModelCreating setup the property of the entity as follows which indicates when a new record is added use the AccountGenerator class.

modelBuilder.Entity<Customer>(entity =>
{
 
  entity.Property(e => e.AccountNumber)
    .ValueGeneratedOnAdd()
    .HasValueGenerator<AccountGenerator>();
 
});

In the AccountGenerator class both Next and NextValue get the next account number. 

using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;
 
namespace EntityOperations.Classes
{ 
  public class  AccountGenerator : ValueGenerator<string>
  {
    public override  bool GeneratesTemporaryValues => false;
    private readonly  CustomerContext _context;
 
    public AccountGenerator()
    {
      _context = new  CustomerContext();
    }
    /// <summary>
    /// Get current account number from table and increment by 1
    /// </summary>
    /// <returns>Next account number or recycle back to first alpha number</returns>
    private string  NextAccountNumber()
    {
      var lastAccountValue = _context.NewAccountTable
        .AsNoTracking()
        .FirstOrDefault()?.CustomerAccountNumber;
 
      if (lastAccountValue == null)
      {
        return "A0000";
      }
 
      var currentValue = Increment.AlphaNumericValue(lastAccountValue);
      var accountTable = _context.NewAccountTable.FirstOrDefault();
 
      if (accountTable == null) return  currentValue;
 
      accountTable.CustomerAccountNumber = currentValue;
 
      _context.SaveChanges();
 
      return currentValue;
    }
 
    /// <summary>
    /// Template method to perform value generation for AccountNumber.
    /// </summary>
    /// <param name="entry">In this case Customer</param>
    /// <returns>Current account number</returns>
    public override  string Next(EntityEntry entry) => NextAccountNumber();
    /// <summary>
    /// Gets a value to be assigned to AccountNumber property
    /// </summary>
    /// <param name="entry">In this case Customer</param>
    /// <returns>Current account number</returns>
    protected override  object NextValue(EntityEntry entry) => NextAccountNumber();
  }
}

Testing


 The following class provides mocked data, a method to reset the account number in the database and a method to read back data added.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EntityOperations;
using Microsoft.EntityFrameworkCore;
 
namespace EntityFrontEnd.Classes
{
  public class  HelperOperations
  {
    /// <summary>
    /// Reset account number
    /// </summary>
    /// <returns></returns>
    public static  async Task<bool> ResetAccountNumber()
    {
      var result = false;
 
      await Task.Run(async () =>
      {
        using (var context = new CustomerContext())
        {
          context.NewAccountTable
            .FirstOrDefault().CustomerAccountNumber = "A0000";
 
          return context.SaveChanges() == 1;
        }
 
      });
 
      return result;
 
    }
    /// <summary>
    /// Add four mocked up customers, set account number
    /// </summary>
    /// <returns></returns>
    public async static Task<bool> AddCustomers()
    {
      var result = false;
 
      await Task.Run(async () =>
      {
        using (var context = new CustomerContext())
        {
          await context.AddRangeAsync(MockedData.Customers());
          result =  await context.SaveChangesAsync() == 4;
        }
      });
 
      return result;
 
    }
    /// <summary>
    /// Get customers
    /// </summary>
    /// <returns></returns>
    public static  async Task<List<Customer>> GetCustomers()
    {
      var customers = new  List<Customer>();
 
      await Task.Run(async () =>
      {
        using (var context = new CustomerContext())
        {
          customers = await context.Customer
            .AsNoTracking()
            .Include(customer => 
              customer.ContactTypeIdentifierNavigation)
            .Include(customer => 
              customer.GenderIdentifierNavigation)
            .ToListAsync();
        }
      });
 
      return customers;
    }
  }
}

Using a Windows form to provide a way to add, reset and present using the class above.

using System;
using System.Windows.Forms;
using EntityFrontEnd.Classes;
 
namespace EntityFrontEnd
{
  public partial  class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
 
      CustomerDataGridView.AutoGenerateColumns = false;
    }
 
    private async void AddCustomersButton_Click(object sender, EventArgs e)
    {
 
      var result = await HelperOperations.AddCustomers();
 
      MessageBox.Show(result ?
        "Add range successful" :
        "Add range failed");
 
    }
 
    private async void ResetAccountNumberButton_Click(object sender, EventArgs e)
    {
      var result = await HelperOperations.ResetAccountNumber();
 
      MessageBox.Show(result ? 
        "Reset finished" : 
        "Reset failed");
    }
 
    private async void ViewCustomersButton_Click(object sender, EventArgs e)
    {
      var customers = await HelperOperations.GetCustomers();
      CustomerDataGridView.DataSource = customers;
    }
  }
}

Setting up code sample

  1. Create database and populate tables with the following script.
  2. Right click Solution Explorer, select restore NuGet packages.
  3. Build and run.

Summary

This article has shown how to generate values for a property in a model when adding new records. Similarly shadow properties can be used which is done when saving data as explained in the following article.

See also

Source code

Clone or download the following GitHub repository.