String parameter having name of column of database to actual column value - c#

I have a method that sends a string parameter(paramType) having name of column of database. and I want to get the value of the column in my Select statement.
I need to replace paramType with value of that column.
var details = DetailsRepository
.Find(application => application.Created > compareDate)
.Select(m => new {m.ApplicationId, paramType })
.Distinct();
I just need to replace the paramType with name of the column is Select. Suppose I have paramtype = "Address", I want the second element to be m.Address.

If the value of paramType is the name of a member, then to do this you'll have to build an expression tree at runtime. This is possible, but is awkward unless all the columns you'll be selecting will always be of the same type.This is further complicated by the fact that you want to select a tuple in the lambda. Frankly, it would be easier to write the SQL dynamically in this case...
Edit: you could try this, which demonstrates the amount of evil needed here:
static Expression<Func<Application, T>> BuildTupleSelector<T>(
string paramType, Func<T> usedForGenericTypeInference)
{
var m = Expression.Parameter(typeof(Application), "m");
var body = Expression.New(typeof(T).GetConstructors().Single(),
new Expression[] {
Expression.PropertyOrField(m, nameof(Application.ApplicationId)),
Expression.PropertyOrField(m, paramType)
},
new MemberInfo[]
{
typeof(T).GetProperty("ApplicationId").GetGetMethod(),
typeof(T).GetProperty("Value").GetGetMethod()
});
return Expression.Lambda<Func<Application, T>>(body, m);
}
with usage:
var details = DetailsRepository
.Find(application => application.Created > compareDate)
.Select(BuildTupleSelector(paramType,
() => new { ApplicationId = 123, Value = "abc" }))
.Distinct();
details should now the the anonymous type with an int ApplicationId and string Value.

Related

GroupBy with different parameters

I have one query with groupBy parameters depends on the user input. If the condition is true, query will be grouped with DOB attribute. else, it doesnt need to.
Here is my code
var userList = user.GroupBy(x => new { x.Name, x.Age});
if (isBaby)
{
userList = user.GroupBy(x => new { x.Name, x.Age, x.DOB });
}
but i got an error which the parameters is not same. The latter code is to select from this query like this.
var allList= userList.Select({
...
}).ToList();
I dont want to create two select list because it is easier to manage if only use one select list.
Edited:
This is the error
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<SystemLinq.IGrouping<<anonymous type: string name, string age>, Domains.User>>' to 'System.Collections.Generic.IEnumerable<SystemLinq.IGrouping<<anonymous type: string name, string age, string DOB>, Domains.User>>'
enter code here
Assuming DOB is a DateTime:
var userList = user.GroupBy(x => new { x.Name, x.Age, dob = isBaby ? x.DOB : DateTime.MinValue });
That way the expression is always the same type, but won't cause an additional tier of grouping when isBaby is false.
Well you are constructing two different results in the group by...
Var implicitly tries to assume the type, and the first assignment is an anon type with only Name and Age. The second one adds DOB which is a different anon type.
Something like
new { x.Name, x.Age, DateOfBirth = isBaby ? x.DOB : DateTime.MinValue }
Would fix it

Lambda Select Columns Without an Expression

I want an EF query to return an entire column, but I want to choose that column with a variable. Can that be done? I.E. use a variable instead of a lambda expression as such:
FieldValues = db.PbiData
.Where(x => DatasetList.Contains(x.DatasetId))
.Select(FieldName)
.ToList()
FieldName will always match one of the column names of the PbiData table. For example, the first FieldName value is "Department", and the query below works just fine:
FieldValues = db.PbiData
.Where(x => DatasetList.Contains(x.DatasetId))
.Select(x=>x.Department)
.ToList()
The where clause in each of these queries simply restricts the data returned to the data relevant to the current user.
My attempt, per a comment below:
foreach(var F in model.FieldMetaData)
{
if (F.FieldType == "String")
{
PbiFieldData PbiFieldData = new PbiFieldData()
{
FieldName = F.FieldName,
FieldValues = await db.PbiData.Where(x => DatasetList.Contains(x.DatasetId)).Select(F.FieldName).ToListAsync()
};
model.PbiData.Add(PbiFieldData);
};
}
yes, you can use Expression
ParameterExpression param = Expression.Parameter(typeof(Table), yourVariable);
MemberExpression propExpression = Expression.PropertyOrField(param, yourVariable);
Expression<Func<Table, string>> selector = Expression.Lambda<Func<Table, string>>(propExpression, param);
var result = db.Table.Select(selector).First();
You could use System.Linq.Dynamic Nuget for the purpose
var fieldName = "Department";
var results = context.PbiData
.Where(x=> DatasetList.Contains(x.DatasetId))
.Select(fieldName);
Based on comment, to convert to List, one can use
var results = await context.PbiData
.Where(x=> DatasetList.Contains(x.DatasetId))
.Select(fieldName).ToListAsync();
You can write native sql query:
var FieldValues = ctx.Database.SqlQuery<string>
(
$#"select {fieldName} from PbiData
where DatasetId in ({string.Join(", ", DatasetList.Select(x => $"'{x}'"))})"
).ToList();

