What's the best way to do this where PROPNAME could be any property of type T? Build it up with reflection, or is there a good LINQ way of doing this?
T[] vals = people.Select(x => x.PROPNAME).ToArray<T>();
this is the best I've got so far:
public T[] ConvertListCol<TFrom,T>(IEnumerable<TFrom> list, string col)
{
var typ = typeof (TFrom);
var props = typ.GetProperties();
var prop = (from p in props where p.Name == col select p).FirstOrDefault();
var ret = from o in list select prop.GetValue(o, null);
return ret.ToArray<T>();
}
Got an error in the return... but getting closer. It's okay, but a little messier than I'd hoped.
Using a non-explicit var type, it will automatically generate the type for you
var names = people.Select(x => x.PROPNAME).ToArray();
The type will be inferred if you use the var keyword:
var names = people.Select(x => x.PROPNAME).ToArray();
Related
I have been looking at many posts here and on the web but none of them seem to be helping.
I have a table with about 2 million records, it has over 200 columns.
A simple web service allow the user to pull a specific number of columns out of the table, the user has the option to choose which column to pull.
The result needs to be string of comma separated values, so my query needs to pull the requested columns and return a concatenate string.
I have done this using ADO.NET and pure SQL queries it works fine however I was asked to do it in Entity Framework.
Here is what I have and have done already.
I get the requested columns list as an array of strings.
The following is my query, not sure if it's the best solution or idea hence I'm asking for help here.
var valueList2 = ctx.mytable.Where(x => x.pcds == comValue).Select(x => new{temp = x.column1 +", "+ x.column2}).Select(x => x.temp).ToList();
The above gives me string of two columns separated by commas, I just need to somehow push my array of column names into the lambda part of it.
I did the following but then realised that it only works with a specific type of a class not anonymous, also I can't figure out how I can use it for a multiple columns and not make it so complex.
var createdType = typeof(mytable);
var Param = Expression.Parameter(typeof(string), "pr");
var obj = Expression.New(createdType);
var ValueProperty = createdType.GetProperty("long");
var ValueAssignment = Expression.Bind(ValueProperty, Param);
var memberInit = Expression.MemberInit(obj, ValueAssignment);
var lm = Expression.Lambda<Func<string, mytable>>(memberInit, Param);
Thank you
I'm using Dynamic Linq (source code). Sadly there is little documentation about how to use it :-) In a fun boomerang effect, there is an "evolved" version. The boomerang effect is because the code for generating the dynamic class is based on one of my responses :-) The remaining code seems to be very beautiful... And there is a full suit of unit tests with code samples!!! Note that this second library is a superset of the first library, so you can probably apply many examples to the first one! :-)
I'm adding some static methods to translate the result of a a Dynamic Linq query to a IEnumerable<object[]>.... Example code:
using (var ctx = new Model1())
{
var result = ctx.MyTable
.Take(100)
.SimpleSelect(new[] { "ID", "Col1", "Col2" })
.ToObjectArray();
foreach (var row in result)
{
Console.WriteLine(string.Join(", ", row));
}
}
More complex example:
var columnsNames = new[] { "SomeNullableInt32", "SomeNonNullableDateTimeColumn" };
// One for each column!
var formatters = new Func<object, string>[]
{
x => x != null ? x.ToString() : null,
x => ((DateTime)x).ToShortDateString()
};
var result = ctx.MyTable.Take(100).SimpleSelect(columnsNames).ToObjectArray();
foreach (var row in result)
{
var stringRow = new string[row.Length];
for (int i = 0; i < row.Length; i++)
{
stringRow[i] = formatters[i](row[i]);
}
Console.WriteLine(string.Join(", ", stringRow));
}
And the classes... One (SimpleSelect) produces the Dynamic SQL Select, and "anonymizes" the field names. I do this because for each type of return the Dynamic Linq will generate at runtime a class. This class won't be unloaded until the program ends. By anonymizing the columns (I rename them to Item1, Item2, Item3...) I increase the possibility that the same class will be reused. Note that different type of columns will generate different classes! (int Item1, string Item2 will be a different class from int Item1, DateTime Item2), the other (ToObjectArray) returns a IEnumerable<object[]>, something easier to parse.
public static class DynamicLinqTools
{
private static ConcurrentDictionary<Type, Func<object, object[]>> Converters = new ConcurrentDictionary<Type, Func<object, object[]>>();
public static IQueryable SimpleSelect(this IQueryable query, string[] fields)
{
// With a little luck, "anonymizing" the field names we should
// reduce the number of types created!
// new (field1 as Item1, field2 as Item2)
return query.Select(string.Format("new ({0})", string.Join(", ", fields.Select((x, ix) => string.Format("{0} as Item{1}", x, ix + 1)))));
}
public static IEnumerable<object[]> ToObjectArray(this IQueryable query)
{
Func<object, object[]> converter;
Converters.TryGetValue(query.ElementType, out converter);
if (converter == null)
{
var row = Expression.Parameter(typeof(object), "row");
// ElementType row2;
var row2 = Expression.Variable(query.ElementType, "row2");
// (ElementType)row;
var cast = Expression.Convert(row, query.ElementType);
// row2 = (ElementType)row;
var assign = Expression.Assign(row2, cast);
var properties = query.ElementType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.CanRead && x.GetIndexParameters().Length == 0)
.ToArray();
// (object)row2.Item1, (object)row2.Item2, ...
var properties2 = Array.ConvertAll(properties, x => Expression.Convert(Expression.Property(row2, x), typeof(object)));
// new object[] { row2.Item1, row2.Item2 }
var array = Expression.NewArrayInit(typeof(object), properties2);
// row2 = (ElementType)row; (return) new object[] { row2.Item1, row2.Item2 }
var body = Expression.Block(typeof(object[]), new[] { row2 }, assign, array);
var exp = Expression.Lambda<Func<object, object[]>>(body, row);
converter = exp.Compile();
Converters.TryAdd(query.ElementType, converter);
}
foreach (var row in query)
{
yield return converter(row);
}
}
}
This is a short and easy answer for whoever needs a different answer, but per our discussion with #xanatos, it's not the best as it also returns all the columns which need to be cut off before adding to a list of strings.
List<string> valueList = new List<string>();
using (var ctx = new DataEntities1())
{
var query = ctx.myTable.Where(x => x.pcds == scode).SingleOrDefault();
foreach (var item in columnsArray)
{
valueList.Add(typeof(myTable).GetProperty(onsColumns[Convert.ToInt32(item)]).GetValue(query).ToString());
}
}
So far I have this:
if (o.GetType().IsGenericType && o is System.Collections.IEnumerable)
{
var typefield = typeof(List<object>).GetFields().Where(f => f.Name == "T").ToArray();
Type T = (Type)(typefield[0].GetValue(o));
addButton.Enabled = true;
if (((System.Collections.IList)o).Count > 0)
{
removeButton.Enabled = true;
}
comboBox1.Enabled = true;
comboBox1.Items.Clear();
foreach (var item in Assembly.GetAssembly(T).GetTypes().Where(x => T.IsAssignableFrom(x) && !x.IsAbstract))
{
comboBox1.Items.Add(item);
}
comboBox1.DisplayMember = "Name";
}
but it crashes on the line "Type T = (Type)(typefield[0].GetValue(o));" because the array is empty, which means that the previous line did not work.
Question:
Assuming that everything that passes the first line is a List, how should I determine the type of objects in the list. (note that checking the type of the members is not good enough. The members are likely to be derived from the Type that is specified in the list definition.)
P.S.
I read here that my first line is the best way to determine something as a list:
If object is Generic List
Do you have a more accurate way?
I think one answer could be this method:
public IEnumerable<Type> GetGenericIEnumerables(object o) {
return o.GetType()
.GetInterfaces()
.Where(t => t.IsGenericType == true
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(t => t.GetGenericArguments()[0])
.ToList();
}
The you can use it this way:
var type= GetGenericIEnumerables(o).FirstOrDefault();
If o is is not IEnumerable<T>, then the type is null, else, it returns T.
It also works perfect when o is not generic itself, and inherits from IEnumerable<T>, for example for string the function will return char.
Based on jason's asnwer.
If I understood you correctly you get the generic parameters via
Type.GetGenericArguments()
See https://msdn.microsoft.com/en-us/library/system.type.getgenericarguments%28v=vs.110%29.aspx
To determine the type of a List<T> you can use Type.GetGenericArguments()
var o = new List<int>();
if (o.GetType().IsGenericType && o is IList)
{
var args = o.GetType().GetGenericArguments()
if(args.Count() == 1)
{
Type T = args[0];
// rest of code
}
}
You can use GetGenericArguments. This code will give you the type of element in your list o
if (o.GetType().IsGenericType && o is System.Collections.IEnumerable)
{
var itemType = o.GetType().GetGenericArguments()[0];
//Console.WriteLine(itemType);
}
If you have
var o = new List<string>{ "a", "b" };
It will give you
System.String;
This question already has answers here:
Generate EF orderby expression by string [duplicate]
(6 answers)
Closed 7 years ago.
I am using .NET 4.51, EF 6
I make a number of calls to my repository layer where I need to do some basic ordering on a single field in either ascending or descending order such as:
The result of GetAllList() is a List<T>. Now unfortunately the Id field I have to sort by is not always called Id nor is the Text field. They can be other things such as MyId, SomeTextField and so on.
So I was wondering if there was a way I could do the OrderBy() and OrderByDescending() clauses by supplying a string for the field name something like:
_Repository.GetAllList().OrderBy(r => r."SomeTextField")
In this way I could move all this code to a common method.
Any pointers greatly appreciated.
This will work:
public static class LinqExtensions
{
private static PropertyInfo GetPropertyInfo(Type objType, string name)
{
var properties = objType.GetProperties();
var matchedProperty = properties.FirstOrDefault (p => p.Name == name);
if (matchedProperty == null)
throw new ArgumentException("name");
return matchedProperty;
}
private static LambdaExpression GetOrderExpression(Type objType, PropertyInfo pi)
{
var paramExpr = Expression.Parameter(objType);
var propAccess = Expression.PropertyOrField(paramExpr, pi.Name);
var expr = Expression.Lambda(propAccess, paramExpr);
return expr;
}
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> query, string name)
{
var propInfo = GetPropertyInfo(typeof(T), name);
var expr = GetOrderExpression(typeof(T), propInfo);
var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
return (IEnumerable<T>) genericMethod.Invoke(null, new object[] { query, expr.Compile() });
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string name)
{
var propInfo = GetPropertyInfo(typeof(T), name);
var expr = GetOrderExpression(typeof(T), propInfo);
var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
return (IQueryable<T>) genericMethod.Invoke(null, new object[] { query, expr });
}
}
Testing:
var r = new List<temp> {
new temp { a = 5 },
new temp { a = 1 },
new temp { a = 15 }
}.OrderBy("a");
Gives the correct result (1, 5, 15) - and will provide lazy execution for your use with EF
You will need to implement the overloads if needed.
Does it have to be a string? Why not just make a method that takes a Func key selector as a parameter.
public List<T> GetAllListOrdered<T,TProp>(SimpleOrderingDirectionEnum direction, Func<T,TProp> keySelector)
{
return direction == SimpleOrderingDirectionEnum.Ascending ? _Repository.GetAllList().OrderBy(keySelector).ToList() : _Repository.GetAllList().OrderByDescending(keySelector).ToList();
}
Then call it like
Func<ObjectToSortType, ObjectPropertyToSortBy> keySelector = r => r.Id;
GetAllListOrdered(SimpleOrderingDirectionEnum.Ascending, keySelector);
If the Rob's answer is not enough for you. Try Linq Dynamic. http://dynamiclinq.azurewebsites.net/
using System.Linq.Dynamic; //Import the Dynamic LINQ library
//The standard way, which requires compile-time knowledge
//of the data model
var result = myQuery
.Where(x => x.Field1 == "SomeValue")
.Select(x => new { x.Field1, x.Field2 });
//The Dynamic LINQ way, which lets you do the same thing
//without knowing the data model before hand
var result = myQuery
.Where("Field1=\"SomeValue\"")
.Select("new (Field1, Field2)");
Thanks to all. Rob, your solution was pretty close to what I ended up with.
Based on your insights I did some more searching and came across Marc Gravel's answer here Dynamic LINQ OrderBy on IEnumerable<T> (second post).
It added dynamic's as an additional bonus.
For a certain column name I want to calculate the SUM. I just found out how to use reflection but don't know if i'm doing it the right way..
string columnName = "days";
ObjectQuery<TableX> objectSet = context.CreateObjectSet<TableX>();
Func<TDomainEntity, object> fieldGetter;
var type = typeof(TDomainEntity);
var prop = type.GetProperty(columnName);
fieldGetter = e => prop.GetValue(e,null);
//this wouldn't work because the fieldGetter should be Func<TDomainEntity, decimal> and not object
var total = objectSet.Sum(fieldGetter);
I can't get this working because the fieldgetter should be of the type decimal but the the type.GetProperty will fail and casting it didn't work. So what am I missing or is there another way?
I could make a giant switch statement for the columnname but that's not nice is it :)
If you know the type should be decimal, then tell that to the getter:
Func<TDomainEntity, decimal> fieldGetter = e => (decimal)prop.GetValue(e,null);
Which should then work with LINQ-to-Objects as:
var total = objectSet.Sum(fieldGetter);
Note that if you intend to use LINQ-to-EF, you'll have to construct an expression tree instead:
var p = Expression.Parameter(typeof(TDomainEntity));
var lambda = Expression.Lambda<Func<TDomainEntity, decimal>>(
Expression.PropertyOrField(p, columnName), p);
var total = query.Sum(lambda);
Of course, you can use that approach either way:
var p = Expression.Parameter(typeof(TDomainEntity));
var lambda = Expression.Lambda<Func<TDomainEntity, decimal>>(
Expression.PropertyOrField(p, columnName), p);
var total = objectSet.Sum(lambda.Compile());
Do you know how fix this error? It` s show error in this line "foreach (int s in itemColl)"
What I have to do?
Error 1 Cannot convert type 'AnonymousType#1' to
'int' C:\Users\Rafal\Desktop\MVC ksiązka\moj
projekt\sklep\SportsStore.WebUI\Controllers\ProductController.cs 37 21 SportsStore.WebUI
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select new
{
p.Id_kat
};
foreach (int s in itemColl)
{
Console.WriteLine(s);
}
You are selecting itemColl with new keyword, defining an anonymous type, you can't apply foreach loop with int type. Your current query is returning something like IEnumerable<AnonymousType>
Instead you may do:
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select p.Id_Kat;
This will return IEnumerable<int> and that you can use in your current foreach loop.
But if you want to use your current query with selection on anonymous type, you have to modify your foreach loop with implicit type var, and since your current query is returning an anonymous type object you may select the Id_kat from the object. Something like.
foreach (var s in itemColl)
{
Console.WriteLine(s.Id_kat);
}
IMO, the second approach is not recommended because you are just returning an int type wrapped inside an anonymous type. Its better if you can change your query to return IEnumerable<int>
You just need to change the select to:
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select p.Id_kat;
This will create an IEnumerable<int> which is what you are trying to use in the foreach.
The select new { p.Id_Kat } is creating a new Anonymous Type which is in the simplest way of saying it is a new class like this:
class AnonymousType#1
{
public int Id_Kat {get; set;}
}
var itemColl = from p in re.Kategorie
where p.Nazwa == category
//This is Anonymous Type
select new
{
//Anonymous type's attribute(s) go(es) here
IntItem = p.Id_kat
};
foreach (var s in itemColl)
{
Console.WriteLine(s.IntItem);
}
Well, you could return a real (int)-value instead of an anonymous linq-result
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select p.Id_Kat;