How to write generic IEnumerable<SelectListItem> extension method - c#

I'm fairly new (ok, REALLy new) to generics but I love the idea of them. I am going to be having a few drop-down lists on a view and I'd like a generic way to take a list of objects and convert it to a list of SelectListItems
What I have now:
public static IEnumerable<SelectListItem> ToSelectListItems(
this IEnumerable<SpecificObject> items, long selectedId)
{
return
items.OrderBy(item => item.Name)
.Select(item =>
new SelectListItem
{
Selected = (item.Id == selectedId),
Text = item.Name,
Value = item.Id.ToString()
});
}
Trouble is, I'd need to repeat that code for each drop-down as the objects have different fields that represent the Text property of the SelectListItem
Here is what I'd like to accomplish:
public static IEnumerable<SelectListItem> ToSelectListItem<T>(this IEnumerable<T> items, string key, string value, int SelectedId) {
// I really have no idea how to proceed from here :(
}

Well, you could do something like this:
public static IEnumerable<SelectListItem> ToSelectListItems(
this IEnumerable<T> items,
Func<T,string> nameSelector,
Func<T,string> valueSelector,
Func<T,bool> selected)
{
return items.OrderBy(item => nameSelector(item))
.Select(item =>
new SelectListItem
{
Selected = selected(item),
Text = nameSelector(item),
Value = valueSelector(item)
});
}

You could pass in delegates to do the comparisons, and property retrieval. Something like this:
public static IEnumerable<SelectListItem> ToSelectListItem<T>(this IEnumerable<T> items,
int selectedId, Func<T,int> getId, Func<T, string> getName,
Func<T, string> getText, Func<T, string> getValue)
{
return
items.OrderBy(item => getName(item))
.Select(item =>
new SelectListItem
{
Selected = (getId(item) == selectedId),
Text = getText(item),
Value = getValue(item)
});
}
Then you would use it like so:
var selected = specificObjects.ToSelectListItem(10, s => s.Id, s=> s.Name, s => s.Name, s => s.Id.ToString());

In order for this to work as written, your type T will need to implement some interface which provides Name and Id properties:
public interface ISelectable
{
string Name { get; }
int Id { get; }
}
With this in place, you can do:
public static IEnumerable<SelectListItem> ToSelectListItems<T>(this IEnumerable<T> items, long selectedId)
where T : ISelectable
{
return
items.OrderBy(item => item.Name)
.Select(item =>
new SelectListItem
{
Selected = (item.Id == selectedId),
Text = item.Name,
Value = item.Id.ToString()
});
}
This is required in order to use the Name and Id properties within your extension method... You could, instead, provide a different means of receiving these (ie: passing delegates), but that may or may not increase the maintenance cost in your scenario.

//You need to use reflection to get the value of
public static IEnumerable<SelectListItem> GetListItems<T>
(this IEnumerable<T> items, int selectedVal)
{
return from item in items
select new SelectListItem
{
Text =item.GetPropValue("Name"),
Value = item.GetPropValue("Id"),
Selected =
item.GetPropValue("Id").Equals(selectedVal.ToString())
};
}
public static string GetPropValue<T> (this T item,string refName)
{
return
item.GetType().GetProperty(refName).GetValue(item).ToString();
}

Related

Pass expression to initializer

I would like to pass an expression that represents a variable to used when instantiating an object.
Instead of:
class MyObject : IMyInterface { ... }
var list = db.MyObjects.Where(x => !x.IsDeleted).ToList();
var anotherList = list.Select(x => new AnotherObject() {
Id = x.Id,
Value = x.Value
});
I would like to make this so that a list of objects of IMyInterface can be transformed into another type of list (AnotherObject as example) using defined expressions as so:
var list = db.MyObjects
.Where(x => !x.IsDeleted)
.ToAnotherObjectList(x => x.Id, x => x.Value);
...
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<IMyInterface> list,
Expression id,
Expression value)
{
return list.Select(x => new AnotherObject() { Id = id, Value = value }).ToList();
}
I'm not sure how to accomplish this. I know I can use reflection to create objects and set properties by a string but I'm not sure how to pass expressions.
UPDATE
Well, I thought I'd have to do some reflection but it's simpler than what I was thinking. Here's my solution that works in IRL.
public static IEnumerable<AnotherObject> ToAnotherObject<T>(this IEnumerable<T> list, Func<T, int> getId, Func<T, string> getValue, Func<T, bool> getSelected = null) where T : IMyInterface
{
return list.Select(x => new AnotherObject {
Display = getValue(x),
Id = getId(x),
Selected = getSelected != null && getSelected(x),
});
}
You could use a Func<TInput,TReturn> for that. For example:
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<T> list,
Func<T, int> getId,
Func<T, object> getValue)
{
return list.Select(x => new AnotherObject() { Id = getId(x), Value = getValue(x) }).ToList();
}
Call:
list.ToAnotherObjectList(i => i.Id, i=> i.Value);
In this example I used Funcs with one parameter (of type T) and return type int/object.

