Passing data in ASP.NET MVC using LINQ - nuttiness - c#

Allow me to start with: I am a n00b on ASP.NET MVC. I love it, but I am a n00b.
I am trying to pass "complex" data back from a LINQ query. I understand how to use the data context and then just cast that data when I send it back, but when I do a more complicated LINQ query which returns an anonymous type, things break down.
I saw someone ask a similar question (MVC LINQ to SQL Table Join Record Display), and the answer seemed to be to create a new data type to capture the data from the LINQ query. I don't get that I can create a var type in the controller, and access the member fields within the controller, but if I want to pass that var back to my View, I need to create an entire new class for that.
Here’s my Controller code:
var vrGoodResults1 = from records in db.Words
group records by records.word1 into g
select new
{
strWord = g.Key,
intWordCount = g.Count()
};
ViewData["GoodWords"] = vrGoodResults1;
return View();
And the View looks like this:
<% foreach (var kvp in (IEnumerable)ViewData["GoodWords"]) %>
<% { %>
<%= String.Format("{0} was used times", kvp) %> <br />
<% } %>
Which outputs:
{strWord = cool, intWordCount = 2 } was used times
{strWord = educated, intWordCount = 1 } was used times
{strWord = great, intWordCount = 1 } was used times
{strWord = smart, intWordCount = 6 } was used times
{strWord = strong, intWordCount = 2 } was used times
{strWord = super smart, intWordCount = 2 } was used times
So the data is getting to the View, but I cannot refer to the data by the field names I assigned in the LINQ query. When I try dumping kvp.GetType(), I get:
<>f__AnonymousType1`2[System.String,System.Int32]
All I want to do is something along the lines of:
<%= String.Format("{0} was used {1} times", kvp.strWord, kvp.intWordCount) %> <br />
But I am getting a compile error on the kvp.strWord.
error CS1061: 'object' does not contain a definition for 'strWord' and no extension method 'strWord' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
If I insert the following code into my Controller:
foreach (var kvp in vrGoodResults1)
{
string strNew = kvp.strWord;
}
I can reference the fields of my variable kvp without a compile error. So something is getting lost when passing from the Controller to the View. Am I forgetting to include something somewhere? Perhaps a “using” or in the “<# Page” directive, am I forgetting to inherit something?
When you are using LINQ for clear data contexts, you just set the IEnumerable<”datatype”> where “datatype” = your data context type, and you are all good. When you reduce your data set into something new in LINQ, I can't believe that the best answer is to create a new class so that I can use it in my View.

var is a compiler shortcut for declaring a type in the current scope. It doesn't add any dynamic functionality to the .NET runtime, so code outside the scope sees the object as "System.Object", since that is the most-specific type in the inheritance chain the view code is aware of.
You should create a real class if you want to pass tuple objects around; that's what you had to do before var, so it's not like you're losing anything by having to do it now :)

The best answer here simply is to create a new type. You can do get anonymous types back from object (see here) but it is ugly and brittle. Don't do it!
You could use reflection (kvp.GetType().GetProperty("strWord").GetValue(kvp, null)) - but that also isn't a great idea.
In this case - perhaps use the existing KeyValuePair<string,int> from the original select? Or your own Tuple<T1,T2>?

Don't be afraid of new types. They give lots of other benefits too especially when testing. Use the new property syntax and your class will only be as many lines long as you have properties. You can lose that awful Hungarian notation too. Int and str - yuk

Related

MVC - Dynamically bind database tables to var

I have inherited a badly designed database and have to convert the old code to MVC & EF.
I would like to be able to assign a database table name to a variable dynamically from within a SWITCH...CASE.
The table design is exactly the same, but there is a different table for different areas of the business!
How would I go about doing this? I am probably missing something extremely basic!
The code I currently have looks something like the following.
Declare the database
private CDBEntries db = new CDBEntries();
ActionResult Below
var cMembs = db.XXXcmembs;
switch (returnValue.ToUpper())
{
case "CRI":
cMembs = db.YYYsmembs;
break;
default:
break;
}
cSearchQuery = (from CCM in cMembs
join CC in cDB on CCM.name equals CC.cname into CGroup
from CC in CGroup.DefaultIfEmpty()
select new CSearch()
{
id = CCM.id,
Name = CCM.name,
Status = CCM.status,
cid = CC.id
});
There are more joins in the live code, but for simplicity I have reduced the code to its basics.
Don't think you will be able to use var like that. Var is strongly typed and declared at compile type.
var (C# reference)
An implicitly typed local variable is strongly typed just as if you had declared the type yourself, but the compiler determines the type
You might be able to use dynamic, which is inferred at run-time.
Walkthrough: Creating and Using Dynamic Objects
Dynamic objects expose members such as properties and methods at run time, instead of at compile time.
Let me know if you struggle, I will see if its possible from my side later today.
After further research I have realised that what I want to do goes against the reason for using EF (i.e. compile time validation).
As all of the tables have the same layout I am going to 'merge' them into 1 table with an extra column to specify the area.

How to put TempData Array in View

I'm trying to post links to View but I need help, first I created an string array for this.
string[] publicPath = new string[] { "/images/" + fileName };
TempData["link"] = publicPath;
ViewBag.link = TempData["link"];
After that this is my .cshtml;
#foreach (var item in ViewBag.link)
{
<textarea cols="102" rows="5" disabled="disabled" style="resize:none;">HELP!</textarea><br />
}
I have no problem putting the variable into TempData, but I could not understand how to use it in View. Thank you in advance, best regards ...
There's no info here about your actual issue, but I'd imagine it's down to working with dynamics. ViewBag is what's referred to as a dynamic. This allows you to just arbitrarily set a property like ViewBag.link, without having to explicitly define a property, but it also means that ViewBag.link doesn't have a known type. As such, you can't just use it in something like a foreach because you must provide an enumerable in that context, and the compiler has no idea if ViewBag.link is an enumerable type or not.
You're going to have to cast it, first, to use it.
#foreach (var item in (string[])ViewBag.link)
Be careful with this, though. Here, you know it's a string[], but if you change the type of what you're stuffing into ViewBag.link, this code here won't complain. It will blow up during runtime, of course, with an InvalidCastException, but you won't know that until it blows up. In general, you should avoid using dynamics, like ViewBag, for this reason. Instead, use a view model class and strongly type your view with that as its model. Then, you don't have to worry about casting, and you'll get all the standard intellisense and compile-time checking you'd expect.

DisplayTemplate being ignored (covarient interface?)

This is a weird one. I have the following view file (Views/Search/Submit.cshtml):
#model IEnumerable<KeyValuePair<string, ISearchProvider>>
#foreach (var provider in Model)
{
var results = provider.Value.Results.Take(10);
if (results.Count() > 0)
{
<text><li class="dropdown-header">#provider.Key</li></text>
#Html.DisplayFor(x => results)
}
}
... where results is a System.Collections.Generic.IEnumerable<out T>, and T is ISearchMatch.
I have then defined a display template in Views/Search/DisplayTemplates/SiteSearchMatch.cshtml;
#model SiteSearchMatch
<li>#Html.ActionLink(Model.Name, "details", "site", new { Id = Model.Id }, null)</li>
... and SiteSearchMatch implements ISearchMatch like so;
public class SiteSearchMatch: ISearchMatch
{
public int Id { get; set; }
public string Name { get; set; }
}
I'd expect that my display template gets used; but it doesn't. Instead, the output I see being output is;
<li class="dropdown-header">sites</li>
11147166811481897189813271028
... where that string of numbers is the combination of all the Ids of the ISearchMatch's I wanted to render via the display template.
It seems Razor is simply rendering the ISearchMatch using the first attribute defined in the class; if I remove the definition of the Id property, I instead see the combination of all the Name's of the ISearchMatch's.
Does anyone know why this is happening, and how I can get Razor to use the display template I've specified?
Your expectation is wrong:
I'd expect that my display template gets used; but it doesn't.
The output you see is the ID's simply listed. I suspect your ISearchMatch-interface does only expose the Id-property, but this does not matter. What matters is the actual type of the instance of the result. In your case the following line:
#Html.DisplayFor(x => results)
can be implicitly evaluated as
HtmlHelper<IEnumerable<KeyValuePair<string, ISearchProvider>>>
.DisplayFor<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>>
(Func<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>> expression);
Looks pretty complex, but basically it's just a implicit substitution of your model and expression result. Your model is of type IEnumerable<KeyValuePair<string, ISearchProvider>>. That's also the type for the input of your lampda-expression. The result is of type IEnumerable<ISiteMatch>. And here come's the important thing!
The DisplayFor implementation checks, if the result type is enumerable or not. If not, it searches for a fitting template for the type, otherwise it will iterate through the elements and does this for all elements. 1
Searching for a template works based on the type name. In your case the template uses the name of the enumerated type, which is ISearchMatch. It does not find any display template, so it simply dumps the properties, resulting in what you see:
11147166811481897189813271028
To fix this problem, you need to convert your result set to the correct type first. You can do this in different ways. Either you cast the whole result of your provider results:
var results = provider.Value.Results
.Cast<SiteSearchMatch>()
.Take(10);
or you cast them individually within your lamda expression:
#Html.DisplayFor(x => (SiteSearchMatch)results)
The important thing is, that the scalar result type is the same as the model in your display template.
1 Note that this is a little bit more complex, for example the the extension also keeps track of an index and applys it to the output, so that the model could be bound for postback purposes.
The lame answer is that the "Build Action" on my View file Views/Search/DisplayTemplates/SiteSearchMatch.cshtml was set to "None", rather than "Content".
This meant the code worked fine when running in Debug mode within Visual Studio, but didn't work when any deployment was made.
Just to reiterate; this fix required no code changes. Simply change the "Build Action" back to "Content".

razor template delegate, how the inside works?

I'm reading this excellent article regarding razor template delegate. http://www.prideparrot.com/blog/archive/2012/9/simplifying_html_generation_using_razor_templates
While I understand how it's used, as in
Func<dynamic, HelperResult> variable = #<var>#item.ProductName</var>
My question is, how exactly razor engine translated "#<var>#item.ProductName</var>" into a delegate in the background? As in
Func<dynamic, HelperResult> variable = delegate(dynamic x)
{
(what goes on in here?)
}
is #item a reserved keyword that razor parses out? Can it be used in any other convention? say #column or #row or any other ways?
Thanks a lot. Like I said, i'm more interested in how razor view engine translated the template statements into actual code in the background.
[Edit]. Thanks to Brad for pointing out Andrew's article. so above statement "#<var>#item</var>" will translate into
Func<dynamic, HelperResult> variable = delegate(dynamic item)
{
return new Microsoft.WebPages.Helpers.HelperResult(__writer => {
#__writer.Write(" ");
#__writer.Write("<var>");
#__writer.Write(item.ProductName); <--- what's happening here?
#__writer.Write("</var>");
}
So I see razor automatically parses out #<var> and </var> into separate strings and such, my question in regards to "item.ProductName" is..suppose "item" is a "Proudct" type, then is the following what razor is trying to do?
First, razor parses "#item.ProductName" separated by comma ".", get "item" and "ProductName".
Then because of the "dynamic" parameter, in the background, .NET will attempt to find the value of the property "ProductName" of the item "Product"?
Thanks
Never mind. I don't know why i didn't make the connection.
I asked another question in regards to DynamicObject DynamicObject? How does the following code work? and #Alxandr explained this regarding "dynamic". So essentially, it becomes
dynamic item = new Product(...);
String ProductName = item.ProductName;
So in essence, "dynamic" parameter in the background use CSharpGetMemberBinder and through reflection, figure out the "ProductName" of the object "Product".
razor template has a pretty brilliant design

DbSortClause expressions must have a type that is order comparable parameter Name :Key

I am using Linq to entity and have the following query
IQueryable<DomainModel.User> userResult =
userResult.OrderBy(u => u.UserClientRoles.OrderBy(r => r.Role.RoleName));
But I am getting this error
DbSortClause expressions must have a type that is order comparable
parameter Name :Key
and it returns an empty collection.
Any idea what's going on?
.OrderBy(), when working with databases, is supposed to take in a delegate that returns only a single property that represents a column in your database. I'm not sure what you're trying to do, but it looks like
u.UserClientRoles.OrderBy(r => r.Role.RoleName)
Will return an enumeration of values, which can't be ordered.
I had the same problem, I solved it using this:
your code:
IQueryable<DomainModel.User> userResult = userResult.OrderBy(u => u.UserClientRoles.OrderBy(r => r.Role.RoleName));
my code:
List<Membership> results = new List<Membership>();
results.AddRange(memberships.OrderBy(m => m.Roles));
memberships = results.AsQueryable();
coincidences:
*.OrderBy(m => m.Roles)
solution:
*.OrderBy(m => m.Roles.Select(r => r.RoleId).FirstOrDefault())
possible problem's reason:
Maybe, you did what I did, and cause that 1 user/member could have more than 1 role in the same membership. That made a conflict with/to OrderBy() because the application can just "order" a single element at the time, when she call the Role (which is an ICollection of elements) the instead receive more than 1 element with no kind of priority's levels (even when we could assume that the application will take the role's index as priority's base level, actually its don't).
solution's explaination:
When you add the *.Select(r => r.RoleId), you are specifying to the application which element will be used to OrderBy(). But, as you shall see when you maybe reached at this point, just by using the *.Select(r => r.RoleId) could be not enough, because the application is still receiving multiple results with the same priority's level. Adding *.Select(r => r.RoleId).FirstOrDefault() you are basically saying: "...I don't care how many results you received from that element, just the focus on the first result, or order them by its default..." (default normally means EMPTY or NULL).
additional information:
I used non-official's simple concepts/meanings to explain a complex solution with simple words, which means that you could maybe have problems to find similar posts in the web by using the words/concepts used in this "answer". Otherwise, the code itself works and you shouldn't not have any problem by applying it and/or modifying it by yourself. GOOD LUCK!!! (^_^)
In my case, I was accidentally trying to order by an object instead of ordering by one of it's properties.
You should you use
var query = from Foo in Bar
orderby Foo.PropertyName
select Foo;
Instead of
var query = from Foo in Bar
orderby Foo
select Foo;
Note: you will get the same behaviour event if there is an override on Foo's ToString() method.

Categories

Resources