So I got this crazy idea I could make make something cool work. I got tired of new selectlist(item, "blah", "blahblah") so I started writing an extension method (trying to get it more strongly typed) something like this ...
var selectList = projects.ToSelectList(p =>p.ProjectID, p =>p.ProjectName);
the extension method goes a little like this
public static SelectList ToSelectList<T>(this IEnumerable<T> item,
Expression<Func<T, string>> textName,
Expression<Func<T, string>> valueProperty)
{
//do cool stuff
return new SelectList(items, dataTextField, dataValueField);
}
What I need to get to is the reflection properties so I can grab the value and grab the name. Any ideas onhow I can do that? Any thoughts on doing this more better/easier? I've done this before but for the life of me I can't remember how I did it.
Edit this needed some clarification. I copied some code that was in-flight and not refined, so I've updated that code to reflect the more correct criteria.
I don't see why you have to use Reflection to do this.
public static SelectList ToSelectListItem<T>(this IEnumerable<T> items,
Func<T, string> textName, Func<T, string> valueProperty) {
{
return new SelectList(items
.Select(i => new SelectListItem {Text = textName(i), Value = valueProperty(i)}));
}
}
should would work, but I don't have the MVC dll on the current machine.
I originally tried your idea as well when I started with MVC but then I realized that some fields I wanted to format in a certain way and what about when an item is supposed to be selected by default. After handling all of that I realized it would be clearer to just write a one line LAMDA expression.
So now I usually just do a LAMDA expression to generate my SelectListItem collection.
<b>Screener: </b><%= Html.DropDownList("ScreenerOI", Model.Screeners.Select(p=>new SelectListItem() { Text = p.firstName + " " + p.lastName, Value = p.OI.ToString() })) %>
<b>Open Time: </b>
<%= Html.DropDownListFor(c => c.OpenTime, Model.HoursInDay.Select(p => new SelectListItem() { Text = p != null ? DateTime.Now.Date.Add(p.Value).ToString("h:mm tt") : "Clear Time", Value = p != null ? p.ToString() : "", Selected = Model.OpenTime == p }).ToList())%>
It seems like this is just as clear as to what is going on as what your idea is.
Another Example
<b>Screener Status: </b>
<%= Html.DropDownListFor(c => c.InfoStatusOI, Model.InfoStatuses.Select(p => new SelectListItem() { Text = p != null ? p.Status.ToString() : "", Value = p != null ? p.OI.ToString() : "", Selected = p != null && Model.InfoStatusOI == p.OI }).ToList())%>
This needs cleaned and tested for things like a zero count of the items, but here's the end result.
public static SelectList ToSelectList<T>(this IEnumerable<T> items, Expression<Func<T, object >> textName, Expression<Func<T, object >> valueProperty)
{
var dataTextField = textName.Body.ToString().Split('.')[1];
var dataValueField = valueProperty.Body.ToString().Split('.')[1];
return new SelectList(items, dataTextField, dataValueField);
}
Related
I have one SelectListItem for DropDownList. I have to filter based on some condition. If I try adding the condition then its gives me an error like this (LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression). I ll be adding that code here. Please guide me to solve this.
Code
IEnumerable<SelectListItem> IssueId = (from txt in Db.Issues where txt.BibId == BibId
select new SelectListItem()
{
Text = txt.Description,
Value = txt.Id.ToString(),
Selected = true,
});
SelectList IssueIds = new SelectList(IssueId, "Value", "Text");
ViewBag.IssueId = IssueIds;
Thanks
Try this:
LINQ2EF does not know ToString() but after AsEnumerable() you'll get a local collection when ToString() is implemented.
IEnumerable<SelectListItem> IssueId =
(from txt in Db.Issues.Where(e => e.BibId == BibId).AsEnumerable()
select new SelectListItem()
{
Text = txt.Description,
Value = txt.Id.ToString(),
Selected = true
});
Linq To Sql can't generate TSQL for txt.Id.ToString()
You will need to iterate the result instead after executing the query, or cast to Enumerable as xeondev suggests.
That extension does not seem to be sorted by linq to Entities but you could just do the mapping once you have the issues, e.g.
var issues = (from issue in Db.Issues
where issue .BibId == BibId
select issue ).ToList();
IEnumerable<SelectListItem> IssueId = (from txt in issues
where txt.BibId == BibId
select new SelectListItem()
{
Text = txt.Description,
Value = txt.Id.ToString(),
Selected = true,
});
I have a list of type stored procedure which have an ID and a Name as data in it.
i have property of int type in model and a list of same stored procedure.
now i want to bind this information into ListBoxFor
in view i have written this
#Html.ListBoxFor(x => x.HobbyId, new MultiSelectList(Model.listHobby, "pkHobbyId", "Hobby"))
but i am getting an error
The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed.
Please Help how to bind.
try that
#Html.ListBoxFor(x => x.HobbyId, Model.listHobby.Select(f => new SelectListItem { Text = f.Hobby, Value = f.pkHobbyId.ToString() }), new { Multiple = "multiple" })
listHobby is iEnumerable list on my sample
sorry if i mislead you, rushed to answer but you cannot get the result of the multiselect listbox into a guid or int variable (whatever type is your HoobyId is) you should have an array to grab the result like
public string[] SelectedHobbyIds { get; set; }
so there must be something wrong with your View Models so its better that u would post your view models to be checked
# Chhatrapati Sharma,
In your controller, try this,
ViewData['anyName'] = new SelectList {
Text = , // text from ur function
Value = , // Value from function
Selected = // if required
}
and in view, bind the viewdata like,
<#Html.ListBox("docImages", ((IEnumerable<SelectListItem>)ViewData["anyName"]))
For testing, try a sample selectlist item as follows,
ViewData['anyName'] = new List<SelectListItem>{
new SelectListItem {Text = "First", Value = "0"},
new SelectListItem {Text = "Second"), Value = "1"},
new SelectListItem {Text = "Third", Value = "2"}
};
If this sample works, then check your function "_supp.listDocImages()" and make sure it return IList
#Html.ListBoxFor(x => x.HobbyId, Model.listHobby.Select(f => new SelectListItem { Text = f.Hobby, Value = f.pkHobbyId.ToString() }), new { Multiple = "multiple" })
HobbyId in expression must be ienumerable because you set multi select
I am trying to work with MvcContrib Grid control. But I cannot seem to get the sorting to work on complex objects that hold other objects.
I have setup my controller/classes/Views similar to the OP in this question.
Sorting with MVCContrib
I have tried to use the SortColumnName to my childobject.property but it gives me an error saying My main object does not have this property. This is my code snippet
//POCO class
class Issue {
public int ID {get; get; }
.....
public int priorityId {get; set;}
public virtual Priority priority {get; set;}
}
//Controller code
public ViewResult Index(int? pageNo, GridSortOptions sort)
{
var issues = db.issues.Include(i => i.priority);
ViewBag.sort = sort;
if (!string.IsNullOrEmpty(sort.Column))
{
issues = issues.OrderBy(sort.Column, sort.Direction);
}
return View(issues.ToList().AsPagination(pageNo ?? 1, 10));
}
//View code for the Grid
#Html.Grid(Model).Sort(ViewBag.sort as GridSortOptions).Columns(column => {
column.For(issue => Html.ActionLink(" ", "Edit", new { id = issue.ID, areas = "Issues", controller = "Main"}, new { #id="editBtn"})).Named("Edit");
column.For(issue => Html.ActionLink(issue.ID.ToString(), "Edit", new {id = issue.ID, areas = "Issues", controller = "Main"})).Named("ID").Sortable(true);
column.For(issue => issue.priority.codeDesc).Named("Priority").SortColumnName("priority.codeDesc").Sortable(true);
}).Empty("No data found")
When I try to sort on the priority string, it gives me an error saying 'priority.codeDesc is not a property of Issue'.
TIA
The issue here isn't actually related to the grid, but rather to the .OrderBy extension method provided as part of the MvcContrib sorting extensions. This extension is fairly simplistic and I only wrote it to cover simple cases where you want to sort on a direct property of the object, however in your case you're trying to order on a nested property ("priority.codeDesc") which isn't supported - you can't use dot notation with this extension.
You'd either need to switch to using a different mechanism to perform the actual sorting, or if this is a one-off situation then you could hard-code the sorting logic for this particular column (not ideal, but if it's a one off then it's simpler than writing a new sorting mechanism), eg:
if (!string.IsNullOrEmpty(sort.Column))
{
if(sort.Column == "priority.codeDesc")
{
issues = issues.OrderBy(x => x.priority.codeDesc);
}
else
{
issues = issues.OrderBy(sort.Column, sort.Direction);
}
}
OMG! Dots!
I was in the same boat but thanks God I found a brilliant solution posted by our fellow developer Jarrett Meyer. I found it after maybe 3 hours Googling in the past and just now when I decided to boost my pagination and sorting with MvcContrib Grid.
You can find the full post here:
Server-Side Sorting With Dynamic LINQ
His code saved me... :D The use of LINQ's Aggregate function was AWESOME! Kudozzz to him.
I had to change Jarretts' original code a little bit to fit it to my needs. Here's the code after I modified it:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, GridSortOptions sortOptions)
{
if (string.IsNullOrEmpty(sortOptions.Column))
{
return collection;
}
Type collectionType = typeof(T);
ParameterExpression parameterExpression = Expression.Parameter(collectionType, "p");
Expression seedExpression = parameterExpression;
Expression aggregateExpression = sortOptions.Column.Split('.').Aggregate(seedExpression, Expression.Property);
MemberExpression memberExpression = aggregateExpression as MemberExpression;
if (memberExpression == null)
{
throw new NullReferenceException(string.Format("Unable to cast Member Expression for given path: {0}.", sortOptions.Column));
}
LambdaExpression orderByExp = Expression.Lambda(memberExpression, parameterExpression);
const string orderBy = "OrderBy";
const string orderByDesc = "OrderByDescending";
Type childPropertyType = ((PropertyInfo)(memberExpression.Member)).PropertyType;
string methodToInvoke = sortOptions.Direction == MvcContrib.Sorting.SortDirection.Ascending ? orderBy : orderByDesc;
var orderByCall = Expression.Call(typeof(Queryable), methodToInvoke, new[] { collectionType, childPropertyType }, collection.Expression, Expression.Quote(orderByExp));
return collection.Provider.CreateQuery<T>(orderByCall);
}
Now you can call this extension method like this in your controller method:
var users = Database.Memberships.OrderBy(sort);
where sort is GridSortOptions that lives in MvcContrib.UI.Grid.
sort.ColumnName can contain strings like these ones now:
User.UserName
User.MyRelatedEntity.RelatedEntityProperty
User.MyRelatedEntity.RelatedEntityProperty.AndSoON
Note that when you create your Grid columns you can specify
.SortColumnName("User.UserName")
I'm having some issues setting a generic list property of a POCO object when from an EF context. For instance I have a very simple object that contains the following:
public class foo
{
public string fullName;
public Entity entity;
public List<SalesEvent> eventList;
}
My code to populate this object from looks something like this:
.Select(x => new foo()
{
fullName = x.vchFirstName + " " + x.vchLastName,
entity = new EntityVo()
{
address1 = x.vchAddress1,
entityId = x.iEntityId,
emailAddress = x.vchEmailAddress,
firstName = x.vchFirstName,
lastName = x.vchLastName,
city = x.vchCity,
state = x.chState,
workNumber = x.vchWorkNumber,
mobileNumber = x.vchMobileNumber,
siteId = x.iSiteId
}
eventList = _context.Events
.Where(e => e.iEntityId == x.iEntityId
&& e.iStatusId >= eventStatusMin
&& e.iStatusId <= eventStatusMax)
.Select(e => new List<SalesEventMatchVo>
{
new SalesEventMatchVo()
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
}
}).FirstOrDefault()
}).ToArray();
This issue I'm having is that I'm unable to populate the eventList property with all of the events, it's only grabbing the first record(which makes sense looking at the code). I just cant seem to figure out to populate a the entire list.
Is there a reason simply removing the FirstOrDefault at the end isn't the solution here? I feel like I might be misunderstanding something.
EDIT:
I think I see what you are trying to do. The issue is that you are creating a list in the select statement, when the select statement works only over one thing at a time. It is basically mapping an input type to a new output type.
Try something like this instead:
eventList = _context.Events.Where(e => e.iEntityId == x.iEntityId && //FILTER EVENTS
e.iStatusId >= eventStatusMin &&
e.iStatusId <= eventStatusMax)
.Select(e => new SalesEventMatchVo() //MAP TO SALESEVENT
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
})
.ToList() //CONVERT TO LIST
As a side note, unless you actually need a List for some reason, I would store foo.eventList as IEnumerable<SalesEvent> instead. This allows you to skip the List conversion at the end, and in some scenarios enables neat tricks like delayed and/or partial execution.
Also, I'm not sure what the point of your .Select(q=>q) statements are in several lines of the SalesEventMatchVo initializer, but I'm pretty sure you can chop them out. If nothing else, you should Select after Where, as Where can reduce the work performed by all following statements.
I have a IEnumerable<T> collection with Name, FullName and Address.
The Address looks like this:
Street1=54, Street2=redfer street, Pin=324234
Street1=54, Street2=fdgdgdfg street, Pin=45654
Street1=55, Street2=tryry street, Pin=65464
I want to loop through this collection and print only those Names, FullNames whose Street1=54
How can i do it in LINQ?
Ok I was able to do this to extract Street1 of the Address
coll.Address.Split(",".ToCharArray())[0]returns me Street1=54 .
Now how do I add this to the condition and print only those Name, FullName whose Street1=54
Based on your update, you can adapt Jared Par's code this way:
var result = collection.Where(x => x.Address.Contains("Street1=54"));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
If you want to be able to plug in your Street1 value with a variable, then do this:
var street1 = "54";
var result = collection.Where(x => x.Address.Contains("Street1=" + street1 ));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
BTW, you really should update your question or add a comment to a specific answer rather than adding a new answer that isn't.
Try this
var result = collection.Where(x => x.Address.Street1==54);
foreach ( var cur in result ) {
Console.WriteLine(var.Name);
}
Select the correct list of :
IList<T> matches = myListOfEnumerables.Where(m => m.Street1 == 54).ToList();
Then loop and print.
Actually the record looks like this:
{Name="Jan" FullName="Kathy Jan" Address="Street1=54, Street2=redfer street, Pin=324234"}
I have to loop through this collection and print only those Names, FullNames whose Street1=54
If the updated information is accurate, you should change the way you store the data.
It looks like you've packed the address information into a string. Why not store it as an object. In fact, why not just as more fields in the same object as the Name and FullName? (and why duplicate the first-name information?)
public class Person
{
public string FirstName, LastName, Street1, Street2, Pin;
}
IEnumerable<Person> persons = GetAllPersonsSomehow();
foreach (Person person in persons.Where(p => p.Street1 == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
Assuming you have to keep the address information in a string, you need a parser for it.
public static IDictionary<string, string> GetAddressFields(string address)
{
return address.Split(',').ToDictionary(
s => s.Substring(0, s.IndexOf('=')).Trim(),
s => s.Substring(s.IndexOf('=') + 1).Trim());
}
foreach (Person person in persons.Where(p =>
GetAddressFields(p.Address)["Street1"] == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
So, what you could do is that you could write a generator for parsing the Address field and then enumerating properties of that. This is a fairly common thing in the functional programming world.
To be fair you would want this code to be lazy in that it would only compute a minimal set. I'm gonna suggest some code from the BCL but you can (and probably should) rewrite the same helper methods with generators.
public static IEnumerable<KeyValuePair<string,string>> NameValueSplit( this string s )
{
foreach (var x in s.Split(','))
{
var y = x.Split(new char[] { '=' }, 2, StringSplitOptions.None);
yield return new KeyValuePair<string, string>(y[0].TrimStart(), y[1].TrimEnd());
}
}
With that helper function you can write code like this
var result = collection.Where(x => x.Address
.NameValueSplit().Any(x => x.Key == "Street1" && x.Value == "54"));
foreach ( var item in result )
{
Console.WriteLine(item.Name);
}
Now this code will not run on your SQL Server if you were thinking of that, but you could write a WHERE clause where you would search the Address field for a sub string %Street1=54%. I like lazy evaluation for string operations and think that's a lacking feature in the BCL. That is why I suggested that kind of solution.