LINQ select property by name [duplicate]

This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I'm attempting to use a variable inside of a LINQ select statement.
Here is an example of what I'm doing now.
using System;
using System.Collections.Generic;
using System.Linq;
using Faker;
namespace ConsoleTesting
{
internal class Program
{
private static void Main(string[] args)
{
List<Person> listOfPersons = new List<Person>
{
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person()
};
var firstNames = Person.GetListOfAFirstNames(listOfPersons);
foreach (var item in listOfPersons)
{
Console.WriteLine(item);
}
Console.WriteLine();
Console.ReadKey();
}
public class Person
{
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person()
{
FirstName = NameFaker.Name();
LastName = NameFaker.LastName();
City = LocationFaker.City();
CountryName = LocationFaker.Country();
}
public static List<string> GetListOfAFirstNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCities(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCountries(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfLastNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
}
}
}
I have a Some very not DRY code with the GetListOf... Methods
i feel like i should be able to do something like this
public static List<string> GetListOfProperty(
IEnumerable<Person> listOfPersons, string property)
{
return listOfPersons.Select(x =>x.property).Distinct().OrderBy(x=> x).ToList();
}
but that is not vaild code. I think the key Might Relate to Creating a Func
if That is the answer how do I do that?
Here is a second attempt using refelection But this is also a no go.
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Person person = new Person();
Type t = person.GetType();
PropertyInfo prop = t.GetProperty(property);
return listOfPersons.Select(prop).Distinct().OrderBy(x =>
x).ToList();
}
I think the refection might be a DeadEnd/red herring but i thought i would show my work anyway.
Note Sample Code is simplified in reality this is used to populate a datalist via AJAX to Create an autocomplete experience. That object has 20+ properties and I can complete by writing 20+ methods but I feel there should be a DRY way to complete this. Also making this one method also would clean up my controller action a bunch also.
Question:
Given the first section of code is there a way to abstract those similar methods into a single method buy passing some object into the select Statement???
Thank you for your time.
You would have to build the select
.Select(x =>x.property).
by hand. Fortunately, it isn't a tricky one since you expect it to always be the same type (string), so:
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, property);
var lambda = Expression.Lambda<Func<Person,string>>(body, x);
Then the Select above becomes:
.Select(lambda).
(for LINQ based on IQueryable<T>) or
.Select(lambda.Compile()).
(for LINQ based on IEnumerable<T>).
Note that anything you can do to cache the final form by property would be good.
From your examples, I think what you want is this:
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (string)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
typeof is a built-in operator in C# that you can "pass" the name of a type to and it will return the corresponding instance of Type. It works at compile-time, not runtime, so it doesn't work like normal functions.
PropertyInfo has a GetValue method that takes an object parameter. The object is which instance of the type to get the property value from. If you are trying to target a static property, use null for that parameter.
GetValue returns an object, which you must cast to the actual type.
person => (string)prop.GetValue(person) is a lamba expression that has a signature like this:
string Foo(Person person) { ... }
If you want this to work with any type of property, make it generic instead of hardcoding string.
public static List<T> GetListOfProperty<T>(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (T)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
I would stay away from reflection and hard coded strings where possible...
How about defining an extension method that accepts a function selector of T, so that you can handle other types beside string properties
public static List<T> Query<T>(this IEnumerable<Person> instance, Func<Person, T> selector)
{
return instance
.Select(selector)
.Distinct()
.OrderBy(x => x)
.ToList();
}
and imagine that you have a person class that has an id property of type int besides those you already expose
public class Person
{
public int Id { get; set; }
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
all you need to do is fetch the results with type safe lambda selectors
var ids = listOfPersons.Query(p => p.Id);
var firstNames = listOfPersons.Query(p => p.FirstName);
var lastNames = listOfPersons.Query(p => p.LastName);
var cityNames = listOfPersons.Query(p => p.City);
var countryNames = listOfPersons.Query(p => p.CountryName);
Edit
As it seems you really need hardcoded strings as the property inputs, how about leaving out some dynamism and use a bit of determinism
public static List<string> Query(this IEnumerable<Person> instance, string property)
{
switch (property)
{
case "ids": return instance.Query(p => p.Id.ToString());
case "firstName": return instance.Query(p => p.FirstName);
case "lastName": return instance.Query(p => p.LastName);
case "countryName": return instance.Query(p => p.CountryName);
case "cityName": return instance.Query(p => p.City);
default: throw new Exception($"{property} is not supported");
}
}
and access the desired results as such
var cityNames = listOfPersons.Query("cityName");
You should be able to do it with Reflection. I use it something similar.
Just change your reflection try to this:
public static List<string> GetListOfValues(IEnumerable<Person> listOfPersons, string propertyName)
{
var ret = new List<string>();
PropertyInfo prop = typeof(Person).GetProperty(propertyName);
if (prop != null)
ret = listOfPersons.Select(p => prop.GetValue(p).ToString()).Distinct().OrderBy(x => x).ToList();
return ret;
}
I hope it helps.
It's based on C# 6
You can also use this. works for me.
public static class ObjectReflectionExtensions
{
public static object GetValueByName<T>(this T thisObject, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
return prop.GetValue(thisObject);
}
}
And call like this.
public static List<string> GetListOfProperty(IEnumerable<Person> listOfPersons, string propertyName)
{
return listOfPersons.Select(x =>(string)x.GetValueByName(propertyName)).Distinct().OrderBy(x=> x).ToList();
}
If you want to select all the values:
object[] foos = objects.Select(o => o.GetType().GetProperty("PropertyName").GetValue(o)).ToArray();

How to replace an item in an IEnumerable<T>?

Currently, to replace an item I'm using the following code:
var oldItem = myList.Single(x => x.Id == newItem.Id);
var pos = myList.ToList().IndexOf(oldItem);
myList.Remove(oldItem);
myList.ToList().Insert(pos, newItem);
So, I created an extension to do the same as above but using lambda expression, and I came up with this:
public static ICollection<T> Replace<T>(this IEnumerable<T> list, T oldValue, T newValue) where T : class
{
if (list == null)
throw new ArgumentNullException(nameof(list));
return list.Select(x => list.ToList().IndexOf(oldValue) != -1 ? newValue : x).ToList();
}
So I can use it as follows:
var oldItem = myList.Single(x => x.Id == newItem.Id);
myList = myList.Replace(oldItem, newItem);
However, it's not working. What am I missing?
While you can't replace the item in the materialized collection itself, you can replace the item that is yielded from a different IEnumerable<T> at a certain position.
All you have to do is use the Select extension method on IEnumerable<T> to map to the new item when the Id property matches, and use the new IEnumerable<T> instance, like so:
// Assuming newItem is already defined, has the value you want to yield
// and T is the type of newItem
IEnumerable<T> newEnumerable = myList.Select(i =>
i.Id == newItem.Id ? newItem : i
);
Then, when you iterate through the newEnumerable, when the item in the old list has an Id equal to newItem.Id, it will yield newItem.
IEnumerable is for traversing a generic data structure. Think about it as read-only access. In fact, making modifications to the IEnumerable while enumerating it is a big NO NO. Your original solution is fine, except for the last line. Just do Remove and Insert.
You're going to have a hard time doing this with IEnumerable as that interface is designed for iterating. However, you can get things working with IList
public static IList<T> Replace<T>(IList<T> list, T oldValue, T newValue) where T : class
{
var pos = list.IndexOf(oldValue);
list.Remove(oldValue);
list.Insert(pos, newValue);
return list;
}
With test code of
static void Main(string[] args)
{
IList<Item> myList = new List<Item>() { new Item { Id = "123" }, new Item { Id = "abc" }, new Item { Id = "XYZ" } };
var newItem = new Item { Id = "abc" };
var oldItem = myList.Single(x => x.Id == newItem.Id);
myList = Replace(myList, oldItem, newItem);
}
For an obvious definition of Item
class Item
{
public string Id { get; set; }
public readonly Guid Guid = Guid.NewGuid();
}
Since your sample code shows the use of ToList(), maybe creating a new collection (rather than modifying an existing one) is fine. If that's the case, you would write your Replace() method as follows
public static ICollection<T> Replace<T>(IEnumerable<T> list, T oldValue, T newValue) where T : class
{
var l = list.ToList();
var pos = l.IndexOf(oldValue);
l.Remove(oldValue);
l.Insert(pos, newValue);
return l;
}

using extension method for dropdown

I'm currently working on a MVC app which has a create and edit view. Both views are very similar in terms of the UI. On each page I've got around 5-7 dropdowns which I populate in the controller with the help of the following extension method.
public static IEnumerable<SelectListItem> ToSelectListItems<T>(this IEnumerable<T> items, Func<T, string> textSelector, Func<T, string> valueSelector)
{
return items.OrderBy(item => textSelector(item))
.Select(item =>
new SelectListItem
{
Text = textSelector(item),
Value = valueSelector(item)
});
}
Now on edit view I want to display the same dropdowns but this time with the selected value to be set to true to whatever the user selected on the create view. This is how I'm currently doing this
List<SelectListItem> userDdl = new List<SelectListItem>();
foreach (var items in GetUserDropDown(userId, userName))
{
userDdl.Add(new SelectListItem { Value = items.Value, Text = items.Text });
}
userDdl.Where(a => a.Value == someValueFromDb).First().Selected = true;
This works ine but I was thinking is there a better way approach for this or am I stuck with this
I've manage to figure something out. I created an overload for ToSelectListItems method as follows:
public static IEnumerable<SelectListItem> ToSelectListItems<T>(this List<T> items, Func<T, string> nameSelector, Func<T, string> valueSelector, Func<T, bool> selecter)
{
return items
.OrderBy(item => nameSelector(item))
.Select(item =>
new SelectListItem
{
Selected = selecter(item),
Text = nameSelector(item),
Value = valueSelector(item)
});
}
Now I can do:
List<SelectListItem> userDdl = GetUserDropDown(userId, userName).ToSelectListItems(a => a.UserName, a => a.UserId.ToString(), a => a.UserId.ToString() == userIdFromDb).ToList();

Don't bind duplicate items to checked list box

I've got a list of objects which have team details of salesmen.
the list has several teams which have the same name but the salesman is different.
the teamDetails class has the following attributes:
string teamName;
string region;
int teamSales;
string salesmanFullName;
string salesmanAddress;
the user has an option to find all the teams which have sales over a certain value. these teams are then added to a check box list.
this is how i'm populating the check box list:
var viewList = from toSearch in GlobalVariables.allSalesmenList
where toSearch.teamSales > Convert.ToInt32(txtSalesSearch.Text)
select toSearch;
SearchCheckedListBox.DataSource = viewList.ToList();
SearchCheckedListBox.DisplayMember = "teamName";
the problem I'm having is the team name is shown more than once if the team has more than one salesman.
how would I prevent the checkbox from having repeated values?
Try to use distinct with comparer:
var viewList = from toSearch in GlobalVariables.allSalesmenList
where toSearch.teamSales > Convert.ToInt32(txtSalesSearch.Text)
select toSearch;
SearchCheckedListBox.DataSource = viewList.Distinct(new TeamComparer()).ToList();
SearchCheckedListBox.DisplayMember = "teamName";
Comparer code:
public class TeamComparer : IEqualityComparer<teamDetails>
{
public bool Equals(teamDetails x, teamDetails y)
{
if (x.teamName == y.teamName) return true;
return false;
}
public int GetHashCode(teamDetails obj)
{
if (Object.ReferenceEquals(obj, null)) return 0;
return obj.teamName.GetHashCode();
}
}
You can simply use this
SearchCheckedListBox.DataSource = viewList.GroupBy(x => x.teamName)
.Select(g => g.First())
.ToList();
If you are planing to use the same trick more than once, you can write an extension method
public static IEnumerable<T> DistinctBy<T, S>(this IEnumerable<T> list, Func<T, S> selector)
{
return list.GroupBy(selector).Select(g => g.First());
}
then the code would be
SearchCheckedListBox.DataSource = viewList.DistinctBy(x => x.teamName).ToList();

Categories