How to pass a Linq Expression in lieu a Predicate? - c#

I've been reading articles and SO posts for the last couple of hours about building my own Linq Expressions dynamically, then using those as predicates to filter items in a List or and Array.
Here's a simple example of what I have so far:
public class AWDRiskMRASCodeXref
{
public string RiskSubType { get; set; }
public string AcordReqCodeInt { get; set; }
public string MrasReqCodeInt { get; set; }
public string AcordReqCodePpe { get; set; }
public string MrasReqCodePpe { get; set; }
public string AcordReqCodeWeb { get; set; }
public string MrasReqCodeWebS { get; set; }
}
I will then use something like Dapper to retrieve the list from the database;
var items = conn.Query<AWDRiskMRASCodeXref>("SELECT RiskSubType, AcordReqCodeInt, MrasReqCodeInt, AcordReqCodePpe, MrasReqCodePpe, AcordReqCodeWeb, MrasReqCodeWeb FROM LKUP_AWDRiskMRASCodeXref;").ToList();
var param = Expression.Parameter(typeof(AWDRiskMRASCodeXref), "x");
var member = Expression.Property(param, "AcordReqCodePpe"); //x.AcordReqCodePpe
var constant = Expression.Constant("1004700009");
var body = Expression.Equal(member, constant); //x.AcordReqCodePpe == "1004700009"
var finalExpression = Expression.Lambda<Func<AWDRiskMRASCodeXref, bool>>(body, param); //x => x.AcordReqCodePpe == "1004700009"
finalExpression.Dump();
The finalExpression "x => (x.AcordReqCodePpe == "1004700009")" looks great in LinqPad, but what do I need to do the the express to enable it to be passed the the following Linq query?
var item = items.FirstOrDefault(finalExpression);
Thank you,
Stephen

Since you've already pulled the data from your database, all you need to do for your expression to work is to compile it and pass it to FirstOrDefault extension method instead.
var compiledExpression = finalExpression.Compile();
var item = items.FirstOrDefault(compiledExpression);
As a bonus tip, if you're using .NET Framework 4.6 or higher. You could use the nameof operator while generating your member property. It's much better than the hardcoded string, if you ever change the name of your property. It'd look like this:
var member = Expression.Property(param, nameof(AWDRiskMRASCodeXref.AcordReqCodePpe));

The problem is that you have already 'resolved' the query when you called .ToList() on the query. Remove that so that your query reads something like:
var item = conn.Query<AWDRiskMRASCodeXref>("SELECT RiskSubType, AcordReqCodeInt, MrasReqCodeInt, AcordReqCodePpe, MrasReqCodePpe, AcordReqCodeWeb, MrasReqCodeWeb FROM LKUP_AWDRiskMRASCodeXref;")
.FirstOrDefault(finalExpression);

Related

Problem on using containers within models C#

I try to use models to get my containers on stored procedure executions , but it says that SP on SP.GR0007R, which is variable of the models, is a variable but used like a type, but if i use Cont.ReportManager.GR0007R instead, no errors found but no result either.
This is my code:
using (var db = new SqlConnection(connStr))
{
var param = new DynamicParameters();
switch (filter.RPTCode)
{
case "GR0007R":
param.Add("#Name", !string.IsNullOrEmpty(filter.NAME) ? filter.NAME : string.Empty);
var sp7r = await db.QueryAsync<SP.GR0007R>(filter.RPTQuery, param, commandType: CommandType.StoredProcedure);
break;
}
res = true;
}
this is where i declare SP
public async Task<bool> GetReportData(Models.ReportManager.EswisReportModel SP, GetListParameterReportList filter)
and this is my model
public class EswisReportModel
{
public Cont.ReportManager.GR0001R GR0001R { get; set; }
public Cont.ReportManager.GR0002R GR0002R { get; set; }
public Cont.ReportManager.GR0007R GR0007R { get; set; }
public Cont.ReportManager.GR0008R GR0008R { get; set; }
public Cont.ReportManager.GR0009R GR0009R { get; set; }
}
so do i have to convert it or there's other way to call it?
thank you
It seems you are trying to put in an instance and property name, and hope that the generic handling will be so smart to know you mean the backing type of that property. It doesn't. According to the C# specification, you need to use the type name, not a reference to it, nor some kind of inferred use of it through a variable name.
So that is why you should use the actual type name, Cont.ReportManager.GR0007R:
var sp7r = await db.QueryAsync<Cont.ReportManager.GR0007R>(...);

