【问题标题】:ASP.NET Core MVC Edit CheckBoxList values in DatabaseASP.NET Core MVC 编辑数据库中的 CheckBoxList 值
【发布时间】:2016-11-01 14:50:19
【问题描述】:

我无法理解如何在 [HttpPost] 编辑操作方法 CustomerDeviceController 上将 CheckBoxList 值更新到我的数据库中的 CustomerDevice 表。

CustomerDeviceController 的我的索引操作方法显示我的客户表中的客户列表。我有一个标记为“Edit”的 ActionLink,它将 CustId 值传递给 CustomerDeviceController [HttpGet] Edit(int?id) Action Method,然后将分配给 CustId 的所有选定 DevId 值显示到 CheckBoxList,这部分工作正常。

当我现在尝试更新 CheckBoxList 值时,我收到以下错误消息。有人告诉我,我需要遍历并删除 DevId 值,然后才能更新它们。这是我无法理解的部分。

更新 CheckBoxList 时出现错误消息

Microsoft.EntityFrameworkCore.dll 中出现“System.InvalidOperationException”类型的异常,但未在用户代码中处理

附加信息:无法跟踪实体类型“CustomerDevice”的实例,因为已在跟踪具有相同键的该类型的另一个实例。添加新实体时,对于大多数键类型,如果未设置键(即,如果键属性为其类型分配了默认值),则会创建一个唯一的临时键值。如果您为新实体显式设置键值,请确保它们不会与现有实体或为其他新实体生成的临时值发生冲突。附加现有实体时,请确保只有一个具有给定键值的实体实例附加到上下文。

模型

    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; }
}

视图模型

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

客户设备控制器

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);
    }

编辑视图

    <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 });
    }
}

【问题讨论】:

    标签: c# asp.net-core-mvc


    【解决方案1】:

    基本上,您正在尝试根据我的理解进行多对多的编辑。

    首先你应该看看这个Many-To-Many。您将需要像示例中一样更新模型,并在 OnModelCreating 方法中手动添加关系。也不要忘记在您的数据库上下文中为其添加 DbSet。

    在客户和设备模型中添加:

    public List&lt;CustomerDevice&gt; CustomerDevices{ get; set; }

    在 WebFormContext OnModelCreating 你应该有:

            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);
    

    之后,您可以使用 include 快速加载相关对象(例如在 Edit 操作中)(see more about that):

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

    那么你应该只对分离的客户进行操作。

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

    然后将更改保存给您的客户

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

    在你的情况下,你可以这样做:

    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);
    }
    

    【讨论】:

    • 我编辑了我的帖子以包含我的 OnModelCreating 方法。我不确定如何或将在我的 OnModelCreating 方法中急切地加载相关对象。另外,Customer customer = db.Customers .Include(c =&gt; c.CustomerDevices) .SingleOrDefault(c =&gt; c.CustId == id); 是 EditPost 方法吗?你也可以帮我处理customer.CustomerDevices.Add(...); customer.CustomerDevices.Remove(...);//edit
    • 请看我更新的帖子。您仍然需要通过在 Customer 和 Device 中添加 CustomerDevices 列表来更改模型,并且您错过了 OnModelCreating 中的多对多关系。我还添加了一个关于如何实现编辑操作的示例。
    • 附带说明,您选择的模型 ID 非常糟糕。按照惯例,名为 Id 或 Id 的属性将被配置为实体的键。否则你必须自己设置。见docs.efproject.net/en/latest/modeling/keys.html
    • 好的,我按照您的建议进行了以下更改,但在更新时我仍然收到上述 更新 CheckBoxList 时出现错误消息 的相同错误。如果我完全删除该用户的选定值并添加新值,那很好。但是,如果我选择新值并保留其中一个旧值,则会出现该错误。
    • 我还必须将Customer customer = db.Customers .Include(c =&gt; c.CustomerDevices) .SingleOrDefault(c =&gt; c.CustId == id); 修改为Customer customer = db.Customers .Include(c =&gt; c.CustomerDevices) .SingleOrDefault(c =&gt; c.CustId == vmEdit.CustId);
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-30
    相关资源
    最近更新 更多