How to pass variables to Expression Func Projection before compile - c#

I am trying to write Expression Functions for my projections. I found a good article about that link. But I couldn't figure out how can I pass variables to these functions.
How can I write a projection function for this one
int i = 3;
var query = await _db.Users
.FilterByName(name)
.Select(item => new SearchResultItemViewModel
{
Id = item.Id,
Article = item.FirstName + i.ToString()
});
}))
This one is working. In select SQL string has only Id and Firstname but I can't pass any variable.
var query = await _db.Users
.FilterByName(name)
.Select(item => SearchResultItemViewModel.Projection)
public static Expression<Func<ApplicationUser, SearchResultItemViewModel>> Projection
{
get
{
return item => new SearchResultItemViewModel
{
Id = item.Id,
Article = item.FirstName
};
}
}
This one is working only if you call compile and invoke. SQL string has all rows. Leading to bad performance
var query = await _db.Users
.FilterByName(name)
.Select(item => SearchResultItemViewModel.Projection.Compile().Invoke(item,i))
public static Expression<Func<ApplicationUser,int, SearchResultItemViewModel>> Projection
{
get
{
return( item,i) => new SearchResultItemViewModel
{
Id = item.Id,
Article = item.FirstName + i.ToString()
};
}
}

I don't use EF so this might vary in your specific use case, but this seems to be a question about LINQ Expressions more than anything else.
The first big problem is that you are trying to use an Expression<Func<ApplicationUser, int, SearchResultItemViewModel>> where you really meant Expression<Func<ApplicationUser, SearchResultItemViewModel>> and that's not going to do what you want. Instead of binding to a variable you're invoking the indexed variant of Select. So instead of getting the same value of i for all rows you get the index of the row.
When you create an expression that references a variable, one of two things happens. For local variables (and parameters) the value is copied to an anonymous class instance which is bound to the expression, so you can't change it afterwards. For other variables the expression contains a reference to the variable itself, as well as the containing object for non-static variables.
Which means that you could in principle use a static variable to alter the parameter and never have to recreate the projection expression. There are certainly times when this is useful.
On the other hand, your code above is creating a new instance each time you access the Projection property. So why not just change it to a function and generate the expression you need when you need it?
public static Expression<Func<ApplicationUser, SearchResultItemViewModel>> Projection(int parm)
=> item => new SearchResultItemViewModel
{
Id = item.Id,
Article = item.FirstName + parm.ToString()
};
Each time you invoke the method you'll get back an expression that uses the specified value.
Or you could use an expression visitor to take a template expression and modify the constants in it to whatever you need at the time. Fun, but a bit beyond scope here I think.

Related

Initializing var type in LINQ

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.

C# Linq Assign Anonymous Type in GroupBy

My sql database does not include fault code owners, those details are stored within an xml file Is it possible to Groupby FaultCodeOwner when that data is coming from an external source?
I get the following error: cannot assign void to anonymous type property
var query = referenceDt.AsEnumerable()
.Where(results => declarations.CaapcityIssues.Contains((results.Field<string>("FabricName"))))
.GroupBy(results => new
{
**FaultCodeOwner = faultCodeDetails.getFacultCodeOwner(results.Field<int>("FaultCode"), out owner)**
})
.OrderBy(newFaultCodes => newFaultCodes.Key.FaultCodeOnwer)
.Select(newFaultCodes => new
{
FaultCodeOwner = newFaultCodes.Key.FaultCodeOwner,
Count = newFaultCodes.Count()
});
You cannot group by anything that is not in the database without bringing your query results in memory first.
Inserting ToEnumerable or ToList after the Where method will do that. Unfortunately, you may bring more data in memory than you otherwise would.
Change the GroupBy method to this:
.GroupBy(results =>
{
FaultCodeOwnerType faultCodeOwner; // rename FaultCodeOwnerType to the type of FaultCodeOwner
faultCodeDetails.getFacultCodeOwner(results.Field<int>("FaultCode"), out faultCodeOwner);
return new
{
FaultCodeOwner = faultCodeOwner
};
})
faultCodeDetails.getFacultCodeOwner returns void, so you can't assign a variable to it. You have to first declare a variable of the FaultCodeOwner's type then pass it as an out parameter to getFacultCodeOwner, which will assign it for you.

