包含其他模型属性的MVC模型 [英] MVC Model that includes property of a different Model

查看:77
本文介绍了包含其他模型属性的MVC模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对MVC比较陌生,这是我的位置:

我有2个型号:

网站模型-

  SiteID()作为整数Name()作为字符串Latitude()为Double经度()为双精度 

和设备型号-

  DeviceID()作为整数Make()作为字符串Model()作为字符串Serial()作为字符串Site()作为网站 

如您所见,设备已链接到站点.在数据库上下文中,设备"表包含外键"Site_SiteID",如下所示:

我想做的是在设备"视图中进行创建和编辑,使它成为站点"字段是一个 DropDownListFor ,其列表包含站点"表中存在的站点.然后将所选站点另存为设备站点.

这是我在创建"视图中创建的内容,用于创建将在下拉列表中的列表...其次是DropDownListFor:

  @ModelType TestWebApplication2.Device@代码ViewData("Title")=创建"将selectSite昏暗化为新列表(属于SelectListItem)Dim db =新的TestWebApplication2.Models.TestWebApplication2Context昏暗的listOfSites作为列表(站点)listOfSites =(从db.Sites中的站点选择站点).ToList()对于listOfSites中的每个项目selectSite.Add(新的SelectListItem()与{.Value = item.SiteID,.Text = item.Name})下一个结束码< div class ="form-group">@ Html.LabelFor(Function(model)model.Site,htmlAttributes:= New With {.class ="control-label col-md-2"})< div class ="col-md-10">@ Html.DropDownListFor(Function(model)model.Site,selectSite),新增了{.htmlAttributes =新增了{.class ="form-control"}})@ Html.ValidationMessageFor(Function(model)model.Site,",新的{.class ="text-danger"})</div></div> 

这是在 DevicesController 中创建的帖子:

 < HttpPost()>< ValidateAntiForgeryToken()>异步函数Create(< Bind(Include:="DeviceID,Make,Model,Serial,Site")> ByVal设备作为设备)作为任务(动作结果)如果ModelState.IsValid然后db.Devices.Add(设备)等待db.SaveChangesAsync()返回RedirectToAction("Index")万一返回视图(设备)结束功能 

此方法的工作原理是,我可以在创建"页面的下拉列表中获得站点"列表,但是当我单击保存"时,它给我一个错误,例如值'1'无效".这是因为它试图传递的是字符串(不是列表项的值的类型),而不是站点.

因此,我尝试做的一件事是将每个下拉列表项的Value设置为站点项本身,如下所示:

  selectSite.Add(新的SelectListItem()与{.Value = item,.Text = item.Name}) 

但是它给了我一个错误,它无法将Site强制转换为字符串,因此您无法传递回整个对象.

因此,我改为尝试通过在Controller中按ID获取设备的站点来设置它,例如(注意,站点"已从绑定"列表中删除):

 < HttpPost()>< ValidateAntiForgeryToken()>异步函数Create(< Bind(Include:="DeviceID,Make,Model,Serial")> ByVal设备作为设备)作为任务(动作结果)如果ModelState.IsValid然后将thisSite设置为Integer = CInt(Request.Form.Get("Site"))device.Site =(来自db.Sites中的站点,其中site.SiteID = thisSite选择站点).FirstOrDefault()db.Devices.Add(设备)等待db.SaveChangesAsync()返回RedirectToAction("Index")万一返回视图(设备)结束功能 

这确实可以设置设备的站点,但实际上并没有保存到数据库中(我想是因为数据库只想保留外键,而不是实际的Site对象).因此,下次我访问索引"或详细信息"页面时,设备的站点为 Nothing .

我想必须有一种方法可以做到这一点,但是我不确定从这里还能尝试什么.感谢您的帮助!

解决方案

直到现在我还没有意识到被称为.因此, Site 作为其自身模型的 Device 的属性,将两个表链接在一起.

因此,正如史蒂夫·格林(Steve Greene)所暗示的那样,我所缺少的是将模型中的 Site Device 包含在我想要使用的视图的模型中网站名称(在索引列表中,并使用谓词进行详细信息和删除)...称为急于加载" ./p>

我还建议使用ViewData将生成DropDownFor项目列表的代码移至Controller.现在,正如问题中所述,因为仅回传了站点ID,所以我必须根据他们选择的项目的ID手动设置设备的站点.

