Link to home
Start Free TrialLog in
Avatar of akohan
akohan

asked on

How to type cast in MVC 4?

This is the case I'm facing:

I do have 3 controllers as following: a) MetricController b) CategoryController c) NavController

Before I go further, I better to share that I have a simple layout on View (HTML page) which consists of a column (on left) representing categories and on right portion of page I have to show a set of information. The information has to change base on selected category (report type) on left column.

MetricController is responsible to retrieve some information from a table (Metrics table) and it is something:



   public class Metric  {   //just a class for IIS log
        public int Id { get; set; }
        public string date { get; set; }
        public string time { get; set; }
        public string c_ip { get; set; }
        public string cs_username { get; set; }
        public string s_sitename { get; set; }
        public string s_computername { get; set; }
        public string s_ip { get; set; }
        AND MORE...

    }

Open in new window



 
public class MetricController : Controller {
        private IMetricRepository repository;

        public MetricController(IMetricRepository metricRepository) {
            this.repository = metricRepository;
        }

        public ViewResult List() {

            MetricsListViewModel model = new MetricsListViewModel {
                Metrics = repository.Metrics
                .OrderBy(p => p.Id)   //just few columns for now
            };

            return View(model);
        }
    }

Open in new window



NavController is responsible for navigation and showing categories (retrieved from its table) in left column as following:

public class NavController : Controller  {

        private ICategoryRepository repository;
        public NavController(ICategoryRepository repo) {
            repository = repo;
        }

        public PartialViewResult Menu() {
            IEnumerable<string> categories = repository.Categories
                                                .Where(x => x.Visible == "1" /*|| x.Visible == "0"*/)
                                                .Select(x => x.CategoryName)
                                                .Distinct()
                                                .OrderBy(x => x);

            return PartialView(categories);
        }

Open in new window


Now, I have an HTML page which shows off a left column populated with categories (or let's say report names in the left column) and need to setup and run a query every time user clicks on one of a report/category name.

One thing I know (feel free to correct me if I'm wrong) is that I have to work on CategoryController but using a code as following ends up to getting a type casting error:

public class CategoryController : Controller   {
        private IMetricRepository repository;

        public CategoryController(IMetricRepository metricRepository) {
            repository = metricRepository;
        }

        public ViewResult List(string category)
        {

            MetricsListViewModel viewModel = new MetricsListViewModel {
                Metrics = repository.Metrics
                    .Select(p => p.cs_username)  <<<<<<<<  cannot convert 
            };
        }
    }

Open in new window


The error is:

Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)

This is my first MVC application and I feel like I'm missing something. I could use a constant list of report and use

@Html.ActionLink(GenerateReport1, action ,Controllername)
@Html.ActionLink(GenerateReport2, action ,Controllername)
@Html.ActionLink(GenerateReport3, action,Controllername)
but need to load the report names from a table. Any help is appreciated.

Regards,
Amit
Avatar of kaufmed
kaufmed
Flag of United States of America image

Please provide the definition for the MetricsListViewModel class.
Avatar of akohan
akohan

ASKER

Oh sorry! I missed that part ... Thanks for reminding it goes as:


 
   public class MetricsListViewModel  {

        public IEnumerable<Metric> Metrics { get; set; }
        public PagingInfo PagingInfo { get; set; }
        public string CurrentCategory { get; set; }
    }
}

Open in new window

The cs_username property in your last snippet of the OP is of type string. Your problem is that Select will return IEnumerable<string>, but you are trying to assign that result to a property of type IEnumerable<Metric>. This does not work. You need to modify your Select so that it returns the correct type.
Avatar of akohan

ASKER

OK, so what do you suggest? which part has to be modified to avoid this type cast error?
I would think that you don't even need the Select:

e.g.

MetricsListViewModel viewModel = new MetricsListViewModel
{
    Metrics = repository.Metrics
};

Open in new window


...but that would depend on how repository.Metrics is defined. You may still need the Select, but with a tweak:

MetricsListViewModel viewModel = new MetricsListViewModel
{
    Metrics = repository.Metrics.Select(p => new Metric() { cs_username =  p.cs_username });
};

Open in new window

Avatar of akohan

ASKER

1)
I guess, something is not right. please see the attached picture.
I am new to MVC but I am expecting this to return a variable from ViewResult type and pass it onto a view.