Dynamically build select list from linq to entities query

I'm looking for a way to dynamically create a select list from a iQueryable object.
Concrete example, i want to do something like the following:
public void CreateSelectList(IQueryable(of EntityModel.Core.User entities), string[] columns)
{
foreach(var columnID in columns)
{
switch(columnID)
{
case "Type":
SelectList.add(e => e.UserType);
break;
case "Name":
SelectList.add(e => e.Name);
break;
etc....
}
}
var selectResult = (from u in entities select objSelectList);
}
So all properties are known, i however don't know beforehand what properties are to be selected. That will be passed via the columns parameter.
I know i'm going to run into issues with the type of the selectResult type, because when the select list is dynamic, the compiler doesn't know what the properties of the anonymous type needs to be.
If the above is not possible: The scenario I need it for is the following:
I'm trying to create a class that can be implemented to display a paged/filtered list of data. This data can be anything (depends on the implementations).The linq used is linq to entities. So they are directly linked to sql data. Now i want to only select the columns of the entities that i am actually showing in the list. Therefore i want the select to be dynamic. My entity might have a hundred properties, but if only 3 of them are shown in the list, i don't want to generate a query that selects the data of all 100 columns and then only uses 3 of them. If there is a different approach that I haven't thought of, I'm open to ideas
Edit:
Some clarifications on the contraints:
- The query needs to work with linq to entities (see question subject)
- an entity might contain 100 columns, so selecting ALL columns and then only reading the ones i need is not an option.
- The end user decides what columns to show, so the columns to select are determined at run time
- i need to create a SINGLE select, having multiple select statements means having multiple queries on the database, which i don't want
Dynamic select expression to a compile time known type can easily be build using Expression.MemberInit method with MemberBindings created using the Expression.Bind method.
Here is a custom extension method that does that:
public static class QueryableExtensions
{
public static IQueryable<TResult> Select<TResult>(this IQueryable source, string[] columns)
{
var sourceType = source.ElementType;
var resultType = typeof(TResult);
var parameter = Expression.Parameter(sourceType, "e");
var bindings = columns.Select(column => Expression.Bind(
resultType.GetProperty(column), Expression.PropertyOrField(parameter, column)));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var selector = Expression.Lambda(body, parameter);
return source.Provider.CreateQuery<TResult>(
Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
source.Expression, Expression.Quote(selector)));
}
}
The only problem is what is the TResult type. In EF Core you can pass the entity type (like EntityModel.Core.User in your example) and it will work. In EF 6 and earlier, you need a separate non entity type because otherwise you'll get NotSupportedException - The entity or complex type cannot be constructed in a LINQ to Entities query.
UPDATE: If you want a to get rid of the string columns, I can suggest you replacing the extension method with the following class:
public class SelectList<TSource>
{
private List<MemberInfo> members = new List<MemberInfo>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
var member = ((MemberExpression)selector.Body).Member;
members.Add(member);
return this;
}
public IQueryable<TResult> Select<TResult>(IQueryable<TSource> source)
{
var sourceType = typeof(TSource);
var resultType = typeof(TResult);
var parameter = Expression.Parameter(sourceType, "e");
var bindings = members.Select(member => Expression.Bind(
resultType.GetProperty(member.Name), Expression.MakeMemberAccess(parameter, member)));
var body = Expression.MemberInit(Expression.New(resultType), bindings);
var selector = Expression.Lambda<Func<TSource, TResult>>(body, parameter);
return source.Select(selector);
}
}
with sample usage:
var selectList = new SelectList<EntityModel.Core.User>();
selectList.Add(e => e.UserType);
selectList.Add(e => e.Name);
var selectResult = selectList.Select<UserDto>(entities);
What you are going for is possible, but it's not simple. You can dynamically build EF queries using the methods and classes in the System.Linq.Expressions namespace.
See this question for a good example of how you can dynamically build your Select expression.
I believe this is what you need:
var entities = new List<User>();
entities.Add(new User { Name = "First", Type = "TypeA" });
entities.Add(new User { Name = "Second", Type = "TypeB" });
string[] columns = { "Name", "Type" };
var selectResult = new List<string>();
foreach (var columnID in columns)
{
selectResult.AddRange(entities.Select(e => e.GetType().GetProperty(columnID).GetValue(e, null).ToString()));
}
foreach (var result in selectResult)
{
Console.WriteLine(result);
}
This code outputs:
First
Second
TypeA
TypeB
UPDATE (according to comments)
// initialize alist of entities (User)
var entities = new List<User>();
entities.Add(new User { Name = "First", Type = "TypeA", SomeOtherField="abc" });
entities.Add(new User { Name = "Second", Type = "TypeB", SomeOtherField = "xyz" });
// set the wanted fields
string[] columns = { "Name", "Type" };
// create a set of properties of the User class by the set of wanted fields
var properties = typeof(User).GetProperties()
.Where(p => columns.Contains(p.Name))
.ToList();
// Get it with a single select (by use of the Dynamic object)
var selectResult = entities.Select(e =>
{
dynamic x = new ExpandoObject();
var temp = x as IDictionary<string, Object>;
foreach (var property in properties)
temp.Add(property.Name, property.GetValue(e));
return x;
});
// itterate the results
foreach (var result in selectResult)
{
Console.WriteLine(result.Name);
Console.WriteLine(result.Type);
}
This code outputs:
First
TypeA
Second
TypeB

