简体   繁体   中英

ASP.NET Core MVC Edit CheckBoxList values in Database

I'm having trouble understanding how to update the CheckBoxList values to the CustomerDevice table in my Database on the [HttpPost] Edit Action Method CustomerDeviceController.

My Index Action Method for the CustomerDeviceController displays a list of Customers from my Customers table. I have an ActionLink labeled "Edit" that passes the CustId value to the CustomerDeviceController [HttpGet] Edit(int? id) Action Method which then displays all selected DevId values assigned to the CustId to the CheckBoxList, this part works fine.

When I try to update the CheckBoxList values now I get the following error message below. I was told that I would need to loop through and delete the DevId values before I could update them. This is the part I'm having trouble understanding.

Error Message when updating CheckBoxList

An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code

Additional information: The instance of entity type 'CustomerDevice' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (ie if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

Models

    public class CheckBoxListItem
{
    public int ID { get; set; }
    public string Display { get; set; }
    public bool IsChecked { get; set; }
}

    public class Customer
{
    public int CustId { get; set; }
    public string CustDisplayName { get; set; }
    ...
}

    public class Device
{
    public int DevId { get; set; }
    public string DevType { get; set; }
}

    public class CustomerDevice
{
    public int CustId { get; set; }
    public int DevId { get; set; }

    public Customer Customer { get; set; }
    public Device Device { get; set; }
}

ViewModels

    public class CustomerDeviceFormViewModel
{
    public int CustId { get; set; }
    public string CustDisplayName { get; set; }
    public List<CheckBoxListItem> Devices { get; set; }
}

CustomerDeviceController

public ActionResult Create(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var customervm = new CustomerDeviceFormViewModel();
        {
            Customer customer = db.Customers.SingleOrDefault(c => c.CustId == id);

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

            customervm.CustId = customer.CustId;
            customervm.CustDisplayName = customer.CustDisplayName;

            // Retrieves list of Devices for CheckBoxList
            var deviceList = db.Devices.ToList();
            var checkBoxListItems = new List<CheckBoxListItem>();
            foreach (var device in deviceList)
            {
                checkBoxListItems.Add(new CheckBoxListItem()
                {
                    ID = device.DevId,
                    Display = device.DevType,
                    IsChecked = false //On the create view, no devices are selected by default
                });
            }

            customervm.Devices = checkBoxListItems;
            return View(customervm);
        }
    }


    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(CustomerDeviceFormViewModel vm)
    {
        if (ModelState.IsValid)
        {
            foreach (var deviceId in vm.Devices.Where(x => x.IsChecked).Select(x => x.ID))
            {
                var customerDevices = new CustomerDevice
                {
                    CustId = vm.CustId,
                    DevId = deviceId
                };

                db.CustomerDevices.Add(customerDevices);
            }

            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(vm);
    }


    public ActionResult Edit(int? id)
    {
        Customer customer = db.Customers.SingleOrDefault(c => c.CustId == id);
        if (customer == null)
        {
            return NotFound();
        }
        // Get all devices
        var deviceList = db.Devices.ToList();
        // Get the selected device ID's for the customer
        IEnumerable<int> selectedDevices = db.CustomerDevices
            .Where(x => x.CustId == id).Select(x => x.DevId);
        // Build view model
        var model = new CustomerDeviceFormViewModel()
        {
            CustId = customer.CustId,
            CustDisplayName = customer.CustDisplayName,
            Devices = deviceList.Select(x => new CheckBoxListItem()
            {
                ID = x.DevId,
                Display = x.DevType,
                IsChecked = selectedDevices.Contains(x.DevId)
            }).ToList()
        };
        return View(model);
    }


    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(CustomerDeviceFormViewModel vmEdit)
    {
        if (ModelState.IsValid)
        {
            Customer customer = db.Customers
                       .Include(c => c.CustomerDevices)
                       .SingleOrDefault(c => c.CustId == vmEdit.CustId);

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

            IEnumerable<int> selectedDevices = vmEdit.Devices.Where(x => x.IsChecked).Select(x => x.ID);

            // Remove all selected devices for this customer
            foreach (int removeId in selectedDevices)
            {
                customer.CustomerDevices.Clear();
            }


            // Add the new selected devices
            foreach (int deviceId in selectedDevices)
            {
                CustomerDevice customerDevice = new CustomerDevice
                {
                    CustId = customer.CustId,
                    DevId = deviceId
                };
                customer.CustomerDevices.Add(customerDevice);        
            }

            // Update the customer
            db.Customers.Update(customer); //or just db.Update(customer); same thing
            //                               // Save and redirect
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(vmEdit);
    }

Edit View

    <div class="form-group">
    Please select the Devices to assign to <b>@Html.DisplayFor(c => c.CustDisplayName)</b>
</div>

<div class="form-group">
    @Html.EditorFor(x => x.Devices)
</div>

@Html.HiddenFor(c => c.CustId)

<div class="form-group">
    <button type="submit" class="btn btn-primary">Submit</button>
</div>

Shared/EditorTemplates/CheckBoxListItem.chtml

<div class="checkbox">
<label>
    @Html.HiddenFor(x => x.ID)
    @Html.CheckBoxFor(x => x.IsChecked)
    @Html.LabelFor(x => x.IsChecked, Model.Display)
</label>
<br />

WebFormContext

public class WebFormContext : DbContext
{
    public WebFormContext(DbContextOptions<WebFormContext> options)
        : base(options)
    { }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<State> States { get; set; }
    public DbSet<Device> Devices { get; set; }
    public DbSet<CustomerDevice> CustomerDevices { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>()
            .HasKey(c => c.CustId);

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustDisplayName)
            .HasColumnType("varchar(100)")
            .HasMaxLength(100)
            .IsRequired();

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustFirstName)
            .HasColumnType("varchar(50)")
            .HasMaxLength(50);

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustLastName)
            .HasColumnType("varchar(50)")
            .HasMaxLength(50);

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustCompanyName)
            .HasColumnType("varchar(50)")
            .HasMaxLength(50);

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustAddress)
            .HasColumnType("varchar(100)")
            .HasMaxLength(100)
            .IsRequired();

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustPhoneNumber)
            .HasColumnType("varchar(12)")
            .HasMaxLength(12);

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustMobileNumber)
            .HasColumnType("varchar(12)")
            .HasMaxLength(12);

        modelBuilder.Entity<Customer>()
            .Property(c => c.CustEmailAddress)
            .HasColumnType("varchar(320)")
            .HasMaxLength(320);

        modelBuilder.Entity<Device>()
            .HasKey(d => d.DevId);

        modelBuilder.Entity<Device>()
            .Property(d => d.DevType)
            .HasColumnType("varchar(50)")
            .HasMaxLength(50)
            .IsRequired();

        modelBuilder.Entity<CustomerDevice>()
            .HasKey(cd => new { cd.CustId, cd.DevId });
    }
}