这是我的设备控制器现在的样子:

 异步函数Index()作为Task(Of ActionResult)'需要具有此包含站点,以便它可以提取该数据以便在视图中引用站点名称'(这样做称为急切加载")昏暗的设备= db.Devices.Include("Site").ToListAsync()返回视图(等待设备)结束功能GET:设备/详细信息/5异步函数详细信息(ByVal id为整数吗?)作为任务(动作结果的)如果IsNothing(id)然后返回新的HttpStatusCodeResult(HttpStatusCode.BadRequest)万一昏暗的设备作为设备=等待db.Devices.FindAsync(id)如果IsNothing(设备)则返回HttpNotFound()别的'需要使用谓词使该站点包括在内,以便它可以提取该数据以便在视图中引用站点名称device = db.Devices.Include("Site").Where(Function(x)x.DeviceID = id).FirstOrDefault()万一返回视图(设备)结束功能GET:设备/创建函数Create()'在视图中创建DropDownFor将引用的项目列表将selectSite昏暗化为新列表(属于SelectListItem)Dim db =新的TestWebApplication2.Models.TestWebApplication2Context昏暗的listOfSites作为列表(站点)listOfSites =(从db.Sites中的站点选择站点).ToList()对于listOfSites中的每个项目selectSite.Add(新的SelectListItem()与{.Value = item.SiteID,.Text = item.Name})下一个ViewData("selectSite")= selectSite返回View()结束功能开机自检:设备/创建为防止过度发布攻击,请启用您要绑定的特定属性,以用于'更多详细信息,请参见http://go.microsoft.com/fwlink/?LinkId=317598.< HttpPost()>< ValidateAntiForgeryToken()>异步函数Create(< Bind(Include:="DeviceID,Make,Model,Serial")> ByVal设备作为设备)作为任务(动作结果)如果ModelState.IsValid然后'由于在DropDownFor中选择了站点,该站点仅传递ID整数,因此需要相应地设置设备的site属性请注意,将网站作为导航属性,它看起来并没有像您想象的那样被保存...但是,这一切都可以将thisSite设置为Integer = CInt(Request.Form.Get("Site"))device.Site =(来自db.Sites中的站点,其中site.SiteID = thisSite选择站点).FirstOrDefault()db.Devices.Add(设备)等待db.SaveChangesAsync()返回RedirectToAction("Index")万一返回视图(设备)结束功能GET:设备/编辑/5异步函数编辑(ByVal id为整数?)作为任务(动作结果)如果IsNothing(id)然后返回新的HttpStatusCodeResult(HttpStatusCode.BadRequest)万一昏暗的设备作为设备=等待db.Devices.FindAsync(id)如果IsNothing(设备)则返回HttpNotFound()别的'在视图中创建DropDownFor将引用的项目列表将selectSite昏暗化为新列表(属于SelectListItem)Dim db =新的TestWebApplication2.Models.TestWebApplication2Context昏暗的listOfSites作为列表(站点)listOfSites =(从db.Sites中的站点选择站点).ToList()对于listOfSites中的每个项目selectSite.Add(新的SelectListItem()与{.Value = item.SiteID,.Text = item.Name})下一个ViewData("selectSite")= selectSite万一返回视图(设备)结束功能开机自检:设备/编辑/5为防止过度发布攻击,请启用您要绑定的特定属性,以用于'更多详细信息,请参见http://go.microsoft.com/fwlink/?LinkId=317598.< HttpPost()>< ValidateAntiForgeryToken()>异步功能Edit(< Bind(Include:="DeviceID,Make,Model,Serial")> ByVal设备作为设备)作为任务(动作结果)如果ModelState.IsValid然后'由于在DropDownFor中选择了站点,该站点仅传递ID整数,因此需要相应地设置设备的site属性请注意,将网站作为导航属性,它看起来并没有像您想象的那样被保存...但是,这一切都可以将thisSite设置为Integer = CInt(Request.Form.Get("Site"))device.Site =(来自db.Sites中的站点,其中site.SiteID = thisSite选择站点).FirstOrDefault()db.Entry(device).State = EntityState.Modified等待db.SaveChangesAsync()返回RedirectToAction("Index")万一返回视图(设备)结束功能GET:设备/删除/5异步函数Delete(ByVal id为Integer?)作为Task(Of ActionResult)如果IsNothing(id)然后返回新的HttpStatusCodeResult(HttpStatusCode.BadRequest)万一昏暗的设备作为设备=等待db.Devices.FindAsync(id)如果IsNothing(设备)则返回HttpNotFound()别的'需要使用谓词使该站点包括在内,以便它可以提取该数据以便在视图中引用站点名称device = db.Devices.Include("Site").Where(Function(x)x.DeviceID = id).FirstOrDefault()万一返回视图(设备)结束功能开机自检:Devices/Delete/5< HttpPost()>< ActionName("Delete")>< ValidateAntiForgeryToken()>异步函数DeleteConfirmed(ByVal id为整数)作为任务(ActionResult的)昏暗的设备作为设备=等待db.Devices.FindAsync(id)db.Devices.Remove(设备)等待db.SaveChangesAsync()返回RedirectToAction("Index")结束功能受保护的重写子Dispose(ByVal以布尔方式进行处置)如果(处置)则db.Dispose()万一MyBase.Dispose(处理)结束子 

I am relatively new to MVC, and here's where I'm at:

I have 2 Models:

The Site model -

SiteID() As Integer
Name() As String
Latitude() As Double
Longitude() As Double

And the Device model -

DeviceID() As Integer
Make() As String
Model() As String
Serial() As String
Site() As Site

As you can see, a Device is linked to a Site. In the DB Context, the Devices table contains the foreign key "Site_SiteID" as shown:

What I'm trying to do is in the Device views for Create and Edit, make it so the Site field is a DropDownListFor, whose list contains the Sites that exist in the Site table. Then save the selected site as the device's site.

This is what I put in the Create view for creating the list that will be in the drop down...followed by the DropDownListFor:

@ModelType TestWebApplication2.Device
@Code
    ViewData("Title") = "Create"

    Dim selectSite As New List(Of SelectListItem)
    Dim db = New TestWebApplication2.Models.TestWebApplication2Context
    Dim listOfSites As List(Of Site)
    listOfSites = (From site In db.Sites Select site).ToList()

    For Each item In listOfSites
        selectSite.Add(New SelectListItem() With {.Value = item.SiteID, .Text = item.Name})
    Next

End Code

 <div class="form-group">
     @Html.LabelFor(Function(model) model.Site, htmlAttributes:=New With {.class = "control-label col-md-2"})
     <div class="col-md-10">
         @Html.DropDownListFor(Function(model) model.Site, selectSite), New With {.htmlAttributes = New With {.class = "form-control"}})
         @Html.ValidationMessageFor(Function(model) model.Site, "", New With {.class = "text-danger"})
     </div>
 </div>

