How do I select specific columns from IQueryable<T> in C# - c#

I need select some columns dynamically from a IQueryble.
Example:
IQueryable<Car> query = (from a in _dbContext.Table1
select new Car
{
Prop1 = a.Prop1,
Prop2 = a.Prop2,
Prop3 = a.Prop3
});
I need to do something like:
var lambda = new Test().CreateNewStatement<Car>("Prop1,Prop2");
I am using the function:
public Func<T, T> CreateNewStatement<T>(string fields)
{
var xParameter = Expression.Parameter(typeof(T), "o");
var xNew = Expression.New(typeof(T));
var bindings = fields.Split(',').Select(o => o.Trim())
.Select(o =>
{
var mi = CustomGetType<T>(o);
var xOriginal = Expression.Property(xParameter, mi);
return Expression.Bind(mi, xOriginal);
}
);
var xInit = Expression.MemberInit(xNew, bindings);
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
return lambda.Compile();
}
And the Call:
var lambda = new Test().CreateNewStatement<Car>("Prop1");
var ax = query.Select(lambda);
var result = ax.ToList();
But in database, the query is wrong:
SELECT
[Limit1].[C1] AS [C1],
[Limit1].[Prop1] AS [Prop1],
[Limit1].[Prop2] AS [Prop2],
[Limit1].[Prop3] AS [Prop3],
FROM ( ...
The correct search in database should be:
SELECT
[Limit1].[C1] AS [C1],
[Limit1].[Prop1] AS [Prop1]
FROM ( ...
I need manipulate the IQueryable< T > before the search in database.

The issue you're having is that you're passing a Func<User, User> into Select. This essentially means that the method is executed in memory, not in the database.
Entity Framework cannot generate SQL code from a compiled function.
When you write this:
var users = Users.Select(u => new User { a = 1 });
You're passing it an Expression<Func<User, User>> which entity framework CAN turn into SQL.
So, what you need to change is the end of your function. Instead of this:
var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
return lambda.Compile()
You want to simply do
return Expression.Lambda<Func<T, T>>(xInit, xParameter);
And change the return type of your function to be Expression<Func<T,T>>
HOWEVER
For whatever reason, EntityFramework does not allow you to construct objects which are entities. Linq-To-Sql does allow this, and running the code with my above changes will indeed work, and only select the one column.
Essentially this means that we need to change the expression to return a class which is not an entity used by entity framework.
So, instead of writing this:
var users = Users.Select(u => new User { a = 1 });
We need to write this:
var users = Users.Select(u => new NotAnEntityUser { a = 1 });
We then need to change our method to return a NotAnEntityUser instead of a User.
public class Test
{
public Expression<Func<TEntityType, TModelType>> CreateNewStatement<TEntityType, TModelType>(string fields)
{
var xParameter = Expression.Parameter(typeof (TEntityType), "o");
var xNew = Expression.New(typeof (TModelType));
var bindings = fields.Split(',').Select(o => o.Trim())
.Select(paramName =>
{
var xOriginal = Expression.Property(xParameter, typeof(TEntityType).GetProperty(paramName));
return Expression.Bind(typeof(TModelType).GetProperty(paramName), xOriginal);
}
);
var xInit = Expression.MemberInit(xNew, bindings);
var lambda = Expression.Lambda<Func<TEntityType, TModelType>>(xInit, xParameter);
return lambda;
}
}
And define our entity and model:
public class User
{
public virtual string Id { get; set; }
public virtual string UserName { get; set; }
public virtual string Salt { get; set; }
public static implicit operator User(NotAnEntityUser o)
{
return new User { Id = o.Id, UserName = o.UserName, Salt = o.Salt };
}
}
public class NotAnEntityUser
{
public virtual string Id { get; set; }
public virtual string UserName { get; set; }
public virtual string Salt { get; set; }
}
Which then lets you write:
var lam = new Test().CreateNewStatement<User, NotAnEntityUser>("Id");
var c = new MyContext().Users.Select(lam).AsEnumerable().Select(u => (User)u).ToList();
And indeed, the SQL generated from this is:
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id]
FROM [dbo].[User] AS [Extent1]
Instead of writing the implicit cast operator, you could also use a tool like automapper, etc.
With automapper, you'd end up with something like:
var c = AutoMapper.Map<List<User>>(new MyContext().Users.Select(lam).ToList());

Related

EF Core dynamically filter from list

I am working on creating a generic dynamic filter on a front-end table. The idea is each column will have a filter, where you can select values of that column. The column names must be completely dynamic and selected values. We are using .NET 5 and EF Core 5.
The SQL query I want is:
SELECT *
FROM Users
WHERE externalId IN ('1234', '5678');
Models:
public class ColumnFilter
{
[Required]
public string Name { get; set; } // column name
public List<string> SelectedValues { get; set; } = new List<string>(); // values selected in this column
}
public class User {
public string Name { get; set; }
public string ExternalId { get; set; }
}
Simplified code:
//columnfilter really comes from API request
var columnFilter = new ColumnFilter{
Name = "ExternalId",
SelectedValues = new List<string>{ "1234", "5678" }
};
var queryable = _context.Users.AsNoTracking();
queryable = queryable.Where(f => columnFilter.SelectedValues.Contains(EF.Property<string>(f, columnFilter.Name)));
var values = await queryable.ToListAsync(); // 0 results
EF Core generates this query:
SELECT *
FROM [Users] AS [u]
WHERE 0 = 1
How do I get this to work dynamically?
It is a sample using string values:
var queryable = _context.Users.AsNoTracking();
queryable = queryable.FilterDynamic(columnFilter.Name, columnFilter.SelectedValues);
public static class QueryableExtensions
{
public static IQueryable<T> FilterDynamic<T>(this IQueryable<T> query, string fieldName, ICollection<string> values)
{
var param = Expression.Parameter(typeof(T), "e");
var prop = Expression.PropertyOrField(param, fieldName);
var body = Expression.Call(typeof(Enumerable), "Contains", new[] {typeof(string)},
Expression.Constant(values), prop);
var predicate = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(predicate);
}
}
The solution I had actually works. I had some other code involved that was causing the issue.

C# Linq Lambda Expression for Entity Framework Query Passing Custom Expression to Where Condition

I have two tables called ShipmentType and Books. Entity class has been mapped for these tables. Created another class called BookShipment which contains two properties, classes of ShipmentType and Book.
public class BookShipment
{
public ShipmentType Shipment { get; set; }
public Books Book { get; set; }
}
I was trying to create a where expression as follows.
Expression<Func<BookShipment, bool>> expr = x => (x.Shipment.ID == 1 && x.Book.ID == 1);
var result = from c in styp
join d in book
on c.ID equals d.ID
select new BookShipment { Shipment = c, Book = d };
var List = result.Where(expr).ToList();
and above expression at where clause is working fine and getting the result from database.
Tried to create a dynamic expression same as above expr expression but it is giving error.
BookShipment table = new BookShipment();
table.Shipment = new ShipmentType();
table.Book = new Books();
ParameterExpression ParameterL = Expression.Parameter(table.GetType(), "x");
ParameterExpression Parameter1 = Expression.Parameter(table.Shipment.GetType(), "x.Shipment");
ParameterExpression Parameter2 = Expression.Parameter(table.Book.GetType(), "x.Book");
var Property1 = Expression.Property(Parameter1, "ID");
var Property2 = Expression.Property(Parameter2, "ID");
var Clause1 = Expression.Equal(Property1, Expression.Constant(1));
var Clause2 = Expression.Equal(Property2, Expression.Constant(1));
var Lambda1 = Expression.Lambda<Func<ShipmentType, bool>>(Clause1, Parameter1);
var Lambda2 = Expression.Lambda<Func<Books, bool>>(Clause2, Parameter2);
var OrElseClause = Expression.Or(Lambda1.Body, Lambda2.Body);
var Lambda = Expression.Lambda<Func<BookShipment, bool>>(OrElseClause, ParameterL);
var result = from c in styp
join d in book
on c.ID equals d.ID
select new BookShipment { Shipment = c, Book = d };
var record = result.Where(Lambda).ToList();
When executing above Where clause it is giving error.
{System.InvalidOperationException: The LINQ expression 'DbSet<ShipmentType>
.Join(
outer: DbSet<Books>,
inner: s => s.ID,
outerKeySelector: b => b.BookID,
innerKeySelector: (s, b) => new TransparentIdentifier<ShipmentType, Books>(
Outer = s,
Inner = b
))
.Where(ti => ti.Shipment.ID == 1 || ti.Book.BookID == 1)' could not be translated.
when you create an expression to pass into the LINQ where function, take the following hints:
the type is Expression<Func<T,Bool>> ...
that means you have ONE parameter of type T, and you return a bool
if you create two Lambdas with this type, and you want to combine them... even though you have the same type T as parameter, the two parameter instances are not the same ... you will have to traverse the tree and replace the parameter so there is only one instance...
if you'd like an example code ... here you go...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace SoExamples.ExpressionTrees
{
class Program
{
static void Main(string[] args)
{
var expr1 = GetExpression<Func<A, bool>>(x => x.Prop1 == 42);
var expr2 = GetExpression<Func<A, bool>>(x => x.Prop2 == "foo");
var expr3 = GetConstComparison<A, int>("Prop3.Prop1", 123);
var test = new A { Prop1 = 42, Prop2 = "foo", Prop3 = new B { Prop1 = 123 } };
var f1 = expr1.Compile();
var t1 = f1(test);
var f2 = expr2.Compile();
var t2 = f2(test);
var f3 = expr3.Compile();
var t3 = f3(test);
Expression tmp = Expression.AndAlso(Expression.AndAlso(expr1.Body, expr2.Body), expr3.Body);
tmp = new ParamReplaceVisitor(expr2.Parameters.First(), expr1.Parameters.First()).Visit(tmp);
tmp = new ParamReplaceVisitor(expr3.Parameters.First(), expr1.Parameters.First()).Visit(tmp);
var expr4 = Expression.Lambda<Func<A, bool>>(tmp, expr1.Parameters.First());
var f4 = expr4.Compile();
var t4 = f4(test);
var list = new List<A> { test };
var result = list.AsQueryable().Where(expr4).ToList();
}
static Expression<TDelegate> GetExpression<TDelegate>(Expression<TDelegate> expr)
{
return expr;
}
static Expression<Func<T, bool>> GetConstComparison<T, P>(string propertyNameOrPath, P value)
{
ParameterExpression paramT = Expression.Parameter(typeof(T), "x");
Expression expr = getPropertyPathExpression(paramT, propertyNameOrPath.Split('.'));
return Expression.Lambda<Func<T, bool>>(Expression.Equal(expr, Expression.Constant(value)), paramT);
}
private static Expression getPropertyPathExpression(Expression expr, IEnumerable<string> propertyNameOrPath)
{
var mExpr = Expression.PropertyOrField(expr, propertyNameOrPath.First());
if (propertyNameOrPath.Count() > 1)
{
return getPropertyPathExpression(mExpr, propertyNameOrPath.Skip(1));
}
else
{
return mExpr;
}
}
}
public class ParamReplaceVisitor : ExpressionVisitor
{
private ParameterExpression orig;
private ParameterExpression replaceWith;
public ParamReplaceVisitor(ParameterExpression orig, ParameterExpression replaceWith)
{
this.orig = orig;
this.replaceWith = replaceWith;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == orig)
return replaceWith;
return base.VisitParameter(node);
}
}
public class A
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public B Prop3 { get; set; }
}
public class B
{
public int Prop1 { get; set; }
}
}
of course you will want to add errorhandling etc...

