1) I've a Product table with 4 columns: ProductID, Name, Category, and Price. Here's the regular linq to query this table.
public ActionResult Index()
{
private ProductDataContext db = new ProductDataContext();
var products = from p in db.Products
where p.Category == "Soccer"
select new ProductInfo { Name = p.Name, Price = p.Price}
return View(products);
}
Where ProductInfo is just a class that contains 2 properties (Name and Price). The Index page Inherits ViewPage - IEnumerable - ProductInfo. Everything works fine.
2) To dynamicaly execute the above query, I do this:
Public ActionResult Index()
{
var products =
db.Products
.Where("Category = \"Soccer\"")
.Select(/* WHAT SOULD I WRITE HERE TO SELECT NAME & PRICE?*/)
return View(products);
}
I'm using both 'System.Lind.Dynamic' namespace and the DynamicLibrary.cs (downloaded from ScottGu blog).
Here are my questions:
What expression do I use to select only Name and Price?
(Most importantly) How do I retrieve the data in my view? (i.e. What type the ViewPage inherits? ProductInfo?)
===================
EDIT
When I write .Select("new(Name, Price)"), I'm able to pass an object to the ViewData's Model property. Unfortunately, in order to use the Viewdata object, I'm asked to cast the Viewdata to a type. But, I do not know how to determine the type to do the casting.
====================
EDIT
Instead of the ViewData's Model property, I'm using simply the ViewData["products"]. To retrieve the content, I just place a IEnumerable cast before the ViewData, like this:
<% foreach(var item in (IEnumerable)ViewData["products"]){%>
<p><% = Html.Encode(item)%><p>
<%}%>
There are 2 situations:
1) If I select only one column (for instance, Name), everything work fine.
2) If I select more than 1 more columns (Name, Price), I get something like this
{Name=Soccer, Price=19.50}
{Name=Shin Pads, Price=11.59}
Why I just don't get something like
Soccer, 19.50
Shin Pads, 11.59
=================================
EDIT April 02 - 05h47 AM
I've define the GetPropertyValue Method (as your response suggets) as static in a static Class that I called 'HelperClass'. Now, this is the way I try to access the value of Name from my object.
<% = Html.Encode(HelperClass.GetPropertyValue(ViewData["product"], "Name")) %>
I get the following Exception:"Object reference not set to an instance of an object". And, the following line from the inside GetPropertyValue() his highlight.
Line 22: return propInfo.GetValue(obj, null);
Do I need to use new keyword? (where?)
Thanks for helping
Private Sub filter()
Dim coll = db.Products.Where(Function(x) x.Category.Equals("Soccer")) _
.Select(Function(x) GetObject(x.Name, x.Price))
End Sub
Private Function GetObject(ByVal name As String, ByVal price As String) As Object
Return new ProductInfo(name, price)
End Function
1) To generate a new projection type at runtime you can:
.Select("new(Name, Price)")
2) To read values from the object, you need to use reflection:
string name = GetPropertyValue(someObject, "Name");
...
public static object GetPropertyValue(object obj, string propName)
{
System.Reflection.PropertyInfo propInfo = obj.GetType().GetProperty(propName);
return propInfo.GetValue(obj, null);
}
Related
I want to return just a value to ViewBag with the LINQ query below
var myCompanyName = (from c in db.Companies
where c.CompanyId == CompanyID
select c.CompanyName).ToString();
ViewBag.myCompanyName = myCompanyName;
In my MVC controller but I get the following output in my View.
SELECT [Extent1].[CompanyName] AS [CompanyName] FROM [dbo].[Companies] AS [Extent1] WHERE [Extent1].[CompanyId] = #p__linq__0 Company Name from Controller Action
The result ViewBag expected from view bag should be something like: CompanyName XYZ. Please how do I make this right?
Your not materializing your query, and I suspect you want to return a single value (not IEnumerable<string>) so you need to replace .ToString() with .FirstOrDefault()
var myCompanyName = (from c in db.Companies
where c.CompanyId == CompanyID
select c.CompanyName).FirstOrDefault();
ViewBag.myCompanyName = myCompanyName;
or if you did want a collection of string, then it would be .ToList()
I cant seem to figure this out, I'm not that new to the MVC model but perhaps my brain is just tired.
View Model
Public Class CategoriesViewModel
Public Property Categories As List(Of cihCategoryOrgDef)
Public Property Category As cihCategoryOrgDef
Public Property SelectedItem As String
Public Sub New(organizationId As Guid, codId As Guid)
Categories = lists.organizationClubCategories
Category = From x In Categories
Where x.codId = codId
Select x
End Sub
End Class
CategoriesController
Function Edit(codId As Guid) As ActionResult
Dim model As CategoriesViewModel = New CategoriesViewModel(ZenCommon.CurrentOrgId, codId)
Return View(model)
End Function
When I run this, I get a "invalid cast exception" on the Category = From x.... line
Unable to cast object of type 'WhereSelectListIterator`2[Library.cihCategoryOrgDef,Library.cihCategoryOrgDef]' to type 'Library.cihCategoryOrgDef'.
I'm trying to get a single Category to use in my View. So I can have an edit page for that specific category. Am I going about this all wrong?
Since Category is a single category, you need to make sure your LINQ returns a single result. If you're sure that the query will only return one value, use SingleOrDefault:
Category = (From x In Categories
Where x.codId = codId
Select x).SingleOrDefault()
If your query could return more than one result and you only want to take the first result, use FirstOrDefault:
Category = (From x In Categories
Where x.codId = codId
Select x).FirstOrDefault()
Your select actually returns a collection, that's why you get invalid cast exception. To get only one item you need something like this (sorry, I'm not very much familiar with vb, so here is c# code):
Category = (From x In Categories
Where x.codId = codId
Select x).DefaultIfEmpty(null).FirstOrDefault();
This question already has answers here:
Working with C# Anonymous Types
(8 answers)
Closed 8 years ago.
I am using ASP.NET mvc 5. I have one class that holds all the LINQ which can access to another class. now i convert the LINQ query to list variable Query and returning as IList... the i create object of this class--> call the method and get result.
now i can see in debugging object names but i can't see in foreach loop. my list hold mix data types, plus result is merging from different tables...
public IList GetAllFeeZonesForFeeSchemeByID(int FeeSchemeID)
{
using (var db = new QualificationContext())
{
var Query = from a in db.FeeScheme
join b in db.FeeZoneSchema.Where(c => c.FeeSchemeID == 1) on a.FeeSchemeID equals b.FeeSchemeID
join c in db.FeeZone on b.FeeZoneID equals c.FeeZoneID
select new
{
FeeScheme = a.FeeSchemeID,
FeeZone = b.FeeZoneID,
FeeZone_Description = c.FeeZoneDescription
};
return Query.ToList();
}
}
in controller class...
foreach(var item in obj1.GetAllFeeZonesForFeeSchemeByID(1))
{
item.???? (can't access the object here....
}
many thanks
You should return Generic IList of concrete (not anonymous class):
public IList<FeeSchemeModel> GetAllFeeZonesForFeeSchemeByID(int FeeSchemeID)
{
using (var db = new QualificationContext())
{
var Query = from a in db.FeeScheme
join b in db.FeeZoneSchema.Where(c => c.FeeSchemeID == 1) on a.FeeSchemeID equals b.FeeSchemeID
join c in db.FeeZone on b.FeeZoneID equals c.FeeZoneID
select new FeeSchemeModel
{
FeeScheme = a.FeeSchemeID,
FeeZone = b.FeeZoneID,
FeeZone_Description = c.FeeZoneDescription
};
return Query.ToList();
}
}
public class FeeSchemeModel
{
public int FeeScheme{get;set;}
public int FeeZone{get;set;}
public string FeeZone_Description{get;set;}
};
But I recommend to use IEnumerable<T> instead of IList<T> and use ToArray() method instead of ToList() method if you don't use special features of List<T> (such as method Add())
IList is non-generic interface, it contains only non-generic IEnumerable definition, which enumerates objects. So type of item will be object. That's why you can see only members of System.Object class.
You should either cast item to appropriate type or use generic collection parametrized with appropriate type. But you can't use neither of these approaches while you are using anonymous objects, because you don't know anonymous type name. So, you need to create some class which you will be able to cast to:
foreach(Foo item in obj1.GetAllFeeZonesForFeeSchemeByID(1))
Or use as parameter of method return type:
public IList<Foo> GetAllFeeZonesForFeeSchemeByID(int FeeSchemeID)
One more option is usage of dynamic type, which will resolve operations on object at runtime. You still will not be able to use IntelliSense but your code will work:
foreach(dynamic item in obj1.GetAllFeeZonesForFeeSchemeByID(1))
{
// use item.FeeScheme
}
have looked at Phil Haacks project on books at
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
which has been useful, but I have a mix of data types.
I use a modelview so that i can have a mix of objects, in this case: Order (ie order.id, order.date etc), Customer, SoilSamplingOrder and a list of SoilSamplingSubJobs which is like this [0].id, [0].field, [1].id, [1].field etc
Perhaps I should be using ICollection instead of List? I had problems getting UpdateModel to work so I used an extract from collection method. the first 4 method calls : orderRepository.FindOrder(id); etc give the model the original to be edited. but after this point i'm a little lost in how to update the subjobs. I hope i have delineated enough to make sense of the problem.
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Order order = orderRepository.FindOrder(id);
Customer cust = orderRepository.FindCustomer(order.customer_id);
IList<SoilSamplingSubJob> sssj = orderRepository.FindSubOrders(id);
SoilSamplingOrder sso = orderRepository.FindSoilSampleOrder(id);
try
{
UpdateModel(order, collection.ToValueProvider());
UpdateModel(cust, collection.ToValueProvider());
UpdateModel(sso, collection.ToValueProvider());
IList<SoilSamplingSubJob> sssjs = orderRepository.extractSSSJ(collection);
foreach (var sj in sssjs)
UpdateModel(sso, collection.ToValueProvider());
orderRepository.Save();
return RedirectToAction("Details", new { id=order.order_id});
}
catch
{
return View();
}
}
I think you should work on developing a view model that reflects the data that you need to get back and create display/edit templates for that model that renders the view model using Phil Haack's methods for your lists of objects -- in this case, arrays of submodel classes. Let the model binding framework build the returned model (as a parameter) to your action, then reconstitute your domain models from the view model data. Brad Wilson has an excellent series of articles on templating that should be helpful.
I use IModelBinder on my complex classes. You don't need IModelBinder, but it will make your controller post codeblock look much cleaner. I'm using VB at the moment, but my class looks something like this for example:
Public Class CombinedRulesAndXmlRules : Implements IModelBinder
Public Rules As New Rules()
Public XmlRules As New XmlRules()
Public RequiredTemplates As New List(Of RequiredTemplates)
Public SearchCriteria As New List(Of SearchCriteriaList)
Public OptionalTemplates As New List(Of OptionalTemplates)
Public Questions As New List(Of Questions)
Public QATemplates As New List(Of QATemplates)
**Public Answers As New List(Of Answers)**
Now I don't use editor templates in my views, so to have your lists appear in the formcollection you have to add something like this in your view:
#For x As Integer = 0 To Model.Answers.Count - 1
Dim incr As Integer = x
#Html.HiddenFor(Function(model) model.Answers(incr).Answer)
#Html.HiddenFor(Function(model) model.Answers(incr).AnswerId)
#Html.HiddenFor(Function(model) model.Answers(incr).AnswerTemplateTag)
#Html.HiddenFor(Function(model) model.Answers(incr).Tag)
Next
When the view is submitted/posted, the model binder takes over before hitting the first line of code in your mvc post controller method. I then iterate through the actual formcollection and strip out the [#] using regex, because your formcollection will show your list items like this: Answers[0].Answer, Answers[0]AnswerId ,etc.:
For x As Integer = 1 To request.Form.Count - 1
keyname = request.Form.Keys(x)
Debug.Write(keyname)
val = request.Form(x).ToString()
'If keyname contains [#] strip it. it's a list item.
Dim pattern As String = "\[(\d+)\]"
Dim iterpattern As String = "\d+"
Dim rgx As New Regex(pattern)
Dim rgxiter As New Regex(iterpattern)
If Regex.IsMatch(keyname, pattern) Then
Dim match As Match = rgxiter.Match(keyname)
ListIteration = CInt(match.Value)
Dim result As String = rgx.Replace(keyname, "")
keyname = result
End If
The Select Case codeblock is next. So you already know you have a strong typed class in your model, so your select can look like this:
Select Case keyname
Case "Answers.Answer"
'add code here to add to your return list. What you
'get in the post controller is a fully populated class.
I am using MVC RC2.
I have Two tables
1)Product (PID, PName, CIDfk);
2)Category(CID, CName);
So i query like these
var Product = from p in dc.Product
from C in dc.Category
where p.CIDfk == c.CID
select new { ProductName = p.PName, ProductCategory = c.CName };
return view();
where dc is database context of LINQ-to-SQL class (.dbml);
How do i display in view? where i pass Product? (in viewdata or in 'return view()')
Please help me out...
You can both use:
- ViewData["MyName"] = product.SingleOrDefault();
This way from the view you'd do:
<% Product p = (Product)ViewData(p) %>
or populate the model:
ViewData.Model = product.SingleOrDefault();
This way from the view you'd do:
<%Product p = ViewData.Model%> //in case of a Strongly typed view
<%Product p = (Product)ViewData.Model%> //otherwise
After populating either ViewData or the Model you can call:
return View();
Another approach is calling the View overload that accepts the model as parameter, as tvanfosson said.
You want to have a strongly typed view and pass the product as the view model
var product = from p in dc.Product
from C in dc.Category
where p.CIDfk == c.CID
select p;
return View( product );
where your view is of type ViewPage<Product>.