Linq group by using reflection

I have data table "Car" which have 3 cols (owner, carType, colour). My question is how can i make the grouping portion more dynamic by using reflection. my idea is add the grouping col in to array, then use the reflection on the query grouping part. however i was struck at the reflection..
var gcols = new string[] { "owner", "carType" };
var reseult = dt.AsEnumerable()
.GroupBy(x => new
{
carType = x.Field<string>("carType"),
colour = x.Field<string>("colour")
})
.Select(x => new
{
CarType = x.Key.carType,
Colour = x.Key.colour,
count = x.Count()
})
.OrderBy(x => x.CarType).ToList();
If you added this extension method to object:
public static T Field<T>(this object source, string FieldName)
{
var type = source.GetType();
var field = type.GetField(FieldName);
return (T)field.GetValue(source);
}
You'd be able to use the syntax you've posted in your code.
I've not added any safety checking here so it'll need cleaning up, but it'll get you going. Ideally you'd want to check that the type of field is the same as T and a few other checks.

LINQ: How to dynamically use an ORDER BY in linq but only if a variable is not string.empty or null

I am using LINQ2SQL and its working pretty well. However depending on the value of the variable type string in C#, I need to use "Order By" in my query or not use an "order by".
If the C# string is NOT null, or empty, then I want to "order by" on the contents of the string variable. If the C# string is empty or null, then I don't include an order by.
Is it possible to write this kind of query?
Do like in VVS's answer, but if you want to pass in the column name for ordering you may want to use this extension method instead of the built-in OrderBy method:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string memberName)
{
ParameterExpression[] typeParams = new ParameterExpression[] { Expression.Parameter(typeof(T), "") };
System.Reflection.PropertyInfo pi = typeof(T).GetProperty(memberName);
return (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { typeof(T), pi.PropertyType },
query.Expression,
Expression.Lambda(Expression.Property(typeParams[0], pi), typeParams))
);
}
Do it in two steps:
var query = from .. in .. where .. select ..;
if (!string.IsNullOrEmpty(someVariable))
{
query = query.OrderBy((..) => ..);
}
Have C# test the contents of the list, and do the order only if it doesn't contain null.
var myList = (from s in dataContect.Users select s).ToList();
bool containsNull = false;
foreach (var item in mylist)
{
if (string.IsNullOrEmpty(item.LastName))
{
containsNull = true;
}
}
if (!containsNull)
{
// If is not contains null, Use Order By
myList = myList.OrderBy(k => k....);
}

Categories