LINQ group by query using reflected property name

I want to populate a drop down with the public properties of a particular object, which I have done fine. But now when the user selects the value from the dropdown, I want it to group the DB table results by that column. I have tried using LINQ but I can only figure out how to explicitly group by an instance variables property, not by a reflection property. This is my method - the parameter passed in is the string name of the property. Eg it will be "Country" if the user wants to group by Customer.Country, it will be "State" if the user wants to group by Customer.State. But at the moment I have hard coded to group by "State" as I cannot figure out how to use the string value passed in with my LINQ query
private void DisplayReportAction(string category)
{
if (!string.IsNullOrEmpty(category))
{
SelectedCategory = category;
_summaries.Clear();
foreach (var custGroup in _customerInterface.CustomerInterface.GetAllCustomers().GroupBy(c => c.State)
.Select(group => new
{
Category = group.Key,
Count = group.Count()
})
.OrderBy(x => x.Category))
{
_summaries.Add(new CustomerReportSummaryViewModel(custGroup.Category, custGroup.Count));
}
ReportVisibility = Visibility.Visible;
}
}
You can use Reflection if you are using LINQ to Objects, for instance you can use this:
_customerInterface.CustomerInterface.GetAllCustomers()
.GroupBy(c => c.GetType().GetProperty(category).GetValue(c, null))
If you are using Linq To Sql then an alternative is to use dynamic queries, check this link
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
You may build expression dynamically:
Expression<Func<Customer,T>> buildExpression<T>(string category)
{
//First build parameter of lambda ( 'c =>' part of expression)
ParameterExpression param = Expression.Parameter(typeof(Customer), "c");
//Then body of expression : ' => c.category'
Expression<Func<Customer, T>> exp = Expression.Lambda<Func<Customer, T>>(Expression.Property(param, category), param);
return exp;
}
And finally, call
_customerInterface.CustomerInterface.GetAllCustomers()
.GroupBy(buildExpression(category))
EDIT:
Well, sorry you still have to know the type of property to give T type parameter to buildExpression function
There are ways to do this, using for example GetProperty(category).PropertyType and then call MakeGenericMethod on GetType().GetMethod("buildExpression<>"), but this requires a little more work.
Anyway, you'll have to find a way to build CustomerReportSummaryViewModel from this type.
I don't know your use case, but you maybe all categories properties are of the same type, so you could hard-code it ?
If you are interested, and can't find a proper way to do it let me know I'll try to write a proper solution.

Best Practices: Adding properties to a LINQ-to-Entities query result?

I'm writing an ASP.NET Web Pages application and in it, I have a massive LINQ to Entities query. This query pulls data from a table in the database, filters it, groups the data twice, and adds extra properties to the result set. I then loop through the table, outputting the rows.
The query is quite big, sorry:
accountOrders = db.EventOrders
.Where(order => order.EventID == eventID)
.OrderBy(order => order.ProductCode)
.GroupBy(order => new { order.AccountNum, order.Exhibitor, order.Booth })
.Select(orders =>
new {
Key = orders.Key,
ProductOrders = orders
.GroupBy(order => new { order.ProductCode, order.Product, order.Price })
.Select(productOrders =>
new {
Key = productOrders.Key,
Quantity = productOrders.Sum(item => item.Quantity),
HtmlID = String.Join(",", productOrders.Select(o => (o.OrderNum + "-" + o.OrderLine))),
AssignedLines = productOrders.SelectMany(order => order.LineAssignments)
})
})
.Select(account =>
new {
Key = account.Key,
// Property to see whether a booth number should be displayed
HasBooth = !String.IsNullOrWhiteSpace(account.Key.Booth),
HasAssignedDigitalLines = account.ProductOrders.Any(order => order.AssignedLines.Any(line => line.Type == "digital")),
// Dividing the orders into their respective product group
PhoneOrders = account.ProductOrders.Where(prod => ProductCodes.PHONE_CODES.Contains(prod.Key.ProductCode)),
InternetOrders = account.ProductOrders.Where(prod => ProductCodes.INTERNET_CODES.Contains(prod.Key.ProductCode)),
AdditionalOrders = account.ProductOrders.Where(prod => ProductCodes.ADDITIONAL_CODES.Contains(prod.Key.ProductCode))
})
.ToList();
I use the added properties to help style the output. For example, I use HasBooth property to check whether or not I should output the booth location in brackets beside the exhibitor name. The problem is I have to save this big query as an IEnumerable, meaning I get the error: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type. Should I even be manipulating the query this way?
Any advice is much appreciated!
At some point, you are passing in a dynamic datatype to the method, which in turn changes the return type to simply dynamic. You can either cast the dynamic type to a type that is recognised at compile time or explicitly set the return type instead of using var.
You can read more about this issue here: http://www.mikesdotnetting.com/Article/198/Cannot-use-a-lambda-expression-as-an-argument-to-a-dynamically-dispatched-operation

