Given the following LINQ statement, can someone tell me if it is possible to refactor the select portion into an expression tree? I have not used expression tree's before and have not been able to find much information regarding Selects.. Note this is to be translated into SQL and run inside SQL Server, not in memory.
var results = db.Widgets
.Select(w => new
{
Name = (w is x) ? "Widget A" : "Widget B"
});
I would like to be able to do this..
var name = [INSERT REUSABLE EXPRESSION]
var somethingElse = [INSERT REUSABLE EXPRESSION]
var results = db.Widgets.Select(w => new { Name = name, SomethingElse = somethingElse });
Obviously the intended use is for more complex statements.
You can do this using LinqKit. It'll work as long as your method is translatable to SQL. This is essentially what a complete example might look like:
public static class ReusableMethods
{
public static Expression<Func<int, Person>> GetAge()
{
return p => p.Age;
}
}
var getAge = ReusableMethods.GetAge();
var ageQuery = from p in People.AsExpandable()
select getAge.Invoke(p);
Note that:
You need to add AsExpandable() to your IQueryable.
You must assign your method to a local variable before using it (not sure about the exact reason why, but its a must).
Related
I'm trying to create a generic blazor component that and has a config object.
So far I've been able to successfully mimic .Where(), .Include().ThenInclude() separately, but now I need to add condition to the include.
My config file has the following structure
class Config
{
public List<string> includes {get;set;}
public List<Expression<Func<T,bool>>> conditions {get;set;}
}
database structure
For the example lets assume the following structure.
Dataset (1)<--(n) DatasetItem
DatasetItem (1)-->(1) Image
DatasetItem (1)-->(1) Label
Image (1)<--(n) Labels
Label(1)<--(n) Tags
where an image can be a part of more than one dataset and it's tags might or might not be included in the same dataset. Imagine an picture of a city park with dogs, people, and cars. Someone would want a dataset of vehicles and consider only the cars and ignore the labels about animals and people.
A Label include the coordinates and one or more TagNames (vehicle, car, red, fourWheeler, etc)
what works
And I can give conditions and includes this way:
var config = new Config<DatasetItem>()
{
includes = new() {"Image", "Image.Labels", "Image.Labels.Tags"}
conditions = new() {di => di.datasetId == 1, di => di.Label.width > 100}
}
the problems and limitations of this approach
For this to work I use the .Include(str) overload, but I don't like that approach since it is prone to fail.
The other problem with this approach is a limitation on the conditions.
For example, I can not add a condition to get only the Tags that belong to a specific category.
It seems that is not possible to add a condition that would give me this filter. Best I got was with the use of All or Any, but those do not give the desire result.
config.conditions.Add(
di => di.Images.Any(i => Labels.Any(l => l.Tags.Any(t => t.Name == "vehicle"))
)
what I'm looking for
Whith regular use of Include, obtaining the desire result would be like this.
DatasetItems.Include(di => di.Images)
.ThenInclude(i => i.Labels)
.ThenInclude(l => l.Tags.Where(t => t.Name == "vehicle"))
So, is there any way to achieve this?
I thing the solution would require to use Expression Trees, Reflection, and Recursive functions using MakeGenericMethod, but I havent found a way to achive this.
the ideal solution would be to find a structure like the one for conditions
List<Expression<Func<T,something>>> that lets me add the condition in this way
includes.add(di => di.Images.Labels.Tags.Where(t => t.Name == "vehicle"))
a failed attempt
I'm including a failed attempt I took to solve this, just in case this helps to narrow down the intention and for if someone can use it for solving the problem. This attempt is far from ideal.
property was a dot-separated string, like "Image.Labels.Tags" and filter was suppoused to be the Where expression.
This function does not compile.
public static IQueryable<T> FilteredInclude<T>(this IQueryable<T> query, string property, Func<dynamic, bool> filter) where T : class
{
var type = typeof(T);
var properties = property.Split('.');
var propertyInfo = type.GetProperty(properties[0]);
var parameter = Expression.Parameter(type, "x");
var propertyExpression = Expression.Property(parameter, propertyInfo);
var lambda = Expression.Lambda(propertyExpression, parameter);
var filteredQuery = query.Where(x => filter(propertyInfo.GetValue(x)));
if (properties.Length == 1)
{
var methodInfo = typeof(QueryableExtensions).GetMethod(nameof(Include));
methodInfo = methodInfo.MakeGenericMethod(type, propertyInfo.PropertyType);
var result = methodInfo.Invoke(null, new object[] { filteredQuery, lambda });
return (IQueryable<T>)result;
}
else
{
var nextProperty = string.Join(".", properties.Skip(1));
var result = filteredQuery.FilteredInclude(nextProperty, filter);
var thenIncludeMethod = typeof(Microsoft.EntityFrameworkCore.Query.Internal.IncludeExpression)
.GetMethod("ThenInclude")
.MakeGenericMethod(propertyInfo.PropertyType);
var resultWithThenInclude = thenIncludeMethod.Invoke(result, new object[] { lambda });
return (IQueryable<T>)resultWithThenInclude;
}
}
Question: How do I initialize var in the following code? [Then, of course, I'll remove var declaration from the if statement]
The last line of the following code returns the well know error: The name lstCurrent does not exists in current context. Clearly the error is because the var is defined inside if statement and used outside if statement
Note: I'm selecting only a few columns from the table and hence dealing with anonymous type. Some examples I saw online did not work - probably since my code is selecting anonymous type. But this is just a guess.
var lstCurrent =????;
if(Type==1)
var lstCurrent =_context.Customers().Where(t =>t.type=="current").Select(c => new { c.LastName, c.City});
else
lstCurrent = _context.Customers().Where(...).Select(...)
return View(lstCurrent.ToList());
var is not a type - it means "I don't care to (or can't) specify what the type is - let the compiler do it for me".
In your case, you're assigning it the result of one of two queries, one of which returns an anonymous type, so you can't specify the type since you don't know the name of the anonymous type (hence the term "anonymous").
In order to use var, the compiler needs some expression at initialization to know what the actual type is.
I'd suggest something like:
var lstCurrent = Type==1
? _context.Customers().Where(t =>t.type=="current").Select(c => new { c.LastName, c.City})
: _context.Customers().Where(...).Select(...)
But note that your "selects" must return the same type (or anonymous types with the exact same fields) or you won't be able to use var.
In the end I would try to bake the condition into your Where clause for less repetetive code:
bool isTypeOne = Type==1;
var lstCurrent = _context.Customers()
.Where(t => isTypeOne ? t.type=="current" : ...)
.Select(c => new { c.LastName, c.City})
Try
IEnumerable lstCurrent;
if(Type == 1)
lstCurrent = foo;
else
lstCurrent = bar;
How do I initialize var in the following code? [Then, of course, I'll
remove var declaration from the if statement]
This is not possible since the type of the object you assign at the left should be known. For instance
var a = "text";
The type of a is known at compile time since the right hand expression is a string. This cannot be done with a sequence of anonymous types, like the one you define.
I can see two options. One is that D Stanley already mentioned. The other is to define a class with two properties like below:
public class PersonCity
{
public string LastName { get; set; }
public string City { get; set; }
}
and then project each element of your query to a PersonCity object.
lstCurrent context.Customers()
.Where(t =>t.type=="current")
.Select(c => new PersonCity
{
LastName = c.LastName,
City = c.City
});
Doing so, you can define now you lstCurrent as below:
var lstCurrent = Enumerable.Empty<PersonCity>();
Important Note
In case of your queries return different types, the above are meaningless. Both queries (one in if and the other at else) should return the same type.
This is a common trap when expecting to use an implicit type declaration or when refactoring code that already has an implicit type (var). The current accepted answer is very much valid but reducing all expression variations into a single 1-liner linq expression can easily impact on readability of the code.
The issue in this case is complicated by the projection to an anonymous type at the end of the query, which can be solved by using an explicit type definition for the projection, but it is simpler to break up the query construction into multiple steps:
var customerQuery = _context.Customers().AsQueryable();
if (Type == 1)
customerQuery = customerQuery.Where(t => t.type == "current");
else
customerQuery = customerQuery.Where(...);
... // any other filtering or sorting expressions?
var lstCurrent = customQuery.Select(c => new { c.LastName, c.City});
return View(lstCurrent.ToList());
or of course that last segment could have been a one liner or if there are no further references to lstCurrent the compiler may optimise that into the following:
return View(customQuery.Select(c => new { c.LastName, c.City}).ToList());
In this example I have deliberately cast to IQueryable<T> to ensure this solution is compatible with both IQueryable<T>/DbSet<T> contexts and repository style IEnumerable<T> contexts.
This variation is usually the first that comes to mind, but we are still declaring the source of the query in two places, which increases the ambiguity of this code and the risk of divergence in later refactoring (by accidentally editing only one branch and not maintaining the code in the other branch):
IQueryable<Customer> customerQuery = null;
if (Type == 1)
customerQuery = _context.Customers().Where(t => t.type == "current");
else
customerQuery = _context.Customers().Where(...);
... // any other filtering or sorting expressions?
var lstCurrent = customQuery.Select(c => new { c.LastName, c.City});
return View(lstCurrent.ToList());
A different solution is to explicitly define the output as its own concrete class:
public class CustomerSummary
{
public string LastName { get;set; }
public string City { get;set; }
}
...
List<Customers> customers = null;
if (Type == 1)
customers = _context.Customers().Where(c => c.type == "current")
.Select(c => new CustomerSummary
{
LastName = c.LastName,
City = c.City
}).ToList();
else
customers = _context.Customers().Where(c => ...)
.Select(c => new CustomerSummary
{
LastName = c.LastName,
City = c.City
}).ToList();
... // any other filtering or sorting expressions?
return View(customers);
It's a lot of code for a once-off, but if you make it abstract enough it could be re-used for other scenarios, I would still combine this with the first code example, that keeps the source, filter and projection logic separated, over the lifetime of an application these 3 elements tend to evolve differently, so separating out the code makes refactoring or future maintenance easier to complete and review.
I have a large select expression to reuse in several classes. For the DRY principle I have chosen to create a property that returns the Expression to the caller code
protected virtual Expression<Func<SezioneJoin, QueryRow>> Select
{
get
{
return sj => new QueryRow
{
A01 = sj.A.A01,
A01a = sj.A.A01a,
A01b = sj.A.A01b,
A02 = sj.A.A02,
A03 = sj.A.A03,
A11 = sj.A.A11,
A12 = sj.A.A12,
A12a = sj.A.A12a,
A12b = sj.A.A12b,
A12c = sj.A.A12c,
A21 = sj.A.A21,
A22 = sj.A.A22,
..............
Lots of assignements
};
}
}
Now I can successfully use the property if I do
var query = dataContext.entity.Join(...).Where(x => ...).Select(Select);
But the following will not compile:
from SezioneJoin sj in (
from A a in ...
join D d in ... on new { ... } equals new { ... }
where
d.D13 == "086" &&
!String.IsNullOrEmpty(a.A32) && a.A32 != "086"
orderby a.A21
orderby a.prog
select new SezioneJoin{...})
select Select
Error is
Unable to cast 'System.Linq.IQueryable<System.Linq.Expressions.Expression<System.Func<DiagnosticoSite.Data.Query.SezioneJoin,DiagnosticoSite.Data.Query.QueryRow>>>' into 'System.Linq.IQueryable<DiagnosticoSite.Data.Query.QueryRow>'
I can understand that the LINQ syntax requires the body of the select statement to be the inner type of the IQueryable that it returns, so the compiler is fooled into returning a list of expressions. With the Lambda syntax, the expression is a parameter that is either compiled in-line or returned by some other method (even dynamically!).
I would like to ask if there is any way to circumvent this and avoid defining large select expressions inline
protected virtual Expression> Select
I'd avoid using the names of any of the Linq-mapped methods (Select, Where, GroupBy, OrderBy, OrderByDescending) as member names. It works in this case, but when it causes problems by matching the definitions for those it can be confusing if you aren't in the habit of just not using those names unless you deliberately want to override Linq.
On a related note. Consider that:
from var item in source select item.Something
is equivalent to:
source.Select(item => item.Something);
Therefore:
from SezioneJoin sj in (/*…*/) select Select;
is equivalent to:
(/*…*/).Select(sj => Select);
That is, you arent' creating a query that executes the expression in Select, but one that returns the expression itself.
You should either just use the form .Select(Select) or use select sj => (Select)(sj) but that second one will (if I even have the parentheses correct to stop it clashing with Queryable.Select, I haven't tested that) call the Select property every time so is at best wasteful and at worse not going to be something a query provider can make use of, so it will fail with most linq-providers. In all, use the .Select(Select) form (and change the name).
(On a separate note, if you're going to buffer an expression, actually buffer it; create a private Expression<Func<SezioneJoin, QueryRow>> once and return it in the property's getter, rather than creating it every time).
Simply use extension method in place of last LINQ select statement:
var query = from SezioneJoin sj ... select new SezioneJoin{...});
var projection = query.Select(Select);
I would like to create a repository model that could take an Expression and use Linq-To-Sql to generate the required SQL statement.
For example, I have a function such as this:
// Possible criteria
Expression<Func<Purchase,bool>> criteria1 = p => p.Price > 1000;
// Function that should take that criteria and convert to SQL statement
static IEnumerable<Customer> GetCustomers (Expression<Func<Purchase,bool>> criteria)
{
// ...
}
Inside the function, I would like to convert criteria to a SQL statement using Linq-To-Sql.
I am aware that you can use DataContext.Log to see the executed queries and DataContext.GetCommand(query).CommandText to see the full query before it is executed. However, I would like just a part of the entire expression generated.
What I am hoping to accomplish is to make my repository abstract the underlying technology (Linq-to-Sql, Dapper, etc). That way I could pass the Expression to the repository, have it generate the right statement and use the right technology to execute it.
You could do something like this:
string sql = DataContext.GetTable<Customer>().Where(criteria).ToString();
ToString() gives you the SQL expression. You could then use regex to pull out the WHERE clause.
This is a code excerpt that I use to build my own predicate to use in the Where function. The compiler can't cope with ienumerables of complex objects, so you have to do it yourself.
Essentially, the code gets passed an ienumerable of (string code, string exchange) tuples, and then builds an expression to retrieve all Security objects that have Security.Code == tuple.Code AND (Security.MasterExchangeForStocksId == tuple.exchange OR SecurityExchangeId == tuple.exchange).
CreateTrEntitiesAsync() simply returns a Entity Framework context, which has a DbSet Security property.
public async Task<Security[]> GetSecurities(IEnumerable<(string code, string exchange)> tickers)
{
using (var ctx = await CreateTrEntitiesAsync())
{
var securityExpr = Expression.Parameter(typeof(Security), "security");
Expression expr = null;
Expression exprToadd;
foreach (var item in tickers)
{
exprToadd = Expression.And(
Expression.Equal(Expression.Property(securityExpr, nameof(Security.Code)), Expression.Constant(item.code)),
Expression.Or(
Expression.Equal(Expression.Property(Expression.Property(securityExpr, nameof(Security.Exchange)), nameof(Exchange.MasterExchangeForStocksId)), Expression.Constant(item.exchange)),
Expression.Equal(Expression.Property(securityExpr, nameof(Security.ExchangeId)), Expression.Constant(item.exchange))
)
);
if (expr == null)
expr = exprToadd;
else
expr = Expression.Or(expr, exprToadd);
}
var criteria = Expression.Lambda<Func<Security, bool>>(expr, new ParameterExpression[] { securityExpr });
var items = ctx.Securities.Where(criteria);
return await items.ToArrayAsync();
}
}
Let's say you have the following code:
string encoded="9,8,5,4,9";
// Parse the encoded string into a collection of numbers
var nums=from string s in encoded.Split(',')
select int.Parse(s);
That's easy, but what if I want to apply a lambda expression to s in the select, but still keep this as a declarative query expression, in other words:
string encoded="9,8,5,4,9";
// Parse the encoded string into a collection of numbers
var nums=from string s in encoded.Split(',')
select (s => {/* do something more complex with s and return an int */});
This of course does not compile. But, how can I get a lambda in there without switching this to fluent syntax.
Update: Thanks to guidance from StriplingWarrior, I have a convoluted but compilable solution:
var result=from string s in test.Split(',')
select ((Func<int>)
(() => {string u="1"+s+"2"; return int.Parse(u);}))();
The key is in the cast to a Func<string,int> followed by evaluation of the lambda for each iteration of the select with (s). Can anyone come up with anything simpler (i.e., without the cast to Func followed by its evaluation or perhaps something less verbose that achieves the same end result while maintaining the query expression syntax)?
Note: The lambda content above is trivial and exemplary in nature. Please don't change it.
Update 2: Yes, it's me, crazy Mike, back with an alternate (prettier?) solution to this:
public static class Lambda
{
public static U Wrap<U>(Func<U> f)
{
return f();
}
}
...
// Then in some function, in some class, in a galaxy far far away:
// Look what we can do with no casts
var res=from string s in test.Split(',')
select Lambda.Wrap(() => {string u="1"+s+"2"; return int.Parse(u);});
I think this solves the problem without the ugly cast and parenarrhea. Is something like the Lambda.Wrap generic method already present somewhere in the .NET 4.0 Framework, so that I do not have to reinvent the wheel? Not to overburden this discussion, I have moved this point into its own question: Does this "Wrap" generic method exist in .NET 4.0.
Assuming you're using LINQ to Objects, you could just use a helper method:
select DoSomethingComplex(s)
If you don't like methods, you could use a Func:
Func<string, string> f = s => { Console.WriteLine(s); return s; };
var q = from string s in new[]{"1","2"}
select f(s);
Or if you're completely hell-bent on putting it inline, you could do something like this:
from string s in new[]{"1","2"}
select ((Func<string>)(() => { Console.WriteLine(s); return s; }))()
You could simply do:
var nums = from string s in encoded.Split(',')
select (s => { DoSomething(); return aValueBasedOnS; });
The return tells the compiler the type of the resulting collection.
How about this:
var nums= (from string s in encoded.Split(',') select s).Select( W => ...);
Can anyone come up with anything
simpler?
Yes. First, you could rewrite it like this
var result = from s in encoded.Split(',')
select ((Func<int>)(() => int.Parse("1" + s + "2")))();
However, that's not really readable, particularly for a query expression. For this particular query and projection, the let keyword could be used.
var result = from s in encoded.Split(',')
let t = "1" + s + "2"
select int.Parse(t);
IEnumerable integers = encoded.Split(',').Select(s => int.Parse(s));
Edit:
IEnumerable<int> integers = from s in encoded.Split(',') select int.Parse(string.Format("1{0}2",s));