LinQ to view in MVC

In my controller I have a method that looks like this:
var CarModelAndGear = from search in entity.CarsCategories
where search.ID == CarCategoryId[0]
select new { search.Manufacturer, search.Model, search.Gear };
I'm trying to get the output of this statement to the view..
ViewData["somename"] = CarModelAndGear;
This way won't work.. I'm getting an error "LINQ to entities does not recognize the method 'Int32 get_Item(Int32)' method, and this method cannot be translated into a store expression. "
How can I get 'CarModelAndGear' to the view?
You need to store the category in a variable first because the provider is not able to interpret the list indexer method:
int CatId = CarCategoryId[0];
var CarModelAndGear = from search in entity.CarsCategories
where search.ID == CatId
select new { search.Manufacturer, search.Model, search.Gear };
You are using LinqToSql here. So what it is trying to do is convert that Linq expression into a SQL statement.
While you can access an item in a collection from C#, this cannot be translated into SQL, so it falls over.
Try this rather:
var id = CarCategoryId[0];
var CarModelAndGear = from search in entity.CarsCategories
where search.ID == id
select new { search.Manufacturer, search.Model, search.Gear };
You can execute linq and then assign to ViewData
ViewData["somename"] = CarModelAndGear.ToList();
But i prefer approach described by live2, when you will combine everything to model and then pass the model
Create a model for your result
public class CarViewResult
{
public string Manufacturer { get; set; }
public string Model { get; set; }
public string Gear { get; set; }
public CarViewResult(string manufacturer, string model, string gear)
{
this.Manufacturer = manufacturer;
this.Model = model;
this.Gear = gear;
}
}
Add add the new model to your LINQ query
var CarModelAndGear = from search in entity.CarsCategories
where search.ID == CarCategoryId[0]
select new CarViewResult(search.Manufacturer, search.Model, search.Gear);
ViewData["somename"] = CarModelAndGear.ToList();

C# Multiple Parameter From Single Expression

Do any one know how to send multiple parameter from single expression.
Please refer to my below class;
public class EMP
{
public string NAME { get; set; }
public String FULLNAME { get; set; }
}
I would like to send selective property to a function, something like below.
SendColumn<EMP>(a=>{cl.NAME,cl.FULLNAME})
or
SendColumn<EMP>(cl =>cl.NAME,cl.FULLNAME)
Right now using the below function, I can only send one parameter per expression
public List<TRow> SendColumn<TValue>(Func<TRow, TValue> expression )
{
// do some processing
}
Try using this syntax:
SendColumn(e => new EMP { NAME = "testName", FULLNAME = "TestFullName" });
If that doesn't work, please elaborate on your context and what error you are getting.

Unable to use use .Except on two List<String>