Build an lambda Expression tree with a specific field in a linked entity

Data MODEL
public class TABLE
{
[Key]
public int ID { get; set; }
public int ONEFIELD { get; set; }
// -------------------- ForeignKey --------------------
[ForeignKey("People")]
public long PeopleID { get; set; }
public virtual People People { get; set; }
}
public class People
{
[Key]
public long PeopleID { get; set; }
public int CountryID { get; set; }
}
I need to build a lambda to query this MODEL :
Get TABLE.ONEFIELD = 1 AND TABLE.PEOPLE.COUNTRYID = 6
LINQ equivalent
_context.TABLEs
.Where(e => e.ONEFIELD == 1)
.Include(e => e.People)
.Where(i=>i.People.CountryID == 6);
My try
public static Expression<Func<TEntity, bool>> BuildLambda<TEntity>(OBJTYPE obj)
{
var item = Expression.Parameter(typeof(TEntity), "table");
Expression query = null;
// 1
var prop1 = Expression.Property(item, "ONEFIELD");
var value1 = Expression.Constant(1);
var equal1 = Expression.Equal(prop1, value1);
var lambdaFIELDONE = Expression.Lambda<Func<TEntity, bool>>(equal1, item);
query = lambdaFIELDONE.Body;
// 2
var prop2 = Expression.Property(item, typeof(People).Name + ".CountryID");
var value2 = Expression.Constant(6);
var equal2 = Expression.Equal(prop2, value2);
var lambdaCOUNTRYID = Expression.Lambda<Func<TEntity, bool>>(equal2, item);
query = Expression.And(query, lambdaCOUNTRYID);
}
but I receive this error
System.ArgumentException: Instance property 'People.CountryID' is not defined for type 'SOLUTION.Models.TABLE'
I don't need Generic, just a fixed lambda (and I couldn't use LINQ).
I tried several things to catch People.CountryID like
Expression.Property(item1, typeof(People).GetProperty("CountryID"));
Expression.Property(item, typeof(People).Name+"." + typeof(People).GetProperty("CountryID"));
Expression.Property(item, typeof(People).Name + "." + typeof(People).GetProperties().Where(x => x.Name == "CountryID").FirstOrDefault().Name);
no success
Any ideas ? thanks
So, to build a nested property access, you must nest the Expressions that access each level. Then you can combine the tests into a body and finally create the lambda for the result:
public static Expression<Func<TEntity, bool>> BuildLambda<TEntity>(OBJTYPE obj) {
// (TEntity table)
var parmTable = Expression.Parameter(typeof(TEntity), "table");
// table.ONEFIELD
var prop1 = Expression.Property(parmTable, "ONEFIELD");
// table.ONEFIELD == 1
var equal1 = Expression.Equal(prop1, Expression.Constant(1));
// table.People
var prop2_1 = Expression.Property(parmTable, nameof(People));
// table.People.CountryID
var prop2_2 = Expression.Property(prop2_1, "CountryID");
// table.People.CountryID == 6
var equal2 = Expression.Equal(prop2_2, Expression.Constant(6));
// table.ONEFIELD == 1 && table.People.CountryID == 6
var finalBody = Expression.AndAlso(equal1, equal2);
// table => table.ONEFIELD == 1 && table.People.CountryID == 6
return Expression.Lambda<Func<TEntity, bool>>(finalBody, parmTable);
}
Using LINQPad, you could create a sample lambda and then use the Dump method and you would see that a nested FieldExpression is created, which is what gets created when you call Expression.Property:
Expression<Func<TEntity, int>> f = t => t.People.CountryID;
f.Dump();