User generated image
Avatar of akohan

ASKER

One question to add here: if there is no need to use a SELECT or no linq involved then how can I retrieve the data from table?
1. Change the ending semi-colon ( ; ) to a comma ( , )
2. It looks like you are using Entity Framework. Without going into too much detail, the repository.Metrics effectively is your table. When ASP.NET gets to a point in the page life cycle where it actually needs to spit out the data, then it will make the appropriate calls out to the database just by virtue of having set the code up as I mentioned above.

Having said that, I seem to recall that you cannot pass the "table" back to the view in the fashion I outlined above. If you run into any issues with the above code, then try adding in a ToList call at the end.

e.g.

Metrics = repository.Metrics.Select(p => new Metric() { cs_username =  p.cs_username }).ToList();

Open in new window

Avatar of akohan

ASKER

Thanks. yes, I'm using EF 5.0 in MVC 4. I followed the steps and it runs but then in runtime I get this error

The entity or complex type 'NameSpace.Domain.Concrete.Metric' cannot be constructed in a LINQ to Entities query.
Avatar of akohan

ASKER

just to have it reviewed by you. I'm getting error on this line 26:

Line 24:         {
Line 25:
Line 26:             MetricsListViewModel viewModel = new MetricsListViewModel
Line 27:             {
Line 28:                 Metrics = repository.Metrics.Select(p => new Metric() { cs_username = p.cs_username}).ToList(),
OK, my fault. (We don't get to use EF at work, so I've only played with it a handful of times.) From what I see on the web, you cannot create instances of entities that exist for the purpose of modeling your database. You would need to create an additional class that has the same structure, and then Select into that. This means you would have to change your view model to use the new class as well.

Let me see if I can get someone with a little bit more EF experience to pop in.
Avatar of akohan

ASKER

Thank you so much! I'm new to EF and also to MVC so have to confess totally feel hopeless.
However, just to make sure I am following you right: so I have to make a new class and replace the View Model? or should I change Metric class instead?

I truly appreciate your concern and help OK I will wait for your inputs.

Regards.
Amit
I sent a message to a guy who I know is knowledgeable about EF stuff. I'm not sure when he'll see it, but I'm sure he'll drop buy when he gets it  = )
Avatar of akohan

ASKER

Thank you Kaufmed!
Hi akohan;

If I understand what is going on here in this line of code,

Metrics = repository.Metrics.Select(p => new Metric() { cs_username =  p.cs_username }).ToList();

Open in new window

Metric() is a class that was created by Entity Framework to map to the database table Metrics. Entity Framework will not allow you to create new instance objects of the same object type that it is returning from the query. You can return the complete object or create a Data Transfer Object, DTO, and fill only those fields that are needed. There is a third way and that is to return an Anonymous type from the query but in a case like this I would not suggest that. The DTO class does not need to have all the same fields as the Metric class only those fields that are needed or you can have all if you want that. And as Kaufmed has stated, "you would have to change your view model to use the new class as well."
Avatar of akohan

ASKER

Hello Fernando,

I truly appreciate you and Kaufmed for the care and time you both took to address this issue! OK, now let me bug you with this. This is the MetricsListViewModel class that you both said it should be changed:

public class MetricsListViewModel  {

        public IEnumerable<Metric> Metrics { get; set; }
        public string CurrentCategory { get; set; }
    }

Open in new window


As you see, I have IEnumerable<Metric> defined in there. Also, my model is Metric as following:
  public class Metric  { // information from IIS log - FYI
        public int Id { get; set; }
        public string date { get; set; }
        public string time { get; set; }
        public string c_ip { get; set; }
        public string cs_username { get; set; }
        public string s_sitename { get; set; }
        public string s_computername { get; set; }
        public string s_ip { get; set; }

Open in new window



I'm assuming from "changing" you mean I have to get rid of IEnumerable<Metric>. Right? if so, what it should be changed to?

In existing code in linq,  compiler is trying to type case IEnumerable<string> to IEnumerable<Metric>. Right?

Thank you,
Amit
Hi Amit;

In the Model when you/EDMX designer created the classes to map to the database, for the table Metrics the designer created a class called Metric and I am assuming that the class you posted in your last post is that same class Metric. If I am wrong about that please let me know because the following information is based on that assumption. The reason you were getting the following error, "The entity or complex type 'NameSpace.Domain.Concrete.Metric' cannot be constructed in a LINQ to Entities query.", is that EF was returning a Metric object and you in your query were asking it to create an object of the same type Metric and assign to one of its field the value from the Metric the query had already created. This is not allowed.

So let me ask the question what do you want this query to have accomplished for you?:
Metrics = repository.Metrics.Select(p => new Metric() { cs_username =  p.cs_username }).ToList();

Open in new window


To your question in your last post, "I'm assuming from "changing" you mean I have to get rid of IEnumerable<Metric>. Right?", yes. And to this part of the question, "if so, what it should be changed to?", It should become a collection of object created by the query. But to give a more complete answer I need to understand what the query is returning.
Avatar of akohan

ASKER

Thank you again!

As you advised let me share few things to make sure we both are on the same page:

I did not use EDMX designer, I manually created a Metric class and after getting to  "Server Explorer" in VS 2012, I was able to see the table structure table which was made/generated by  EF 5.0.0.0

In response to your question I should Yes, the class is Metric.cs I already posted it here.

Now, regarding Linq code that you addressed here: that was given to me by Kaufmed as he was helping me with the problem.

What am I trying to do? it is simple.

I have two tables one called Metric which contains few columns (imported from IIS log file e.g. client IP address, Server IP Address and etc) and one is called Category which in fact is a list of Report names in it e.g.  as following:

    public class Category {
        public int CategoryId { get; set; }
        public string CategoryName { get; set; }  // in fact, it should be ReportName
        public string Visible { get; set; }  // to hide/visible a report
    }

Open in new window


When user clicks on a category (or report name ), I need to run a specific query or linq script to generate a report so I did make a controller as:

public class CategoryController : Controller   {

        private IMetricRepository repository;

        public CategoryController(IMetricRepository metricRepository) {
            repository = metricRepository;
        }

        public ViewResult List(string category)   {
             
            IF category is "TOP 10 VISITED PAGES" THEN RUN ITS QUERY OR LINQ CODE
            IF category is "LIST OF UNIQUE VISITORS" THEN RUN ITS QUERY OR LINQ CODE

        }
}

As you notice, it is a simple application and I just need to have code choose and call a query base on selected item or report item from a menu (left column menu on page).  Yes, I can create a static menu in View as:


@Html.ActionLink("New vs Returning", "GetNewVsRetruningUsers", "Metric")
@Html.ActionLink("Total Visits", "GetTop10Page", "Metric")
@Html.ActionLink("Browser", "GetBrowserTypeUsed", "Metric")

Open in new window


but like to be more flexible and also I'm still learning MVC. Please ask me more questions if it is not clear.
Once again I appreciate your help.

Thanks
AK
Is it possible for you to zip up the complete project with a test database so I can run and test on my system?
Avatar of akohan

ASKER

Sure, why not?!!
That's so nice of you. How may I send it over?
Hi akohan;

Well you can't sent it through EE email messaging I got 24 emails with the same message but no files. The first way is to zip the complete project and test database into one zip file and use the attach file link below the "Post a Comment" text box as shown in the image below. The next way if for some reason it does not allow you is to use the EE-Stuff web site which has  the same username and password as here. Once you login to EE-Stuff click on Expert Area Tab at the top left of the page. Then on the next page click on the "Upload a new file" link and follow the instructions on the page. If all else fails you can post to a web site such as Microsoft OneDrive but only if all else fails. For the second and third options please post the link to the downloads so we can download them.
Avatar of akohan

ASKER

Hello Fernando,

I hope all is well. I'm kinda surprised that how you have received 24 emails from me!!! I sent you one to share my email.
No idea why this happened!

OK sure, I will email you the zip file just one question. Where does the database is made by EF? it is not in under the project folder.


Any idea?

Thanks,
Akohan
OK, if your using localdb for development then look here, On my system I found them at the following location.

C:\Users\<USER NAME>\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\Projects

If you can not find them there look in the web.config file under the connectionstring node look at the property AttachDbFileName = " PATH TO LOCATION OF DB ", It could also be in the  App_Data directory.

If you are not using localdb please tell me which SQL Server you are using.
Avatar of akohan

ASKER

Thank you so much! I'll be by my computer tonight and will follow the instructions.

Regards,
Akohan
Avatar of akohan

ASKER

Hello,

No it is not under the path you addressed but also I looked at web.config yet I don't see the property "AttachDbFileName ".  

[i] <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=Analytics;Integrated Security=True" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings>[/i]

Open in new window


Any advice? meanwhile, I will be looking for more information on how to find the DB. This EF doesn't make me feel so comfortable first , I'm new to it and when things get automated nobody exactly knows what is happening in the background.




Thanks.
Avatar of akohan

ASKER

Ok found it! it was hidden.
Hi akohan;

Try this path to see if the database file Analytics is there.

C:\Program Files\Microsoft SQL Server\110\LocalDB\Binn

If it is note and you have a 64 bit machine then also try this location.

C:\Program Files (x86)\Microsoft SQL Server\110\LocalDB\Binn

If that also gives no joy try a file system search for the file Analytics.*
Were did you find it so I will know in the future.
Avatar of akohan

ASKER

No, you are right but I guess it is a networking thing.
My username is akohan but I had to look at akohan.companyName folder instead.

I did a search for the DB name and set the Windows Explorer to show hidden files and it was under C:\Users\MyUsernameDot.CompnayName

So nothing new. I had to pay more attention!

I'm uploading them on server then will email it to your EE contact email.

Would that be OK?
I have explained in this post ID: 40280074 how to up load. I do not think EE mail will do it.
Avatar of akohan

ASKER

I read the other day and today Monday forgot about it!
Here is the URL

please let me know if it works or not.

Regards,
Ak
Hi Akohan;

Have downloaded the two zip files and will be looking at them soon.
Avatar of akohan

ASKER

Thank you!!!
The afa.zip file seems to be corrupted with an unknown error. Can you re-zip the project and try again. The DB.zip file came down with no errors and I unzip'ed it without issue.
Avatar of akohan

ASKER

Sure, this is the URL for new zip file, not sure why my upload is super slow!
URL

URL
I get it and was able to un-zip it fine.
Avatar of akohan

ASKER

Great!

Thanks for confirm it.
OK, so lets get on the same page now that I have something to look at.

I see that you have comment out the original EF query we were working on. So what is the current issue you need help with?
Avatar of akohan

ASKER

OK, let's us forget to the statement we had and commented out.
my question is:

How can I click on one of report names on left column and run a specific query?

And see the result on page?

Thanks
Avatar of akohan

ASKER

In fact, I am thinking of something like case statement so that each case will contain or run a unique query to get specific results per report.
When kaufmed asked me to take a look at that this question it was for the Entity Framework part. As I told him I do not know MVC well enough to help with that. I sent him a email to have him help with this part.
Avatar of akohan

ASKER

That is fine I appreciate your concern and help.
The problem is that I'm new to both MVC and EF :)

See, as I said I was trying to invoke a specific linq query base on selected report (listed on left column) which get both MVC and EF involved. OK, I will wait for Kaufmed but if you don't mind I might ask questions related to EF if your time allows.

Once again, I appreciate all your help!

Regards,
ak
Hi akohan;

In you original question you were trying to display cs_username on the page. Here is a way that can be done. Once those changes are made you can click on Unique Users on the left of the screen and the middle should change.

// In this class CategoryController change this method as shown below.

public ViewResult List(string category)
{
    List<String> viewModel = new List<String>();
    viewModel.AddRange(repository.Metrics.Select(u => u.cs_username).ToList<String>());
    
    return( viewModel );
}


// In the view for the above method found in the View folder for Category in List.cshtml make these chanages.

@model List<String>           

<h2>List of Reports</h2>

@{
    ViewBag.Title = "Airforce Tracer";
}

@foreach (var p in Model) { 
    <div id="item">
        <h3>@p</h3>
    </div>
}

Open in new window

I will stick around until the end to help in any way I can. I will also be learning somethings with this question.
Avatar of akohan

ASKER

Thanks. yes, it looks a right way of doing but here is the situation:

This left menu is populated by a controller called NavController so my assumption is we have to call in there, however, when I do so it doesn't like it and throws error as:

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[System.String]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Afa.Domain.Entities.Category]'.

Either I have to change the design/structure of the application or there is a way to do so.

Just as reminder:

MetricController handles the data item
CategoryController handles the name of categories or reports.
NavController handles the navigation so maybe I have to get rid of it?

Thanks.
Is the project still available? The link doesn't show anything for me.
Avatar of akohan

ASKER

oh sure I will enable it again.
give me few minutes

Thanks.
Avatar of akohan

ASKER

Hi again Kaufmed

Please try this URL

Please confirm it it working for you and when you are done downloading.
I grabbed it.
Avatar of akohan

ASKER

Great! thanks to you both for helping!!!
ASKER CERTIFIED SOLUTION
Avatar of kaufmed
kaufmed
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of akohan

ASKER

Thank you dear Kaufmed,

One question only ... the controller you were talking about in your example, is it a new one or you used an existing one?
if existing one then which one?

Regards,
ak
The Metric controller. You can tell in the URL:

User generated image
Avatar of akohan

ASKER

oh yes!!! got it this time.
Avatar of akohan

ASKER

One question to add here. Currently you have one column displayed in the report or query and its object name is usernames right?
now if I want to have multiple columns to show on report, I think I don't need to modify the Model. Just have to access to all other columns using the same usernames.ColumnName.

Right?
I'll expand the example when I get home this afternoon. The short answer is no.
Avatar of akohan

ASKER

Thank you! please take your time.
My apologies for the delay. I hit the pillow when I got home, and apparently went in to hibernation mode  = )

