使用复杂的视图模型和带有Linq to SQL的交叉表生成带有MVC3的复选框列表

时间:2012-01-18 18:59:50

标签: c# asp.net-mvc-3 linq-to-sql normalization one-to-many

我继承了我的第一个MVC项目,它涉及在Linq to SQL之上使用MVC3。我一直试图找到一种方法来生成一个复选框列表,该列表基于涉及交叉表的多对多关系。

我有一个systemFailureType表,它通过交叉表映射到SystemFailureProblem表。

这是表格的设计师布局:

Designer Layout 这是我的模特:

     [MetadataType(typeof(SystemFailureProblemMetadata))]
    public partial class SystemFailureProblem
    {        
        private class SystemFailureProblemMetadata
        {
            public int ID { get; set; }

            [Required]
            [StringLength(200)]
            [DisplayName("Problem Description")]
            public String Description { get; set; }

            public IList<xSystemFailureProblemToType> FailureTypeCategories { get; set; }

        }        

}



[MetadataType(typeof(SystemFailureTypeMetaData))]
    public partial class SystemFailureType
    {
        private class SystemFailureTypeMetaData
        {
            public int ID { get; set; }

            [Required]
            [StringLength(200)]
            public String Description { get; set; }
        }
    }

我当前的视图代码使用包含问题对象的视图模型。因此,我生成复选框列表的当前代码如下所示:

 @for(int i=0;i < Model.problem.FailureTypeCategories.Count(); i++)
 {
       @Html.CheckBox("FailureTypeCategories["+i+"].ID", false)

 }

我的主要问题是,当我尝试生成表示FailureTypeCategories集合不存在的复选框列表时,我遇到了一些错误。我怀疑它可能与我目前如何设置模型有关。我最初的想法是倾向于实现交叉表的模型,虽然我不太确定如何整合它。我应该采用不同的方式来解决这个问题,还是我走在正确的轨道上而只是错过了一些东西?

编辑:

这是ViewModel

        public SystemFailureProblem problem { get; set; }

        public SystemFailureProblemViewModel() { }

        public SystemFailureProblemViewModel(SystemFailureProblem problem)
        {
            this.problem = problem;
        }

控制器方法非常简单。它只返回表单的部分视图。

 public ActionResult Edit(int id)
    {            
        try
        {
            return PartialView("Form", context.SystemFailureProblems.Single(p => p.ID == id));
        }
        catch (Exception ex)
        {
            ModelState.AddModelError("", ex.Message);
            return PartialView("Form", null);
        }
    }

1 个答案:

答案 0 :(得分:1)

我提出了一个基于this article的想法,该想法利用了Entity Framework,但转换成LinqToSql类并不太难。

首先,我调整了ViewModel类。除了SystemFailureProblem对象之外,您还需要在其中存储更多信息,例如与分配给该问题的SystemFailureType集合相关的信息。

public class SystemFailureProblemTypeViewModel
{
    public int TypeID { get; set; }
    public string TypeDescription { get; set; }
    public bool Assigned { get; set; }
}

接下来,我为Edit操作(GET和POST)创建了逻辑。在GET方法中,您可以找出当前为问题选择的类型(来自xSystemFailureProblemToType表),并使用该数据构造ViewModel。此ViewModel与SystemFailureProblem对象一起传递给View。

public ActionResult Edit(int id)
    {
        SystemFailureProblem problem = (from p in context.SystemFailureProblems
                                        where p.ID == id
                                        select p).Single();

        PopulateSystemFailureProblemData(problem);

        return View(problem);
    }

    public void PopulateSystemFailureProblemData(SystemFailureProblem problem)
    {
        // get all failure types
        var allTypes = from t in context.SystemFailureTypes select t;

        // get al types joined with this problem using cross table
        var problemTypes = from x in context.xSystemFailureProblemToTypes
                           join t in context.SystemFailureTypes on x.SystemFailureTypeID equals t.ID
                           where x.SystemFailureProblemID == problem.ID
                           select t;

        // construct view model collection
        List<SystemFailureProblemTypeViewModel> viewModel = new List<SystemFailureProblemTypeViewModel>();
        foreach (var type in allTypes)
        {
            viewModel.Add(new SystemFailureProblemTypeViewModel
            {
                TypeID = type.ID,
                TypeDescription = type.Description,
                Assigned = problemTypes.Contains(type)
            });
        }

        ViewBag.Types = viewModel;
    }

在POST方法中,我们得到一个string []参数,告诉我们检查了哪些复选框。它是SystemFailureType ID的列表。遍历数据库中的每个SystemFailureType,确定选择/取消选择哪些,并相应地更新xSystemFailureProblemToType表。

[HttpPost]
    public ActionResult Edit(int id, FormCollection collection, string[] selectedTypes)
    {
        SystemFailureProblem problem = (from p in context.SystemFailureProblems
                                        where p.ID == id
                                        select p).Single();

        // get all types joined with this problem using cross table
        var problemTypes = from x in context.xSystemFailureProblemToTypes
                           join t in context.SystemFailureTypes on x.SystemFailureTypeID equals t.ID
                           where x.SystemFailureProblemID == problem.ID
                           select t;

        problem.FailureTypes = problemTypes.ToList<SystemFailureType>();

        if (TryUpdateModel(problem, "", null, new string[] { "Types" }))
        {
            try
            {
                // loop through all types in the system
                foreach (var failureType in context.SystemFailureTypes)
                {
                    // determine if checkbox for current type was checked
                    if (selectedTypes.Contains(failureType.ID.ToString()))
                    {
                        // if no joining record exists (type not previously selected), create a joining record
                        if (!problemTypes.Contains(failureType))
                        {
                            context.xSystemFailureProblemToTypes.InsertOnSubmit(
                                new xSystemFailureProblemToType
                                {
                                    SystemFailureProblemID = problem.ID,
                                    SystemFailureTypeID = failureType.ID
                                });
                        }
                    }
                    else
                    {
                        // if type was unchecked but joining record exists, delete it
                        if (problemTypes.Contains(failureType))
                        {
                            xSystemFailureProblemToType toDelete = (from x in context.xSystemFailureProblemToTypes
                                                                    where x.SystemFailureProblemID == problem.ID &&
                                                                    x.SystemFailureTypeID == failureType.ID
                                                                    select x).SingleOrDefault();
                            context.xSystemFailureProblemToTypes.DeleteOnSubmit(toDelete);

                        }
                    }
                }
                context.SubmitChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        PopulateSystemFailureProblemData(problem);
        return View(problem);
    }

最后,我调整了View。此代码将创建3列复选框,每个复选框的value属性为其代表的SystemFailureType ID。

<div class="editor-field">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<SystemFailures.Data.SystemFailureProblemTypeViewModel> types = ViewBag.Types;

                    foreach (var type in types) {
                        if (cnt++ % 3 == 0) {
                            @:  </tr> <tr> 
                        }
                        @: <td> 
                            <input type="checkbox" 
                                   name="selectedTypes" 
                                   value="@type.TypeID" 
                                   @(Html.Raw(type.Assigned ? "checked=\"checked\"" : "")) /> 
                            @type.TypeDescription
                        @:</td>
                    }
                    @: </tr>
                }
        </table>
    </div>

它可能不是最有效的,但我认为它有效地解决了您问题中最复杂的部分。如果我遗漏了什么,请告诉我!