Basically you are trying to do a many to many edit from what I understand.

First you should have a look at this Many-To-Many . You will need to update your models like in the example and also add the relationship manually in the OnModelCreating method. Also don't forget to add the DbSet for it on your DB context.

In Customer and Device model add:

public List<CustomerDevice> CustomerDevices{ get; set; }

In WebFormContext OnModelCreating you should have:

        modelBuilder.Entity<CustomerDevice>()
            .HasKey(c => new { c.CustId, c.DevId });

        modelBuilder.Entity<CustomerDevice>()
            .HasOne(cd => cd.Customer)
            .WithMany(c => c.CustomerDevices)
            .HasForeignKey(cd => cd.CustId);

        modelBuilder.Entity<CustomerDevice>()
            .HasOne(cd => cd.Device)
            .WithMany(d => d.CustomerDevices)
            .HasForeignKey(cd => cd.DevId);

After that you can use include to eagerly load the related objects (in the Edit action for example) (see more about that) :

Customer customer = db.Customers
                   .Include(c => c.CustomerDevices)
                   .SingleOrDefault(c => c.CustId == id);

Then you should only do operations on the detached customer.

customer.CustomerDevices.Add(...);
customer.CustomerDevices.Remove(...);
//edit

After that save the changes to your customer

db.Customers.Update(customer);
db.SaveChanges();

In your case you can do something like this:

public ActionResult Edit(CustomerDeviceFormViewModel vmEdit)
{
    if (ModelState.IsValid)
    {
        Customer customer = db.Customers
                   .Include(c => c.CustomerDevices)
                   .SingleOrDefault(c => c.CustId == id);

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

        IEnumerable<int> selectedDevices = vmEdit.Devices.Where(x => x.IsChecked).Select(x => x.ID);

        // Add the new selected devices
        foreach (int deviceId in selectedDevices)
        {
           var customerDevice = customer.CustomerDevices.FirstOrDefault(cd => cd.DevId == deviceId);
            if(customerDevice != null)
            {
                 customer.CustomerDevices.Remove(customerDevice);
            }
            else
            {
                CustomerDevice customerDevice = new CustomerDevice
                {
                    CustId = customer.Id,
                    DevId = deviceId
                };
                customer.CustomerDevices.Add(customerDevice);
            }
        }

        // Update the customer
        db.Customers.Update(customer); //or just db.Update(customer); same thing
        // Save and redirect
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(vmEdit);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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