This is the Create post in DevicesController:

    <HttpPost()>
    <ValidateAntiForgeryToken()>
    Async Function Create(<Bind(Include:="DeviceID,Make,Model,Serial,Site")> ByVal device As Device) As Task(Of ActionResult)
        If ModelState.IsValid Then
            db.Devices.Add(device)
            Await db.SaveChangesAsync()
            Return RedirectToAction("Index")
        End If
        Return View(device)
    End Function

This works in that I can get the list of Sites in the drop down in the Create page, but when I hit "Save", it gives me an error like "The value '1' is invalid". This is because it's trying to pass in a string (being that's the type that is the list item's Value) instead of a Site.

So one thing I tried was setting the Value for each drop down list item to the site item itself, like so:

    selectSite.Add(New SelectListItem() With {.Value = item, .Text = item.Name})

But it gives me an error that it can't cast a Site to a String, and so you can't pass back the whole object.

So then I tried instead to set the device's Site by getting it by ID in the Controller, like so (note "Site" was taken out of the Bind list):

    <HttpPost()>
    <ValidateAntiForgeryToken()>
    Async Function Create(<Bind(Include:="DeviceID,Make,Model,Serial")> ByVal device As Device) As Task(Of ActionResult)
        If ModelState.IsValid Then
            Dim thisSite As Integer = CInt(Request.Form.Get("Site"))
            device.Site = (From site In db.Sites Where site.SiteID = thisSite Select site).FirstOrDefault()
            db.Devices.Add(device)
            Await db.SaveChangesAsync()
            Return RedirectToAction("Index")
        End If
        Return View(device)
    End Function

This does work to set the Device's Site, but it is not actually saved to the DB (I'm assuming because the DB wants to only hold the foreign key, not an actual Site object). So the next time I go to like the Index or Details page, the device's site is Nothing.

I would imagine there must a way to do this, but I'm not sure what else to try from here. Any help is appreciated!

解决方案

I was until now unaware of this thing called a "Navigation Property". So Site, being a property of a Device which is it's own Model, links the two tables together.

So what I was missing, as Steve Greene had clued me in on, was Including Site along with Device in the models for the views in which I wanted to use the Site's Name (in the list for Index, and using a Predicate for Details and Delete)...called "Eager Loading".

I also moved the code for generating the list of items for the DropDownFor to the Controller, as suggested, using a ViewData. Now as stated in the question, because that is passing back only the Site ID I have to manually set the Device's Site based on the ID of the item they selected.