So, let's extend the example to add in additional information. Recall above that we said we would have a 2nd class that basically mimics the EF entity. We have that above, but now we need to add in the additional columns that we care about. Let's add in IP address and site name. Previously, I made the Usernames property very simple:  it only contains a "list" of strings that represent the usernames of people in the system. But we now want to add in more information about each user. At this point, though, we wouldn't want to just add in more properties to the view model. Rather, we want to modify the Usernames property to reflect that we have additional user information, not just usernames. (I'll explain why in at the end.) So, let's add an additional class that represents user information:

namespace Airforcea.WebUI.Models
{
    public class VisitorModel
    {
        public string Username { get; set; }
        public string IpAddress { get; set; }
        public string SiteName { get; set; }
    }
}

Open in new window


This class captures all of the "user" information for an individual user. Then, since our view model will contain information about all users that have visited us, we tweak that class:

using System.Collections.Generic;

namespace Airforcea.WebUI.Models
{
    public class UniqueVisitorsViewModel
    {
        public IEnumerable<VisitorModel> Visitors { get; set; }
    }
}

Open in new window


We change the Usernames property to a name more fitting, and we change its type from IEnumerable<string> to IEnumerable<VisitorModel>. We next have to change the query to populate this new class. We'll need to modify the Select to create new instances of the VisitorModel class which each contain those bits of information that we care about from the query (i.e. the properties we put into VisitorModel). So:

public ViewResult UniqueVisitors()
{
    UniqueVisitorsViewModel viewModel = new UniqueVisitorsViewModel();

    viewModel.Visitors = this.repository.Metrics.Select(metric => new VisitorModel() { IpAddress = metric.c_ip, SiteName = metric.s_sitename, Username = metric.cs_username })
                                                .Distinct()
                                                .ToList();

    return View(viewModel);
}

Open in new window


Notice that in a LINQ query a Select can basically create anything you like. Here, I create instances of the VisitorModel, whereas previously I was selecting just strings. All of the remaining code in the controller is the same. Now we can modify the view to show this additional information. Previously we had:

@model Airforcea.WebUI.Models.UniqueVisitorsViewModel

@{
    ViewBag.Title = "UniqueVisitors";
}

<h2>UniqueVisitors</h2>
@if (Model.Usernames.Any())
{
    <ul>
        @foreach (var username in Model.Usernames)
        {
            <li>@username</li>
        }
    </ul>
}
else
{
    <text>No users found.</text>
}

Open in new window


...but we no longer have a Usernames property--we now have Visitors. Let's change the view to dump out all of the information that we Selected in the query:

@model Airforcea.WebUI.Models.UniqueVisitorsViewModel

@{
    ViewBag.Title = "UniqueVisitors";
}

