Advertisement
If you have a new account but are having problems posting or verifying your account, please email us on hello@boards.ie for help. Thanks :)
Hello all! Please ensure that you are posting a new thread or question in the appropriate forum. The Feedback forum is overwhelmed with questions that are having to be moved elsewhere. If you need help to verify your account contact hello@boards.ie
Hi there,
There is an issue with role permissions that is being worked on at the moment.
If you are having trouble with access or permissions on regional forums please post here to get access: https://www.boards.ie/discussion/2058365403/you-do-not-have-permission-for-that#latest

MVC question, 2 models in 1 view...

  • 11-03-2015 12:55pm
    #1
    Closed Accounts Posts: 1,143 ✭✭✭


    Hi folks,

    Just a quick question, I have been learning to use MVC, I previously developed using Webforms so MVC is new to me. I've asked a few questions on the forum previously to help get me started with displaying data, from a DB, etc.

    I have this now working fine using a DBContext and my MS2012 SQL DB. I can read info from my DB and save info to my DB.

    Last night I decided to expand a bit upon what I am doing and I now want to read and/or edit data from two different tables (using 2 different models), from the same view, say for example, one model named 'Vehicle' and another model named 'Product'.

    On the face of it this is easy to do, after a bit of Googling, I added in a new model to my project as follows:

    [HTML]
    namespace MyMVCSolution.Models
    {

    public class MyMergedModel
    {

    //public MyMVCSolution.Models.Vehicle Vehicle { get; set; }
    //public MyMVCSolution.Models.Product Product { get; set; }
    public IEnumerable<MyMVCSolution.Models.Vehicle> Vehicles { get; set; }
    public IEnumerable<MyMVCSolution.Models.Product> Products { get; set; }

    }

    public class MyMergedModelDBContext : DbContext
    {
    public DbSet<MyMVCSolution.Models.Vehicle> VehicleDBs { get; set; }
    public DbSet<MyMVCSolution.Models.Product> ProductDBs { get; set; }

    }


    }
    [/HTML]My View looks like this:

    [HTML]
    @model IEnumerable<MyMVCSolution.Models.MyMergedViewModel>
    @{
    ViewBag.Title = "View My Queries";
    Layout = "~/Views/Shared/_Layout.MobileThemeroller.cshtml";
    }


    @using (Html.BeginForm("UpdateQuery", "Home", FormMethod.Post))
    {
    <div ID="MyCollapsibleSet" data-role="collapsible-set" style="box-shadow:none;" data-iconshadow="false">
    @foreach (var item in Model) {


    @Html.DisplayFor(modelItem => item.Vehicle.VehicleID)<br /></div>@Html.DisplayFor(modelItem => item.Vehicle.VehicleMake) @Html.DisplayFor(modelItem => item.Vehicle.VehicleModel)<br />@Html.DisplayFor(modelItem => item.Vehicle.PaymentStatus)</h3>
    <ul data-role="listview" id="MyListView" data-inset="false" data-theme="d">

    <li><b>TID: </b>@Html.DisplayFor(modelItem => item.Vehicle.ID)<li>
    <li><b>Created: </b>@Html.DisplayFor(modelItem => item.Timestamp)</li>
    <li><b>Make: </b>@Html.DisplayFor(modelItem => item.Vehicle.VehicleMake)</li>
    <li><b>Model: </b>@Html.DisplayFor(modelItem => item.Vehicle.VehicleModel)</li>
    [/HTML](Intellisense is showing a red line under every instance of 'Vehicle' above...)

    My Controller:

    [HTML]
    var context = new MyMergedModelDBContext();

    var query = context.MyMergedModelDBContext.Where(p => p.Status == QueryStatus);

    if (!string.IsNullOrEmpty(QueryStatus))
    {
    //query = query.Where(p => p.Status == QueryStatus);
    query = query.OrderByDescending(p => p.ID);
    }

    var results = query.ToList(); //this executes your query and retrieves the results into memory

    return View(results);

    [/HTML]Basically I'm just trying to expand upon the solution that was advised to me in this thread below, which has been working fine, to adapt it to use 2 models in the same view, as I am now dealing with data coming from 2 different tables accessed via 2 different models and DBContexts but I've obviously gotten something wrong here although it looks like it should be an easy thing to do.

    Thanks a mil in advance for any help with this...

    http://www.boards.ie/vbulletin/showthread.php?t=2057380052


Comments

  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    It's been a while since I have been working with MVC, but I don't think you need DbSet definitions for collections like that, or a seperate DbContext for the collections. Maybe let the DbContext only know about the model (singular), and compose your view model whatever way you like after that? I typically just use one DbContext in a simple app.

    EDIT: Looking at it again, I am not clear what you are doing. Maybe post your product and vehicle models for clarity, you may be fighting the ORM a bit here.


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    You are bumping into something that every MVC developer will have bumped into before. The solution, similar to what you have, is to create another model that can contain the data you want to pass to the view. This is typically called a View Model. It is common for every model passed to a view to be a View Model. This article explains different approaches to passing Domain Models and View Models to a view. Personally I don't think a Domain Model should ever be directly passed to a view – the data should always be mapped to a View Model first. I use AutoMapper for mapping Domain Models to View Models.

    In your solution the main thing that stands out for me is that the MyMergedModel class should not inherit from DbContext. This is because this model is not persisted to the database (i.e. there is no MyMergedModel table in the database schema). Instead you might just create a new folder and namespace called MyMVCSolution.ViewModels which the MyMergedModel class is placed in. Then you would seperately retrieve the Products and Vehicles from the database and assign them to the View Model.

    As an aside: in your code you use MyMergedViewModel in the View but you only created a class called MyMergedModel. Is this a typo or do you have another class?

    So you'd just have the following ViewModel:
    namespace MyMVCSolution.ViewModels
    {
    
        public class MyMergedModel
        {
            public IEnumerable<MyMVCSolution.Models.Vehicle> Vehicles { get; set; }
            public IEnumerable<MyMVCSolution.Models.Product> Products { get; set; }
    
        }
    }
    

    And at the top of your View:
    @model MyMVCSolution.ViewModels.MyMergedModel
    
    // you could access data with the following
    @foreach (var vehicle in Model.Vehicles) {
    
    }
    
    @foreach (var product in Model.Products) {
    
    }
    

    And the controller might look like:
    var vehicles = context.Vehicles.Where(v => v.Something == somethingElse).AsEnumerable<Vehicle>();
    var products = context.Products.Where(p => p.Something == somethingElse).AsEnumerable<Vehicle>();
    
    var model = new MyMergedModel
    {
        Vehicles = vehicles,
        Products = products
    };
    
    return View(model);
    

    The code would be a little different if the Vehicles and Products were related insted of being two independent enumerables. So if that is the case you might indicate so.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Sorry that was a typo, there is just one new model called MyMergedModel, I'm just digesting the advice from the 2 posters above and seeing what changes I need to make to give this another shot, thanks a mil for the advice to the two posters above.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Aswerty wrote: »
    The code would be a little different if the Vehicles and Products were related insted of being two independent enumerables. So if that is the case you might indicate so.

    Vehicles & Products are not related, two separate models just to clarify that. I think I'm doing something wrong with my DBContext class in my model:

    Basically the problem I'm having now is that when I try to use:

    [HTML]
    @model IEnumerable<MyMVCSolution.Models.MyMergedModel>

    @using (Html.BeginForm("UpdateQuery", "Home", FormMethod.Post))
    {
    <div ID="MyCollapsibleSet" data-role="collapsible-set" style="box-shadow:none;" data-iconshadow="false">
    @foreach (var Vehicle in Model.Vehicles) {
    [/HTML]I'm getting an intellisense error at Model.Vehicles in the code directly above (red quiggle is just under "Vehicles"), saying:

    "System.Collection.Generic.IEnumerable<MyMVCSolution.Models.MyMergedModel> does not contain a definition for 'Vehicles' and no extension method '"Vehicles' accepting a first argument of type "System.Collection.Generic.IEnumerable<MyMVCSolution.Models.MyMergedModel>" could be found (are you missing a using directive or an assembly reference?)

    This is my model code:

    [HTML]
    using System;
    using System.Data.Entity;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Configuration;
    using System.Web.Security;
    using System.Web.SessionState;
    using System.Collections;
    using System.ComponentModel;
    using System.Text;
    using System.Net;
    using System.IO;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Serialization;
    using System.Xml.XPath;
    using System.Web.Mvc;

    namespace MyMVCSolution.Models
    {

    public class MyMergedModel
    {



    public IEnumerable<MyMVCSolution.Models.Vehicle> Vehicles { get; set; }
    public IEnumerable<MyMVCSolution.Models.Product> Products { get; set; }

    }

    public class MyMergedModelDBContext : DbContext
    {
    public DbSet<MyMVCSolution.Models.Vehicle> VehicleDBs { get; set; }
    public DbSet<MyMVCSolution.Models.Product> ProductDBs { get; set; }

    }


    }
    [/HTML]My controller code, based on the advice given on thread is now:
    [HTML]
    string QueryStatus = "ProductIsInStock";
    string StockID = "ProductID1";
    var context = new MyMergedModelDBContext();

    var vehicles = context.VehicleDBs.Where(v => v.Status == QueryStatus).AsEnumerable<Vehicle>();
    var products = context.ProductDBs.Where(p => p.StockID == StockID).AsEnumerable<Product>();

    var model = new MyMergedModel
    {
    Vehicles = vehicles,
    Products = products
    };

    return View(model);

    [/HTML]My controller is not raising any errors, intellisense can find the variables in my Vehicle & Product models (highlighted in green above), but my view is not hooking up in the same way for some reason I can't work out and intellisense cannot find the two models and the variables defined within the models (obviously because it can't find the respective model, which is Vehicle and Product, within MyMergedModel).


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Shouldn't
    @model IEnumerable<MyMVCSolution.Models.MyMergedModel>
    

    just be:
    @model MyMVCSolution.Models.MyMergedModel
    

    Why do you need an IEnumberable of MyMergedModel ?

    Also, your DbContext should typically be composed of all classes which are models of db tables, it seems like you may be making a seperate DbContext for one ViewModel.


  • Advertisement
  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Shouldn't
    @model IEnumerable<MyMVCSolution.Models.MyMergedModel>
    
    just be:
    @model MyMVCSolution.Models.MyMergedModel
    
    Why do you need an IEnumberable of MyMergedModel ?

    Also, your DbContext should typically be composed of all classes which are models of db tables, it seems like you may be making a seperate DbContext for one ViewModel.

    I'm afraid I'm not up to speed enough with MVC to know the difference between what you suggested above, I was advised to do this (to use @Model IEnumerable on the previous thread I mentioned in my OP), when I was using a single model to display several rows of data from one particular table in my DB, and this worked out fine for me so I didn't really want to change it as it was working for me and I didn't see anything new when trying to use 2 models in the one view that needed me to depart from what had previously worked for me when I was just using one model in my view.

    EDIT: I thought (and I'm probably wrong here!), but I thought that where I previously had one model with one DBContext, that where I made a new model that encapsulted 2 other models, that this new "supermodel" for want of a better word lol, would need its own DBContext?


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Let's break down what is happening:

    -You are defining a ViewModel which contains two collections, one for Vehicle types, one for Product types

    -You are populating both of these collections in your controller and passing the container (ViewModel) to your view

    -You are then telling your View that you are, in fact, expecting a collection of the ViewModel defined, so a collection of collections, if you like.


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    EDIT: I thought (and I'm probably wrong here!), but I thought that where I previously had one model with one DBContext, that where I made a new model that encapsulted 2 other models, that this new "supermodel" for want of a better word lol, would need its own DBContext?

    I am almost sure this is not correct, one DbContext will suffice for most situations.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Let's break down what is happening:

    -You are defining a ViewModel which contains two collections, one for Vehicle types, one for Product types

    -You are populating both of these collections in your controller and passing the container (ViewModel) to your view

    -You are then telling your View that you are, in fact, expecting a collection of the ViewModel defined, so a collection of collections, if you like.

    So basically I don't need a DBContext at all in my MergedModel because I have already got 2 collections in that new containing model, am I right there?


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    EDIT: I thought (and I'm probably wrong here!), but I thought that where I previously had one model with one DBContext, that where I made a new model that encapsulted 2 other models, that this new "supermodel" for want of a better word lol, would need its own DBContext?

    DbContext is just something that is part of Entity Framework. It doesn't have any relationship with MVC. Once you can get your Product and Vehicle data out of the database this data can be contained in any normal class that has no relationship with DbContext.

    If in your database there was a table called MyMergedModel which had relationships with the Vehicles and Products tables then yes MyMergedModel would derive from DbContext because it would then have "Context" within the "DataBase". It's a very literal class name!
    So basically I don't need a DBContext at all in my MergedModel because I have already got 2 collections in that new containing model, am I right there?

    Yes you are right there.


  • Advertisement
  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Aswerty wrote: »

    Yes you are right there.

    Last question to get this finally sorted then is should I access the data from my controller, as at the mo I'm using a DBContext that I carried over from the solution I used previously on the forum when I just had a single model in my view, (it worked so I was reluctant to go making changes to the solution given that I'm still not very proficient with MVC!)...

    [HTML]
    string QueryStatus = "ProductIsInStock";
    string StockID = "ProductID1";
    var context = new MyMergedModelDBContext();

    var vehicles = context.VehicleDBs.Where(v => v.Status == QueryStatus).AsEnumerable<Vehicle>();

    var products = context.ProductDBs.Where(p => p.StockID == StockID).AsEnumerable<Product>();

    var model = new MyMergedModel
    {
    Vehicles = vehicles,
    Products = products
    };
    return View(model);
    [/HTML]


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    On rereading; one of the things I was telling you not to do you weren't even doing. Your approach to DbContext was fine from the get go. The naming and architecture is just a tad confusing.

    But yeah it's fine calling to the database from your controller. Some people would tell you to push it into a separate class for architectural reasons but doing it in the controller is "technically" fine.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Aswerty wrote: »
    On rereading; one of the things I was telling you not to do you weren't even doing. Your approach to DbContext was fine from the get go. The naming and architecture is just a tad confusing.

    But yeah it's fine calling to the database from your controller. Some people would tell you to push it into a separate class for architectural reasons but doing it in the controller is "technically" fine.

    My prob now is that the only way I know to access the DB from the controller is by using a DBContext. But a poster above has advised me that I don't need it in this case and should not really be using it...


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    My prob now is that the only way I know to access the DB from the controller is by using a DBContext. But a poster above has advised me that I don't need it in this case and should not really be using it...

    Think of an instance of a DbContext as a database connection. You can open a single database connection and use the same one all the time. This isn't a great idea though because in web applications it would mean you keep a single connection open for weeks at a time; this can result in performance issues. Also DbContext is not thread safe. In MVC every HTTP request is handled in a separate thread so you don't want multiple threads, during simultaneous requests, accessing the same DbContext.

    For simple single threaded desktop applications you can get away with a single DbContext.

    What you are doing at the moment is fine. There is things you could do better but that's going off topic quite a bit I think.

    Edit:

    Just to add couterpointaud might have meant one class that derives from DbContext is enough (i.e. MyMergedModelDBContext). Not that a single instance of said class is enough.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Aswerty wrote: »
    Think of an instance of a DbContext as a database connection. You can open a single database connection and use the same one all the time. This isn't a great idea though because in web applications it would mean you keep a single connection open for weeks at a time; this can result in performance issues. Also DbContext is not thread safe. In MVC every HTTP request is handled in a separate thread so you don't want multiple threads, during simultaneous requests, accessing the same DbContext.

    For simple single threaded desktop applications you can get away with a single DbContext.

    What you are doing at the moment is fine. There is things you could do better but that's going off topic quite a bit I think.

    Edit:

    Just to add couterpointaud might have meant one class that derives from DbContext is enough (i.e. MyMergedModelDBContext). Not that a single instance of said class is enough.

    So is this correct do you think?

    [HTML]
    namespace MyMVCSolution.Models { public class MyMergedModel { //public MyMVCSolution.Models.Vehicle Vehicle { get; set; } //public MyMVCSolution.Models.Product Product { get; set; } public IEnumerable<MyMVCSolution.Models.Vehicle> Vehicles { get; set; } public IEnumerable<MyMVCSolution.Models.Product> Products { get; set; } } public class MyMergedModelDBContext : DbContext { public DbSet<MyMVCSolution.Models.Vehicle> VehicleDBs { get; set; } public DbSet<MyMVCSolution.Models.Product> ProductDBs { get; set; } } }
    [/HTML]

    My problem here is that this code above is letting me access my DB columns (via the above model) in my controller, but my view is not letting me type access the same columns when I try to use:

    @foreach (var Vehicle in Model.Vehicles) {

    Even though I am using @Model IEnumerable <MyMVCSolution.Models.MyMergedView>

    And you can see MyMergedModel above, but there is an intellisense error under 'Vehicles' where I try to use: @foreach (var Vehicle in Model.Vehicles) {


    :confused::confused::confused:


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    It is very obvious whats going on there. Both me and the other poster have tried to highlight it.

    If you can answer the following question you should begin to understand.

    What are you passing to the View and what data structure does the View expect?

    Because I can tell you that what is being passed and what is expected is not the same. A tip: get rid of the var keyword in your controller and replace it with the actual types. It should be more obvious what the problem is.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Aswerty wrote: »
    It is very obvious whats going on there. Both me and the other poster have tried to highlight it.

    If you can answer the following question you should begin to understand.

    What are you passing to the View and what data structure does the View expect?

    Because I can tell you that what is being passed and what is expected is not the same. A tip: get rid of the var keyword in your controller and replace it with the actual types. It should be more obvious what the problem is.

    I've used 'var' 3 times, should I be using 'model' or 'DBContext'?

    As far as I'm seeing, I'm trying to pass a model to my view via my controller? Or to take a longer view of it as I see it, I'm trying to use a DBContext, which in my head is an instance of the specific DB table I am trying to work with, to populate my model with data (which is several records of data in my DB), and then display this data in my view?


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Ok I think I'm slowly getting it now:

    MyMergedModelDBContext context = new MyMergedModelDBContext();

    for a start!


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    I've used 'var' 3 times, should I be using 'model' or 'DBContext'?

    As far as I'm seeing, I'm trying to pass a model to my view via my controller? Or to take a longer view of it as I see it, I'm trying to use a DBContext, which in my head is an instance of the specific DB table I am trying to work with, to populate my model with data (which is several records of data in my DB), and then display this data in my view?


    DbContext should not be thought of as representing a table, rather as representing a database, or set of tables.

    Aswerty is correct, earlier I was trying to suggest that you should just have one class deriving from DbContext, not one instance, or one per table / model.

    The main issue though, is the difference between what you are returning from your controller action and what the view is expecting.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    DbContext should not be thought of as representing a table, rather as representing a database, or set of tables.

    Aswerty is correct, earlier I was trying to suggest that you should just have one class deriving from DbContext, not one instance, or one per table / model.

    The main issue though, is the difference between what you are returning from your controller action and what the view is expecting.

    I'm really confused here. Earlier the advice was that I didn't need a DBContext in my MergedModel because I already had 2 collections set up in there, and to have a DBContext was saying I was trying to code for a set up where I had "collections of collections" (I'm calling these 2 collections my 2 child models, 'Vehicle' and 'Product' respectively...

    But now it seems I do need a DBContext, I thought I needed to get rid of my DBContext and just access the model from my controller, I'm really lost here to be honest, I've googled the absolute shít out of this problem and can't figure out why my solution isn't letting me use:

    @foreach (var Vehicle in Model.Vehicles) {}

    or

    @foreach (var Product in Model.Products) {}

    I'll have to be honest here lads, I'm not proficient enough with MVC to get the hints with where I'm going wrong here, I do obviously want to learn where I am fúcking up but I've googled the shít out of this before I asked for help on the forum and as far as I could see, this was a slight change to a solution I got help with before that has previously worked, so I can't work out where I'm going wrong here.


  • Advertisement
  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    I'm really confused here. Earlier the advice was that I didn't need a DBContext in my MergedModel because I already had 2 collections set up in there, and to have a DBContext was saying I was trying to code for a set up where I had "collections of collections" (I'm calling these 2 collections my 2 child models, 'Vehicle' and 'Product' respectively...

    But now it seems I do need a DBContext, I thought I needed to get rid of my DBContext and just access the model from my controller, I'm really lost here to be honest, I've googled the absolute shít out of this problem and can't figure out why my solution isn't letting me use:

    @foreach (var Vehicle in Model.Vehicles) {}

    or

    @foreach (var Product in Model.Products) {}

    I'll have to be honest here lads, I'm not proficient enough with MVC to get the hints with where I'm going wrong here, I do obviously want to learn where I am fúcking up but I've googled the shít out of this before I asked for help on the forum and as far as I could see, this was a slight change to a solution I got help with before that has previously worked, so I can't work out where I'm going wrong here.

    You need a DbContext in order to use Entity Framework to map database tables to model objects. Your model objects in this case are Vehicle and Product. You just need one DbContext in your app, which has DbSet properties for each model (table representation) you need to access.

    The reason you can't use model.Vehicles, is because as far as your view is concerned, your model does not contain a collection of vehicles. It contains an collection of MyMergedViewModel objects, which each contain a collection of vehicles and a collection of products.

    Have you tried changing the model declaration in the view to just MyMergedViewModel as I suggested ?


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    You need a DbContext in order to use Entity Framework to map database tables to model objects. Your model objects in this case are Vehicle and Product. You just need one DbContext in your app, which has DbSet properties for each model (table representation) you need to access.

    The reason you can't use model.Vehicles, is because as far as your view is concerned, your model does not contain a collection of vehicles. It contains an collection of MyMergedViewModel objects, which each contain a collection of vehicles and a collection of products.

    Have you tried changing the model declaration in the view to just MyMergedViewModel as I suggested ?

    Just trying this now, have to clear 24 errors first, I've completely lost the bit of a handle I previously had on this...


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    You need a DbContext in order to use Entity Framework to map database tables to model objects. Your model objects in this case are Vehicle and Product. You just need one DbContext in your app, which has DbSet properties for each model (table representation) you need to access.

    The reason you can't use model.Vehicles, is because as far as your view is concerned, your model does not contain a collection of vehicles. It contains an collection of MyMergedViewModel objects, which each contain a collection of vehicles and a collection of products.

    Have you tried changing the model declaration in the view to just MyMergedViewModel as I suggested ?

    I've tried using just MyMergedView instead but it still won't work. What I don't get is, in relation to what I've highlighted in bold above, I am returning a collection of MyMergedView model objects, of which there are 2, one being 'Vehicles' and one being 'Products'.

    So why can't I tell my View to go into whichever one of those objects I want to use, say for example 'Vehicles', when I use the code below???

    @foreach (var Vehicle in Model.Vehicles) {} ???

    Is this not what I am trying to do, am I not using the statement above to tell my view that I want to use the object 'Vehicles' and that I want to access the data in that object (data that is essentially mapped from my DB table???)


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    This actually doesn't have anything to do with MVC. I think you're misunderstanding how Collections work. Your controller is passing a single instance of the class MyMergedModel to the View. Your View expects an IEnumerable<MyMergedModel>.

    Just to help you understand (this isn't a solution), try:
    //changing this
    @foreach (var Product in Model.Products) {}
    
    //to this
    @foreach (var Product in Model.First().Products) {}
    

    This would cause the View to be correct but wouldn't solve the mismatch of the type being passed from the controller to the View. What would make the most sense is to change the model expected in the View.
    //from this
    @model IEnumerable<MyMergedModel>
    
    //to this
    @model MyMergedModel
    

    In this manner you can access the Enumerables in the MyMergedModel in the following way:
    @foreach (var Product in Model.Products) {}
    

    As far as I can see your controller is fine and your DbContext stuff is fine. Like counterpointaud states you are using an Enumerable that contains Enumerables and treating it like it is just one layer of Enumerables. This is the same as trying to use an array of arrays like it's just a basic array.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Aswerty wrote: »
    This actually doesn't have anything to do with MVC. I think you're misunderstanding how Collections work. Your controller is passing a single instance of the class MyMergedModel to the View. Your View expects an IEnumerable<MyMergedModel>.

    Just to help you understand (this isn't a solution), try:
    //changing this
    @foreach (var Product in Model.Products) {}
    
    //to this
    @foreach (var Product in Model.First().Products) {}
    
    This would cause the View to be correct but wouldn't solve the mismatch of the type being passed from the controller to the View. What would make the most sense is to change the model expected in the View.
    //from this
    @model IEnumerable<MyMergedModel>
    
    //to this
    @model MyMergedModel
    
    In this manner you can access the Enumerables in the MyMergedModel in the following way:
    @foreach (var Product in Model.Products) {}
    
    As far as I can see your controller is fine and your DbContext stuff is fine. Like counterpointaud states you are using an Enumerable that contains Enumerables and treating it like it is just one layer of Enumerables. This is the same as trying to use an array of arrays like it's just a basic array.

    I've tried everything you've suggested above at least 5 times now and it isn't working here for me. You've suggested I do this:
    //changing this
    [U][B][SIZE=4]@foreach (var Product in Model.Products) {}[/SIZE][/B][/U]
    
    //to this
    @foreach (var Product in Model.First().Products) {}
    
    This would cause the View to be correct but wouldn't solve the mismatch of the type being passed from the controller to the View. What would make the most sense is to change the model expected in the View.

    [code]

    My problem is that ^^^ will not work for me! I've taken out the IEnumerable at the top of my view, I've tried this several times now and it still won't work as you've suggested...


  • Registered Users, Registered Users 2 Posts: 586 ✭✭✭Aswerty


    I guess I've lost track of the changes that you've made so far.

    Have you tried throwing in a break point on:
    return View(model);
    

    and verified that the model contains the expected data and is not just null. Because if it is null that would bugger things up.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    This is the thing about MVC or EF or whatever it is I'm trying to work with here, it's hard to tell whether it is MVC or EF you are working with here. You google something you want to try and you find an answer and it says it's just 2 simple lines of code to work with 2 models in the same view and sure it's so easy, just continue along as you were.

    If I was still using webforms I'd just use 2 separate SQL connection strings and wire up my controls and I'd be sorted, there would be none of this crap where 3 days later, after just trying to take data from 2 different sources and stick them on a page/view you find yourself hopelessly bogged down in complex architectural considerations!

    //rant over!


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    I'm gonna post my latest code fresh here again:

    Before I do that, I just want to mention that now I can't use a foreach loop in my view because since I've taken out the

    @Model IENumerable<MYMVCSolution.models.MyMergedView>

    I am now getting an intellisense error under the word 'foreach' saying:

    forech statement cannot operate on variables on type MyMVCSolution.models.MyMergedView' because 'MyMVCSolution.models.MyMergedView' does not contain a public definition for 'GetEnumerator'


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    I'm gonna post my latest code fresh here again:

    Before I do that, I just want to mention that now I can't use a foreach loop in my view because since I've taken out the

    @Model IENumerable<MYMVCSolution.models.MyMergedView>

    I am now getting an intellisense error under the word 'foreach' saying:

    forech statement cannot operate on variables on type MyMVCSolution.models.MyMergedView' because 'MyMVCSolution.models.MyMergedView' does not contain a public definition for 'GetEnumerator'

    I guess you are trying to iterate over Model, instead of Model.Products or Model.Vehicles ?


  • Advertisement
  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    I think I have it sorted now lads, will have to take this home with me and get a bit of grub and then crank laptop back up and test the code, it's looking good now, I can finally use: @foreach(item in model.vehicles) {}

    This I wasn't able to do so far, I'll go get a bite and come back at this with a better fresher attitude after that!

    Huge thanks to the both of you for your advice with this, not to mention your patience!


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Had to build it one last time bfore I left work, no errors now and no data type mismatches or errors of that type, but the page is blank where I expected to see data as per my select query parameter in my controller. Will have a look at it later!


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Ok back at this this morning, last night I found that my model is null, there is no data in it?!? I put a breakpoint into my code at return View(model); and I can look in there and view the query but there is no data in there. I've no errors on build or no errors preventing the view from rendering but there is no data in my model as expected.

    Strangely, I have a similar view in my solution that accesses the data using just a single model, not the MergedModel that has been the subject of this thread, and the controller code is pretty much identical, this was covered in this thead: http://www.boards.ie/vbulletin/showt...p?t=2057380052

    And in that view, there are several rows of data being returned when I use that view.

    I've posted my controller code again for my MyMergedModel approach below:
                    string QueryStatus = "Query";
                    string StockID = "ProductID1";
                    var context = new MyMergedModelDBContext();
                    var vehicles = context.VehicleDBs.Where(v => v.Status == QueryStatus).AsEnumerable<Vehicle>();
                    var products = context.ProductDBs.Where(p => p.StockID == StockID).AsEnumerable<Product>();
    
                    MyMergedModel model = new MyMergedModel
                    {
    
                        Vehicles = vehicles,
                        Products = products
    
                    };
    
                    return View(model);
    

    My code for the single model which is returning the data I need is below:
                    string QueryStatus = "Query";
    
                    var context = new VehicleDBContext(); 
    
                    var query = context.VehicleDBs.Where(p => p.Status == QueryStatus);
    
                    if (!string.IsNullOrEmpty(QueryStatus))
                    {
                        //query = query.Where(p => p.Status == QueryStatus);
                        query = query.OrderByDescending(p => p.ID);
                    }
    
                    var results = query.ToList(); //this executes your query and retrieves the results into memory
    
                    return View(results);
                }
    


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    The difference is you are calling ToList() on the controller that is working.

    I think you are coming up against EF lazy-loading. The collection not built until it is needed. It thought that iterating over it would build it, but probably not if you are only iterating in the view. Try replacing the calls to AsEnumerable() with ToList().


  • Registered Users, Registered Users 2 Posts: 11,989 ✭✭✭✭Giblet


    You shouldn't pass any domain models to the view at all, Vehicles and Products need to have view models also.


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Giblet wrote: »
    You shouldn't pass any domain models to the view at all, Vehicles and Products need to have view models also.

    Might be good practice generally, but they don't need to have view models.


  • Advertisement
  • Registered Users, Registered Users 2 Posts: 11,989 ✭✭✭✭Giblet


    Might be good practice generally, but they don't need to have view models.

    Well you could get away with it, but you shouldn't really, the DBContext should be disposed by this point using an ActionFilter or similar or else you will expose it to view logic which is a bad bad idea.


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Giblet wrote: »
    Well you could get away with it, but you shouldn't really, the DBContext should be disposed by this point using an ActionFilter or similar or else you will expose it to view logic which is a bad bad idea.

    OK now I'm intrigued... how is DbContext exposed to the view ? Assuming your DbContext class is not contained in your domain model, which it shouldn't be anyway.


  • Registered Users, Registered Users 2 Posts: 11,989 ✭✭✭✭Giblet


    OK now I'm intrigued... how is DbContext exposed to the view ? Assuming your DbContext class is not contained in your domain model, which it shouldn't be anyway.

    EF uses dynamic proxying to do lazy loading, the properties to be lazy loaded are virtual and overridden by EF to add the call to the database (so effectively, your model is using the context). If your context is disposed, this won't work (as of course, lazy loading requires the context) Really it should be disposed at this point anyway. A unit of work should be as tight as possible, and handled by the framework. Having a primed lazy loaded property in the view leaves you vulnerable to select n+1 and horrible joins.


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Interesting, thanks. Sorry to go a little off-topic OP, although this is somewhat relevant. I had always kind of assumed the DbContext instantiated in the controller would be disposed when the method returned.


  • Registered Users, Registered Users 2 Posts: 11,989 ✭✭✭✭Giblet


    Interesting, thanks. Sorry to go a little off-topic OP, although this is somewhat relevant. I had always kind of assumed the DbContext instantiated in the controller would be disposed when the method returned.

    If you use a using declaration, it would be. Otherwise it would wait until the object was finalised (if you have overridden it to call dispose, or else it will never be called), which is done after garbage collection has occurred and is non deterministic.


  • Advertisement
  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    The difference is you are calling ToList() on the controller that is working.

    I think you are coming up against EF lazy-loading. The collection not built until it is needed. It thought that iterating over it would build it, but probably not if you are only iterating in the view. Try replacing the calls to AsEnumerable() with ToList().

    I've tried this advice but am still getting no data. I've put a breakpoint into my controller method at:

    return View(model);

    and when it breaks when I load the page, I can hover my mouse over the above line of code when it is still running in Visual Studio 2012, to see what is going on there and I can click down into what is in my model and it says

    Products: Count = 0
    Vehicles: Count = 0

    :confused::confused::confused:

    EDIT: I've also changed my controller code to:
    var vehicles = context.VehicleDBs.Where(v => v.Status == QueryStatus).ToList<Vehicle>();
    
    var products = context.ProductDBs.Where(p => p.StockID == StockID).ToList<Product>();
    


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    So I guess your .Where() filter is not returning any records. Maybe try with it removed ? i.e. context.VehicleDBs.ToList()


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    So I guess your .Where() filter is not returning any records. Maybe try with it removed ? i.e. context.VehicleDBs.ToList()

    Just tried that, still no result...


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Just tried that, still no result...

    Please ensure that there is data in your db tables, and that you are connecting to the correct db. If these look ok, post your DbContext code again.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Please ensure that there is data in your db tables, and that you are connecting to the correct db. If these look ok, post your DbContext code again.

    There is defo data in my table because my other view with the controller data I posted earlier (which is very similar to my the controller code for the current issue), is returning several rows of data as expected.

    This is my DBContext Code that is in my MergedModel
    namespace MyMVCSolution.Models
    {
    
        public class MyMergedModel
        {
    
            public IEnumerable<MyMVCSolution.Models.Vehicle> Vehicles { get; set; }
            public IEnumerable<MyMVCSolution.Models.Product> Products { get; set; }
    
        }
    
        public class MyMergedModelDBContext : DbContext
        {
    
            public DbSet<MyMVCSolution.Models.Vehicle> VehicleDBs { get; set; }
            public DbSet<MyMVCSolution.Models.Product> ProductDBs { get; set; }
    
        }
    
    
    }
    

    This is my model for the single view that IS successfully returning the data from my DB...
    namespace MyMVCSSolution.Models
    {
        public class Vehicle
        {
    
            public Int32 ID { get; set; }
    
            public string VehicleID { get; set; }
    
            public DateTime Timestamp { get; set; }
    
            public string VehicleMake { get; set; }
    
            public string VehicleModel { get; set; }
    
            public string Owner { get; set; }
    
            public string Status { get; set; }
    
        }
    
        public class VehicleDBContext : DbContext
        {
            public DbSet<Vehicle> VehicleDBs { get; set; }
    
        }
    

    The code extract above is in a file in my "Models" folder named Vehicle.cs


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Are these both two different projects? Please compare connection strings etc. and make sure you are connecting to the same db in both cases?


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Are these both two different projects? Please compare connection strings etc. and make sure you are connecting to the same db in both cases?

    Yup they are both in the same project/solution, the connection string is in the web.config file, I'm assuming that because it is connecting to the DB for my 'Vehicle.cs' model and bringing back & displaying rows of data, that it is good.

    Here is the view that is working & displaying data:
    @model IEnumerable<MyMVCSolution.Models.Vehicle>
    
    @{
        ViewBag.Title = "View My Queries";
        Layout = "~/Views/Shared/_Layout.MobileThemeroller.cshtml";
    }
    
    @using (Html.BeginForm("ViewMyQueries", "Home", FormMethod.Post)) 
    {
    <div ID="MyCollapsibleSet" data-role="collapsible-set" style="box-shadow:none;" data-iconshadow="false">
    @foreach (var item in Model) {
         
      
    <div data-role="collapsible" data-theme="d" data-content-theme="d" data-iconshadow="false" data-collapsed="true" data-mini="true" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d">  
        <h3>@Html.DisplayFor(modelItem => item.VehicleID)</h3/></div>@Html.DisplayFor(modelItem => item.VehicleMake) @Html.DisplayFor(modelItem => item.VehicleModel)
    

    This works fine, no problem returning data from DB, when I try to do it using a new model containing models 'Vehicle' and 'Product', no data?!?

    I'm obviously new at this but I can't figure out wtf I'm doing wrong here!

    I do want to access data from 2 completely separate tables though from the same view so I'm stuck with this, as I've learnt that I can't declare 2 separate models at the top of the same view unfortunately...


  • Registered Users, Registered Users 2 Posts: 403 ✭✭counterpointaud


    Can't see what is happening. Confirm that product and vehicle are empty lists (count == 0) for both queries. It seems that EF is successfully querying some db for you and returning 0 rows.

    My advice would be to create a new file for your DbContext, and put all model classes in there, stop creating more than one DbContext, and try again. You can just copy paste your VehicleDbContext class to a new file and add a DbSet for product. Delete the MergedModelDbContext, you don't need it.


  • Closed Accounts Posts: 1,143 ✭✭✭LordNorbury


    Can't see what is happening. Confirm that product and vehicle are empty lists (count == 0) for both queries. It seems that EF is successfully querying some db for you and returning 0 rows.

    My advice would be to create a new file for your DbContext, and put all model classes in there, stop creating more than one DbContext, and try again. You can just copy paste your VehicleDbContext class to a new file and add a DbSet for product. Delete the MergedModelDbContext, you don't need it.

    Huge thanks, this has sorted the problem, something with the DBContext in my new model (MergedModelDBContext), isn't workingcorrectly, I've resolved it using the DBContext in each individual model even though both models 'Vehicle' and 'Product' are still being called up via my new Mergedmodel which contains 'Vehicle' and 'Product'
                    var VehicleContext = new VehicleDBContext();
                    var ProductContext = new ProductDBContext();
                    var vehicles = VehicleContext.VehicleDBs.ToList<Vehicle>();
                    var products = ProductContext.ProductDBs.ToList<Product>();
    
                    MyMergedModel model = new MyMergedModel
                    {
    
                        Vehicles = vehicles,
                        Products = products
    
                    };
    
                  
                    return View(model);
    

    I wanted to use a single DB context to access both 'Vehicle' and 'Product' as I would have thought that this was better practice, given that my two models were now being accessed from a new model that merged both 'Vehicle' and 'Product' models. But at the same time I need a result so maybe this is what I'll have to use for the mo.

    Obviously my experience here is very weak, hence why I'm trying to understand new techniques and learn best practice, rather than having to fall back on a previous technique that I used, just to get a result on the day.

    Huge thanks again to folks on thread for helping me out of yet another MVC/EF cul de sac I have found myself stuck in!


Advertisement