Calling a method inside a Linq query

I want to insert into my table a column named 'S' that will get some string value based on a value it gets from a table column.
For example: for each ID (a.z) I want to gets it's string value stored in another table. The string value is returned from another method that gets it through a Linq query.
Is it possible to call a method from Linq?
Should I do everything in the same query?
This is the structure of the information I need to get:
a.z is the ID in the first square in table #1, from this ID I get another id in table #2, and from that I can get my string value that I need to display under column 'S'.
var q = (from a in v.A join b in v.B
on a.i equals b.j
where a.k == "aaa" && a.h == 0
select new {T = a.i, S = someMethod(a.z).ToString()})
return q;
The line S = someMethod(a.z).ToString() causing the following error:
Unable to cast object of type 'System.Data.Linq.SqlClient.SqlColumn'
to type 'System.Data.Linq.SqlClient.SqlMethodCall'.
You have to execute your method call in Linq-to-Objects context, because on the database side that method call will not make sense - you can do this using AsEnumerable() - basically the rest of the query will then be evaluated as an in memory collection using Linq-to-Objects and you can use method calls as expected:
var q = (from a in v.A join b in v.B
on a.i equals b.j
where a.k == "aaa" && a.h == 0
select new {T = a.i, Z = a.z })
.AsEnumerable()
.Select(x => new { T = x.T, S = someMethod(x.Z).ToString() })
You'll want to split it up into two statements. Return the results from the query (which is what will hit the database), and then enumerate the results a second time in a separate step to transform the translation into the new object list. This second "query" won't hit the database, so you'll be able to use the someMethod() inside it.
Linq-to-Entities is a bit of a strange thing, because it makes the transition to querying the database from C# extremely seamless: but you always have to remind yourself, "This C# is going to get translated into some SQL." And as a result, you have to ask yourself, "Can all this C# actually get executed as SQL?" If it can't - if you're calling someMethod() inside it - your query is going to have problems. And the usual solution is to split it up.
(The other answer from #BrokenGlass, using .AsEnumerable(), is basically another way to do just that.)
That is an old question, but I see nobody mention one "hack", that allows to call methods during select without reiterating. Idea is to use constructor and in constructor you can call whatever you wish (at least it works fine in LINQ with NHibernate, not sure about LINQ2SQL or EF, but I guess it should be the same).
Below I have source code for benchmark program, it looks like reiterating approach in my case is about twice slower than constructor approach and I guess there's no wonder - my business logic was minimal, so things like iteration and memory allocation matters.
Also I wished there was better way to say, that this or that should not be tried to execute on database,
// Here are the results of selecting sum of 1 million ints on my machine:
// Name Iterations Percent
// reiterate 294 53.3575317604356%
// constructor 551 100%
public class A
{
public A()
{
}
public A(int b, int c)
{
Result = Sum(b, c);
}
public int Result { get; set; }
public static int Sum(int source1, int source2)
{
return source1 + source2;
}
}
class Program
{
static void Main(string[] args)
{
var range = Enumerable.Range(1, 1000000).ToList();
BenchmarkIt.Benchmark.This("reiterate", () =>
{
var tst = range
.Select(x => new { b = x, c = x })
.AsEnumerable()
.Select(x => new A
{
Result = A.Sum(x.b, x.c)
})
.ToList();
})
.Against.This("constructor", () =>
{
var tst = range
.Select(x => new A(x, x))
.ToList();
})
.For(60)
.Seconds()
.PrintComparison();
Console.ReadKey();
}
}

Categories