ASP.NET Core 2.0 Prevent Duplicate Entries

10,506

You can certainly check for duplicate entries from the code side, but you will of course need to run a query against the database in order to achieve this. From your posted code, you already do something similar in EmployeesController.Edit:

Employee employee = await _context.Employees.SingleOrDefaultAsync(
    m => m.EmployeeID == id);

if (employee == null)
{
    return NotFound();
}

You could take this approach and apply a variation to EmployeesController.Create. e.g.:

Employee existingEmployee = await _context.Employees.SingleOrDefaultAsync(
    m => m.FirstName == employee.FirstName && m.LastName == employee.LastName);

if (existingEmployee != null)
{
    // The employee already exists.
    // Do whatever you need to do - This is just an example.
    ModelState.AddModelError(string.Empty, "This employee already exists.");
    employee.Departments = _context.Departments.ToList();
    employee.Appointments = _context.Appointments.ToList();
    return View(employee);
}

// Your existing code for creating a new employee.

The expression I've used in SingleOrDefaultAsync is just an example: You will need to decide what makes an employee unique according to your own requirements.

To see the error message added in the controller code I've shown, you'll need to update the Create View:

@using (Html.BeginForm("Create", "Employees"))
{
    @Html.ValidationSummary(true)
    ...
Share:
10,506
Brian
Author by

Brian

Updated on June 05, 2022

Comments

  • Brian
    Brian almost 2 years

    I'm very new to ASP.NET Core MVC. I'm using ASP.NET Core 2.0, C#, EntityFrameworkCore Code First and SQL Server 2016. I managed to get my CRUD Operations to work as needed.

    However, I need help on preventing users from registering more than once on my form. If possible I would like to check/prevent the duplicate entries from within the code on the Controller side and not within the Database.

    I'm attaching my code that I have now if someone could possible assist. Thank you in advance!

    Models

    public class Employee
    {
        public int EmployeeID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    
        public int DepartmentID { get; set; }
        public Department Department { get; set; }
    
        public int AppointmentID { get; set; }
        public Appointment Appointment { get; set; }
    }
    
    public class Department
    {
        public int DepartmentID { get; set; }
        public string Name { get; set; }
    
        public ICollection<Employee> Employees { get; set; }
    }
    
    public class Appointment
    {
        public int AppointmentID { get; set; }
        public string TimeSlot { get; set; }
    
        public ICollection<Employee> Employees { get; set; }
    }
    

    ViewModels

    public class EmployeeFormVM
    {
        public int EmployeeID { get; set; }
    
        [Required(ErrorMessage = "Please enter your First Name")]
        [Display(Name = "First Name")]
        [StringLength(50)]
        public string FirstName { get; set; }
    
        [Required(ErrorMessage = "Please enter your Last Name")]
        [Display(Name = "Last Name")]
        [StringLength(50)]
        public string LastName { get; set; }
    
        [Required(ErrorMessage = "Please select your Department")]
        [Display(Name = "Department")]
        public int DepartmentID { get; set; }
    
        public IEnumerable<Department> Departments { get; set; }
    
        [Required(ErrorMessage = "Please select your Appointment")]
        [Display(Name = "Appointment")]
        public int AppointmentID { get; set; }
    
        public IEnumerable<Appointment> Appointments { get; set; }
    }
    

    DbContext

    public class WinTenDbContext : DbContext
    {
        public WinTenDbContext(DbContextOptions<WinTenDbContext> options) : base(options)
        {
        }
    
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<Appointment> Appointments { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Employee>()
                .HasKey(e => e.EmployeeID);
    
            modelBuilder.Entity<Employee>()
                .Property(e => e.FirstName)
                .HasColumnType("varchar(50)")
                .HasMaxLength(50)
                .IsRequired();
    
    
            modelBuilder.Entity<Employee>()
                .Property(e => e.LastName)
                .HasColumnType("varchar(50)")
                .HasMaxLength(50)
                .IsRequired();            
    
            modelBuilder.Entity<Department>()
                .HasKey(d => d.DepartmentID);
    
            modelBuilder.Entity<Department>()
                .Property(d => d.Name)
                .HasColumnType("varchar(50)")
                .HasMaxLength(50);
    
            modelBuilder.Entity<Appointment>()
                .HasKey(a => a.AppointmentID);
    
            modelBuilder.Entity<Appointment>()
                .Property(a => a.TimeSlot)
                .HasColumnType("varchar(50)")
                .HasMaxLength(50);
        }
    }
    

    EmployeesController

    public class EmployeesController : Controller
    {
        private readonly WinTenDbContext _context;
    
        public EmployeesController(WinTenDbContext context)
        {
            _context = context;
        }
    
        // GET: Employees
        public async Task<IActionResult> Index()
        {
            //return View(await _context.Employees.ToListAsync());
    
            var webAppDbContext = _context.Employees.Include(d => d.Department).Include(a => a.Appointment);
            return View(await webAppDbContext.ToListAsync());
        }
    
        // GET: Employees/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
    
            var employee = await _context.Employees
                .SingleOrDefaultAsync(m => m.EmployeeID == id);
            if (employee == null)
            {
                return NotFound();
            }
    
            return View(employee);
        }
    
        // GET: Employees/Create
        public IActionResult Create()
        {
            var departments = _context.Departments.ToList();
            var appointments = _context.Appointments.ToList();
    
            var viewModel = new EmployeeFormVM
            {
                Departments = departments,
                Appointments = appointments
            };
    
            return View(viewModel);
        }
    
        // POST: Employees/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create(EmployeeFormVM employee)
        {
            if (ModelState.IsValid)
            {
                var emp = new Employee();
                {
                    emp.FirstName = employee.FirstName;
                    emp.LastName = employee.LastName;
                    emp.DepartmentID = employee.DepartmentID;
                    emp.AppointmentID = employee.AppointmentID;
                }
                _context.Add(emp);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
    
            else
            {
                employee.Departments = _context.Departments.ToList();
                employee.Appointments = _context.Appointments.ToList();
                return View(employee);
            }
        }
    
        // GET: Employees/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
    
            var employeevm = new EmployeeFormVM();
            {
                Employee employee = await _context.Employees.SingleOrDefaultAsync(m => m.EmployeeID == id);
    
                if (employee == null)
                {
                    return NotFound();
                }
    
                employeevm.EmployeeID = employee.EmployeeID;
                employeevm.FirstName = employee.FirstName;
                employeevm.LastName = employee.LastName;
    
                // Retrieve list of Departments
                var departments = _context.Departments.ToList();
                employeevm.Departments = departments;
                // Set the selected department
                employeevm.DepartmentID = employee.DepartmentID;
    
                // Retrieve list of Appointments
                var appointments = _context.Appointments.ToList();
                employeevm.Appointments = appointments;
                // Set the selected department
                employeevm.AppointmentID = employee.AppointmentID;
            }   
            return View(employeevm);
        }
    
        // POST: Employees/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(EmployeeFormVM vmEdit)
        {
            if (ModelState.IsValid)
            {
                Employee employee = _context.Employees.SingleOrDefault(e => e.EmployeeID == vmEdit.EmployeeID);
    
                if (employee == null)
                {
                    return NotFound();
                }
    
                employee.FirstName = vmEdit.FirstName;
                employee.LastName = vmEdit.LastName;
                employee.DepartmentID = vmEdit.DepartmentID;
                employee.AppointmentID = vmEdit.AppointmentID;
    
                try
                {
                    _context.Update(employee);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!EmployeeExists(vmEdit.EmployeeID))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            return View(vmEdit);
        }
    
        // GET: Employees/Delete/5
        public async Task<IActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
    
            var employee = await _context.Employees
                .SingleOrDefaultAsync(m => m.EmployeeID == id);
            if (employee == null)
            {
                return NotFound();
            }
    
            return View(employee);
        }
    
        // POST: Employees/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var employee = await _context.Employees.SingleOrDefaultAsync(m => m.EmployeeID == id);
            _context.Employees.Remove(employee);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    
        private bool EmployeeExists(int id)
        {
            return _context.Employees.Any(e => e.EmployeeID == id);
        }
    }
    

    Create View

    @using (Html.BeginForm("Create", "Employees"))
        {
            <div class="form-group">
                @Html.LabelFor(e => e.FirstName)
                @Html.TextBoxFor(e => e.FirstName, new { @class = "form-control" })
                @Html.ValidationMessageFor(e => e.FirstName)
            </div>
    
            <div class="form-group">
                @Html.LabelFor(e => e.LastName)
                @Html.TextBoxFor(e => e.LastName, new { @class = "form-control" })
                @Html.ValidationMessageFor(e => e.LastName)
            </div>
    
            <div class="form-group">
                @Html.LabelFor(d => d.DepartmentID)
                @Html.DropDownListFor(d => d.DepartmentID, new SelectList(Model.Departments, "DepartmentID", "Name"), "", new { @class = "form-control" })
                @Html.ValidationMessageFor(d => d.DepartmentID)
            </div>
    
            <div class="form-group">
                @Html.LabelFor(a => a.AppointmentID)
                @Html.DropDownListFor(a => a.AppointmentID, new SelectList(Model.Appointments, "AppointmentID", "TimeSlot"), "", new { @class = "form-control" })
                @Html.ValidationMessageFor(a => a.AppointmentID)
            </div>
    
            <div class="form-group">
                <button type="submit" class="btn btn-primary">Submit</button>
            </div>
        }
    
  • Brian
    Brian over 6 years
    Thank you for replying back. Were should I implement what you supplied within my original posted code? I tried to put what you supplied inside of the [HttpPost]Create if(ModelState.IsValid) code but when I try to enter a duplicate first and last name I get this error ArgumentNullException: Value cannot be null. Parameter name: items on this line of code @Html.DropDownListFor(d => d.DepartmentID, new SelectList(Model.Departments, "DepartmentID", "Name"), "", new { @class = "form-control" })
  • Kirk Larkin
    Kirk Larkin over 6 years
    I've edited the answer to address your specific problem. You'll want to consider restructuring the function a little to remove the duplication this adds but I think that's outside the scope of your specific question here.
  • Brian
    Brian over 6 years
    Thank you, that worked. However, I didn't see the error message This employee already exists. when I tried to enter a duplicate entry, is that something I have to add on the Create View? Also, is there a reason I have to restructure the function that you mentioned.
  • Kirk Larkin
    Kirk Larkin over 6 years
    Answer updated. You don't need to restructure the function - it's just a suggestion to consider the fact that you have duplication of the code that populates the Departments and Appointments lists.
  • Diomedes
    Diomedes over 6 years
    I recommend you use .FirstOrDefaultAsync instead of SingleOrDefaultAsync, specially if the database already has data and there is the possibility that a duplicate already exists. In this scenario, SingleOrDefaultAsync would throw and error but FirstOrDefaultAsync works as intended. If it is a clean project, then SingleOrDefaultAsync should be ok, but still, Single is safer.