So here is what my Devices Controller looks like now:

        Async Function Index() As Task(Of ActionResult)
            'Need to have this include Site so that it will pull in that data in order to reference site name in the View
            '(doing this is called Eager Loading)
            Dim devices = db.Devices.Include("Site").ToListAsync()
            Return View(Await devices)
        End Function

        ' GET: Devices/Details/5
        Async Function Details(ByVal id As Integer?) As Task(Of ActionResult)
            If IsNothing(id) Then
                Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
            End If
            Dim device As Device = Await db.Devices.FindAsync(id)
            If IsNothing(device) Then
                Return HttpNotFound()
            Else
                'Need to have this include Site using Predicate so that it will pull in that data in order to reference site name in the View
                device = db.Devices.Include("Site").Where(Function(x) x.DeviceID = id).FirstOrDefault()
            End If
            Return View(device)
        End Function

        ' GET: Devices/Create
        Function Create()
            'Create the list of items that the DropDownFor in the View will reference
            Dim selectSite As New List(Of SelectListItem)
            Dim db = New TestWebApplication2.Models.TestWebApplication2Context
            Dim listOfSites As List(Of Site)
            listOfSites = (From site In db.Sites Select site).ToList()

            For Each item In listOfSites
                selectSite.Add(New SelectListItem() With {.Value = item.SiteID, .Text = item.Name})
            Next
            ViewData("selectSite") = selectSite

            Return View()
        End Function

        ' POST: Devices/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()>
        Async Function Create(<Bind(Include:="DeviceID,Make,Model,Serial")> ByVal device As Device) As Task(Of ActionResult)
            If ModelState.IsValid Then
                'Because Site is selected in a DropDownFor which only passes the ID integer, need to set the device's site property accordingly
                'Note that with Site being a Navigation property, it looks like it doesn't get saved the way you would think...but it all works
                Dim thisSite As Integer = CInt(Request.Form.Get("Site"))
                device.Site = (From site In db.Sites Where site.SiteID = thisSite Select site).FirstOrDefault()
                db.Devices.Add(device)
                Await db.SaveChangesAsync()
                Return RedirectToAction("Index")
            End If
            Return View(device)
        End Function

        ' GET: Devices/Edit/5
        Async Function Edit(ByVal id As Integer?) As Task(Of ActionResult)
            If IsNothing(id) Then
                Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
            End If
            Dim device As Device = Await db.Devices.FindAsync(id)
            If IsNothing(device) Then
                Return HttpNotFound()
            Else
                'Create the list of items that the DropDownFor in the View will reference
                Dim selectSite As New List(Of SelectListItem)
                Dim db = New TestWebApplication2.Models.TestWebApplication2Context
                Dim listOfSites As List(Of Site)
                listOfSites = (From site In db.Sites Select site).ToList()

                For Each item In listOfSites
                    selectSite.Add(New SelectListItem() With {.Value = item.SiteID, .Text = item.Name})
                Next
                ViewData("selectSite") = selectSite
            End If

            Return View(device)
        End Function

        ' POST: Devices/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()>
        Async Function Edit(<Bind(Include:="DeviceID,Make,Model,Serial")> ByVal device As Device) As Task(Of ActionResult)
            If ModelState.IsValid Then
                'Because Site is selected in a DropDownFor which only passes the ID integer, need to set the device's site property accordingly
                'Note that with Site being a Navigation property, it looks like it doesn't get saved the way you would think...but it all works
                Dim thisSite As Integer = CInt(Request.Form.Get("Site"))
                device.Site = (From site In db.Sites Where site.SiteID = thisSite Select site).FirstOrDefault()
                db.Entry(device).State = EntityState.Modified
                Await db.SaveChangesAsync()
                Return RedirectToAction("Index")
            End If
            Return View(device)
        End Function

        ' GET: Devices/Delete/5
        Async Function Delete(ByVal id As Integer?) As Task(Of ActionResult)
            If IsNothing(id) Then
                Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
            End If
            Dim device As Device = Await db.Devices.FindAsync(id)
            If IsNothing(device) Then
                Return HttpNotFound()
            Else
                'Need to have this include Site using Predicate so that it will pull in that data in order to reference site name in the View
                device = db.Devices.Include("Site").Where(Function(x) x.DeviceID = id).FirstOrDefault()
            End If
            Return View(device)
        End Function

        ' POST: Devices/Delete/5
        <HttpPost()>
        <ActionName("Delete")>
        <ValidateAntiForgeryToken()>
        Async Function DeleteConfirmed(ByVal id As Integer) As Task(Of ActionResult)
            Dim device As Device = Await db.Devices.FindAsync(id)
            db.Devices.Remove(device)
            Await db.SaveChangesAsync()
            Return RedirectToAction("Index")
        End Function

        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If (disposing) Then
                db.Dispose()
            End If
            MyBase.Dispose(disposing)
        End Sub

这篇关于包含其他模型属性的MVC模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