Dynamically compose select using Linq to sql (EF6)

Is it possible to build part of query based on Expression?
public List<SelectListItem> GetSelectedListFromEntity<T>(Expression<Func<T, object>> property) where T : BaseEntity<int>
{
var result = _repository.Query<T>().Select(p => new SelectListItem()
{
Text = property, //? (in simple case it looks like: p.Name + p.Category)
Value = p.Id.ToString(),
).ToList();
return result;
}
For:
var result = GetSelectedListFromEntity<Product>(p => p.Name + p.Category);
If you want it to work with a db provider, something giving you an IQueryable you can do something with expression trees like this.
Given these classes:
public class BaseEntity
{
public int Id { get; set;}
}
public class Product : BaseEntity
{
public string Name { get; set; }
public string Category { get; set; }
}
Now you need a function to make the expression:
public Expression<Func<T, SelectListItem>> CreateExpression<T>(Expression<Func<T, string>> textExp) where T : BaseEntity
{
var arg = textExp.Parameters.First();
var param = new ParameterExpression[] { arg };
var body = Expression.MemberInit(
Expression.New(typeof(SelectListItem)),
Expression.Bind(typeof(SelectListItem).GetMember("Text").First(), textExp.Body),
Expression.Bind(typeof(SelectListItem).GetMember("Value").First(),
Expression.Call(
Expression.PropertyOrField(arg, "Id"),
"ToString",
new Type[0]
)));
var exp = Expression.Lambda(body, param);
return (Expression<Func<T, SelectListItem>>)exp;
}
In C# 6 it's better practice to replace those magic strings with nameof().
Then finally you can call it with something like this:
var items = new List<Product> { new Product { Id = 0, Name = "Test", Category = "Cat" } };
var result = items.AsQueryable().Select(CreateExpression<Product>(p => p.Name + p.Category)).ToList();
Now as long as you linq provider can cope with making SelectListItems you should be fine.

Union in entity framework

I have two tables: Vehicles and Workers.
Vehicle(Id, Number)
Workers(Id, Name, ContractorVehicleNumber)
I would like to write lambda query to return all the vehicles and the contractor vehicles. Something like in sql:
SELECT Id, Number
FROM Vehicle
UNION
SELECT NULL, ContractorVehicleNumber
FROM Workers
This is what I made:
public IQueryable<Vehicle> Get(bool includeContractorVehicles)
{
IQueryable<Vehicle> query = GetQuery();
if (includeContractorVehicles == true)
{
WorkerRepository rep = new WorkerRepository();
IQueryable<Vehicle> contractorsVehicles = rep.GetWirkers().
Select(x => new Vehicle()
{
VehicleNumber = x.ContractorVehicleNumber
});
query = query.Union(contractorsVehicles);
}
return query;
}
But I get an exception:
The entity or complex type 'XXXXXXXX' cannot be constructed in a LINQ to Entities query.
You cannot construct mapped entity type in projection. Your former example will work only if you create a new special type used for projection:
public class VehicleResult
{
public string Number { get; set; }
... // If you don't need more then one column you can use simple type instead of custom class
}
And your method will look like:
public IQueryable<VehicleResult> Get(bool includeContractorVehicles)
{
IQueryable<VehicleResult> query = GetQuery().Select(v => new VehicleResult { ... });
if (includeContractorVehicles == true)
{
WorkerRepository rep = new WorkerRepository();
IQueryable<VehicleResult> contractorsVehicles = rep.GetWorkers().
Select(x => new VehicleResult()
{
Number = x.ContractorVehicleNumber
});
query = query.Union(contractorsVehicles);
}
return query;
}
You cant create entities in the select statement. Try this instead:
public class VehicleDTO
{
public int Id { get; set; }
public int Number { get; set; }
}
public IQueryable<VehicleDTO> Get(bool includeContractorVehicles)
{
var query = GetQuery().Select(x => new VehicleDTO(){ ID = c.ID, Number = c.Number });
if (includeContractorVehicles)
{
WorkerRepository rep = new WorkerRepository();
var contractorsVehicles = rep.GetWirkers().
Select(x => new VehicleDTO(){ Number = x.ContractorVehicleNumber});
query = query.Union(contractorsVehicles);
}
return query;
}
Also are you sure you want a Union and not a Concat ?

Categories

Resources