<h2>UniqueVisitors</h2>
@if (Model.Visitors.Any())
{
    <ul style="float: left;">
        @foreach (var visitor in Model.Visitors)
        {
            <li>@visitor.Username</li>
        }
    </ul>
    <ul style="float: left;">
        @foreach (var visitor in Model.Visitors)
        {
            <li>@visitor.IpAddress</li>
        }
    </ul>
    <ul style="float: left;">
        @foreach (var visitor in Model.Visitors)
        {
            <li>@visitor.SiteName</li>
        }
    </ul>
}
else
{
    <text>No users found.</text>
}

Open in new window


Here I've added in additional <ul> tags, one for each property of the Visitor that I care about. (I've floated them left just so that each <ul> will be next to each other horizontally rather than vertically.) The results of all of these changes are:

User generated image
Now, as to why I created an additional class for the visitor information. Your view model class is a class that encompasses all of the data that your view should display. In this simple example, we only had visitor information, but what if we'd also had information about the individual sites that we'd also like displayed? Your view model functions kind of like a container for all of the data items you'd like to show. If each data item has specific elements to it, then it often times makes sense to create a class that represents the data item, and include the specifics in that class. That is what I did with the VisitorModel:  that class captures the specifics about each visitor that I have. Should I want to display site information also, then I would simply create a SiteModel class that represents the specifics about a site. I would then include a Sites property on my view model class to contain all of the sites that I pulled from the database.
Avatar of akohan

ASKER

LOL. I'm glad you did that ... I did the same thing the night before and crashed big time yet my toddler woke me up by 4:30 am and had to entertain him.
Thank you
I will get back to you soon.  I wish I could assign more than 500 score to this thread!
Avatar of akohan

ASKER

Hello Kaufmed,

Sorry for the delay been busy and today I'm getting back to the code again. I just want to thank you for solving the issue and also so much thanks to Fernando for his help and compiling my problem back to you.

I hope I will be in touch with you both and thanks to you both for sharing your knowledge.

Regards,
Ak
Avatar of akohan

ASKER

Cannot thank you enough for solving this!!!