I am working on an asp.net mvc-5 web applicatio. i have these two model classes:-
public class ScanInfo
{
public TMSServer TMSServer { set; get; }
public Resource Resource { set; get; }
public List<ScanInfoVM> VMList { set; get; }
}
public class ScanInfoVM
{
public TMSVirtualMachine TMSVM { set; get; }
public Resource Resource { set; get; }
}
and i have the following method:-
List<ScanInfo> scaninfo = new List<ScanInfo>();
List<String> CurrentresourcesNames = new List<String>();
for (int i = 0; i < results3.Count; i++)//loop through the returned vm names
{
var vmname = results3[i].BaseObject == null ? results3[i].Guest.HostName : results3[i].BaseObject.Guest.HostName;//get the name
if (!String.IsNullOrEmpty(vmname))
{
if (scaninfo.Any(a => a.VMList.Any(a2 => a2.Resource.RESOURCENAME.ToLower() == vmname.ToLower())))
{
CurrentresourcesNames.Add(vmname);
}
}
}
var allcurrentresourcename = scaninfo.Select(a => a.VMList.Select(a2 => a2.Resource.RESOURCENAME)).ToList();
var finallist = allcurrentresourcename.Except(CurrentresourcesNames).ToList();
now i want to get all the String that are inside the allcurrentrecoursename list but not inside the CurrentresourcesName ?
but that above code is raising the following exceptions :-
Error 4 'System.Collections.Generic.List>'
does not contain a definition for 'Except' and the best extension
method overload
'System.Linq.Queryable.Except(System.Linq.IQueryable,
System.Collections.Generic.IEnumerable)' has some invalid
arguments
Error 3 Instance argument: cannot convert from
'System.Collections.Generic.List>'
to 'System.Linq.IQueryable'
It looks to me like
var allcurrentresourcename = scaninfo.Select(a => a.VMList.Select(a2 => a2.Resource.RESOURCENAME)).ToList();
is not a list of strings at all like you seem to expect it to be. scaninfo is of type List<ScanInfo>, and the lambda expression
a => a.VMList.Select(a2 => a2.Resource.RESOURCENAME)
yields one IEnumerable<TSomething> for each ScanInfo object. So it would seem that allcurrentresourcename is not a List<string>, but rather a List<IEnumerable<TSomething>>, where TSomething is the type of RESOURCENAME (most likely string).
Edit: What you presumably want to use here is the SelectMany LINQ method (see #pquest's comment). It flattens the lists that you get to "one big list" of resource names, which you can then use Except on:
var allcurrentresourcename = scaninfo.SelectMany(a => a.VMList.Select(
b => b.Resource.RESOURCENAME));
You shouldn't even need the ToList() at the end of the line.

Lambda expression on array property

Use-case:
PHP symfony project which has to communicate with a C# back-end with Mongo. In the PHP front-end it is possible to make a query to get data from Mongo. This query is send via an API (XML). The C# back-end deserializes this XML to get the objects. Then I want to execute an Linq-to-objects query (which is the query send via the API) on a collection in my memory. So I wanted to make my own "LinqBuilder" so I can query the objects and return them to my PHP front-end.
I have the following object:
public class MongoDoc
{
public int Id { get; set; }
public string Kind { get; set; }
public BsonDocument Data { get; set; }
}
Below is an example of what I'm trying to achieve.
var list = source.Where(x => x.Data["Identifier"] == "H7PXXK").ToList(); // source is collection of MongoDoc objects
The code line above is what I want to build with expressions because it has to be dynamic. What I did achieve is to query the "Kind" property of my MongoDoc object as follow:
ParameterExpression _expr = Expression.Parameter(typeof(MongoDoc), "x");
expression = Expression.Equal(
Expression.PropertyOrField(_expr, "Kind"),
Expression.Constant("KindValue")
);
This will produce the following lamdba:
x => (x.Kind == "KindValue")
That is correct, but now I need to get the property Identifier in the BsonDocument property Data. Normally it would be something like above: x => x.Data["Identifier"] == "Value". This is exactly what my problem is. How can I achieve this?
think it should be something like that.
var _expr = Expression.Parameter(typeof(MongoDoc), "x");
//x.Data
Expression member = Expression.PropertyOrField(_expr, "Data");
//x.Data["Identifier"]
member = Expression.Property(member, "Item", new Expression[]{Expression.Constant("Identifier")});
//x.Data["Identifier"] == "H7PXXK"
member = Expression.Equal(member, Expression.Constant((BsonValue)"H7PXXK"));
EDIT :
from your comment, it should be
//x.Data["MoreData"]
member = Expression.Property(member, "Item", new Expression[]{Expression.Constant("MoreData")});
//x.Data["MoreData"]["Identifier"]
member = Expression.Property(member, "Item", new Expression[]{Expression.Constant("Identifier")});

Categories

Resources