Is there a way to specify a field name programatically in linq? - c#

I know about dynamic LINQ, but I'm wondering if there's a way to do this without building up a string of a query. I'd like to be able to use all of those nice built in LINQ calls like Contains, Count, Distinct, etc without having to worry about the SQL needed to create them. What I want to do is:
AdventureWorks2008R2Entities AWE = new AdventureWorks2008R2Entities();
var query = AWE.Employees.AsQueryable();
object FieldToQuery = ?;
if (textBox1.Text != "") query = query.Where(x => x.FieldToQuery.Contains(textBox1.Text));
Is something like this possible some how, or is it going against the fundamentals of LINQ?

Consider using PredicateBuilder from LINQKit as an easier alternative to building the expression tree by hand:
var predicate = PredicateBuilder.True<Employee>();
if (textBox1.Text != "") {
var txt = textBox1.Text;
predicate = predicate.And(e => e.Field1ToQuery.Contains(txt));
}
if (textBox2.Text != "") {
var txt = textBox2.Text;
predicate = predicate.And(e => e.Field2ToQuery.Contains(txt));
}
var AWE = new AdventureWorks2008R2Entities();
var query = AWE.Employees.AsExpandable().Where(predicate);
(The var txt is needed because under the covers these expressions are translated into the corresponding SQL (simplified):
SELECT *
FROM Employees
WHERE Field1ToQuery LIKE '%' + #p_1 + '%'
where the parameters are filled with the value of the variable. SQL Server has no knowledge of the textbox on the client-side -- the following SQL will fail:
SELECT *
FROM Employees
WHERE Field1ToQuery LIKE '%' + textBox1.Text + '%'
and (I suppose) passing the entire textbox as a parameter would be too complex.)

You can use
https://github.com/PoweredSoft/DynamicLinq
Nuget package
https://www.nuget.org/packages/PoweredSoft.DynamicLinq/
And do
queryable.Query(t => t.Contains("PropertyOrField", "search value"));
if you want to query multiple fields you can do
queryable.Query(t => t.Contains("FirstName", "Dav").OrContains("FirstName", "Jo"));

Related

LINQ is converting string to special characters

I am using this query to get data from database.
string nfc = "53f8372c";
var temp = db.tempTable.AsNoTracking().Where(
x =>
x.uid.Equals(nfc, StringComparison.CurrentCultureIgnoreCase)
&& x.ENDED == null
&& x.STATUS.Equals(Constants.ACTIVE)
);
The sql that is generated from this query is:
{SELECT
"Extent1"."ID" AS "ID",
"Extent1"."uid" AS "uid",
"Extent1"."ENDED" AS "ENDED",
"Extent1"."STATUS" AS "STATUS",
FROM "tempTable" "Extent1"
WHERE (("Extent1"."uid" = :p__linq__0) AND ("Extent1"."ENDED" IS NULL) AND ('Active' = "Extent1"."STATUS"))}
Why does it convert 53f8372c to :p__linq__0?
That's just parameterizing the SQL. If you look at the parameters passed to the query, you'll see that :p__linq__0 has a value of 53f8372c.
This parameterization is helpful, as the server can cache the query plan and reuse it for the same query using different parameter values.

ServiceStack ormLite chaning OrderBy

I am trying to so the following:
var routines = con.Select<Table>(con.From<Table>().OrderBy(p => p.Field1).ThenBy(i => i.Field2));
The above works perfectly. But I want a rather more generic approach and parse a string like sort="field1,field2". I have the following code:
int sortFieldCount = 0;
var itemsq = con.From<Table>();
foreach (var name in orderByField.Split(',')) {
if(sortFieldCount == 0)
itemsq = sortOrderAscending ? itemsq.OrderBy(name) : itemsq.OrderByDescending(name);
else
itemsq = sortOrderAscending ? itemsq.ThenBy(name) : itemsq.ThenByDescending(name);
sortFieldCount++;
}
But the above code seems to overwrite the first OrderBy. Is there a solution to such a problem?
Thanks
Other ways you can perform multiple Order By's with ServiceStack.OrmLite include:
var orderByAnonType = db.Select(db.From<Track>().OrderBy(x => new { x.Album, x.Name }));
var orderByString = db.Select(db.From<Track>().OrderByFields("Album","Name"));
// Use `-` prefix to inverse sort order, e.g. Album Descending
var orderByString = db.Select(db.From<Track>().OrderByFields("-Album","Name"));
var orderByArray = db.Select(db.From<Track>().OrderBy(x => new[]{ "Album","Name" }));
So you could get a flexible OrderBy like AutoQuery's OrderBy with:
var fields = orderByString.Split(',', StringSplitOptions.RemoveEmptyEntries);
q.OrderByFields(fields);
Here's a live example of this you can play around with on gistlyn.com
There is a couple problems in the accepted answer I'd like to address.
First is the possibility of SQL injection attacks. ServiceStack does not fully validate what you pass in to the list of sort columns. While it will detect some of the more obvious attacks, you could still slip in things like calls to stored functions.
The second problem is descending sorts. It's not obvious from the API, but you can pass in "columnName DESC" rather than just "columnName". In fact, this is how it is able to support "Album,Name", it just passes it directly to SQL with the barest amount of validation.
public IList<Employee> SortBy(string lastName, string sortByColumnA, bool isDescendingA, string sortByColumnB, bool isDescendingB)
{
if (!Utilities.EmployeeColumnNames.Contains(sortByColumnA))
throw new ArgumentOutOfRangeException(nameof(sortByColumnA), "Unknown column " + sortByColumnA);
if (!Utilities.EmployeeColumnNames.Contains(sortByColumnB))
throw new ArgumentOutOfRangeException(nameof(sortByColumnB), "Unknown column " + sortByColumnB);
var sortDirectionA = isDescendingA ? " DESC " : "";
var sortDirectionB = isDescendingB ? " DESC " : "";
using (var db = _dbConnectionFactory.OpenDbConnection())
{
return db.Select(db.From<Employee>().Where(x => x.LastName == lastName)
.OrderBy(sortByColumnA + sortDirectionA + "," + sortByColumnB + sortDirectionB)).ToList();
}
}

LINQ for dynamic columns in a table in c#

I hava table with multiple vehicle columns
My corresponding SQL Query is
sQuery = "Select * from Vehicle where " + variant + "='Y'";
How can I write the same query in LINQ?
You can build lambda expression dynamically by using System.Linq.Expression namespace and pass it to Where method.
For example:
public IQueryable<Vehicle> GetAccounts(string variant)
{
// Build equivalent lambda to 'param => param.{variant} == "Y"'
// This is lambda parameter
var param = Expression.Parameter(typeof(Vehicle));
// This is lambda body (comparison)
var body = Expression.Equal(
// Access property {variant} of param
Expression.Property(param, typeof(Vehicle).GetProperty(variant)),
// Constant value "Y"
Expression.Constant("Y")
);
// Build lambda using param and body defined above
var lambda = Expression.Lambda<Func<Vehicle, bool>>(body, param);
// Use created lambda in query
return VehicleDatatable.Where(lambda);
}
Another idea is to convert query a bit.
You can realize same work using following query:
sQuery = "Select * from Vehicle where (Tracks+Cars+Utility) LIKE '" + value + "'";
Where value is 'Y__' or 'Y' or '__Y' depending on which vehicle type you want to query. It's definitely not most effective, but that is pretty easy to convert to linq.
Build your query:
var query = Vehiclelist;
if (column == "Trucks")
{
query = query.Where(q => q.Trucks=="Y");
}
else if (column == "Cars")
{
query = query.Where(q => q.Cars=="Y");
}
else if (column == "Utility")
{
query = query.Where(q => q.Utility=="Y");
}
This approach is more maintainable, more testable. You have strong-typed expressions and transparent filters.
using lambda expression :
VehicleDatatable.AsEnumerable().Where(q=>q.Trucks=="Y" || q.Cars=="Y" || q.Utility=="Y");
Another way
(from d in VehicleDatatable.AsEnumerable() where string.compare(d["Trucks"],"Y")==0 select d)
Try this:
Vehiclelist.Where(q => q.Trucks=="Y" || q.Cars=="Y" || q.Utility=="Y");
This may also be another approach:
PropertyInfo pi = typeof(Vehicles).GetProperty(vehVariant);
var services = context.Vehicles.ToList();
services = services.Where(item => pi.GetValue(item).ToString().Trim() == "Y").ToList();
Happy Coding.

Concatenate string in Linq query

I have a db call that returns me an object. I use linq to cast that object how I want it
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count)
.Select(c => new
{
Text = c.TariffName,
Key = c.TariffId,
Price = c.LineRental
});
var list = result.ToList();
I now want to add the line rental to the tariff name show that it shows like:
myTariff - 12.99
when I try and do this though I can make this change ok:
Text = c.TariffName + " - ",
but when I try and add the line rental I get problems that linq won't recognise the ToString(). I need it to look like:
Text = c.TariffName + " - " + c.LineRental.ToString(),
I understand that linq won't recognise the ToString() method from reading LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression but how do I change this given I can't set it as a string prior to the linq query?
Convert the query result to a list first then use select to make the toString work.
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count);
var list = result.ToList().Select(c => new
{
Text = c.TariffName + " - " + c.LineRental.ToString(),
Key = c.TariffId,
Price = c.LineRental
});
What is happening linq trying to execute your select statement on the database query level, and it does not know how to transform your .Select lambda to select part of sql statement.
Easiest thing you can do is first query required fields, call .ToList() on query to execute it and perform projection after that.
var result = queryResult.OrderBy(c => c.TariffName)
.Take(count)
.Select(c => new
{
Text = c.TariffName,
Key = c.TariffId,
Price = c.LineRental
});
var list = result.ToList().Select(c=>new {
Text = string.Format("{0} - {1}", c.Text, c.Price),
Key=Key,
Price=Price
});

How to inner join(filtering for nested properties) with expression tree?

My users should be able to configure a filter to get a result from the database.
I decided to use an expression tree, to be flexible with the query. But I have problems with the expression tree for filtering by ReferenceCode.
Entities:
PumpStartUp
Duration (TimeSpan)
DutDbId (Guid/FK)
TimeStamp (DateTime)
DUT (DeviceUnderTest)
DeviceUnderTest
AdminId (string/unique)
DbId (Guid/PK)
ReferenceCode (string)
StartUps (List)
Here is a part of the filter in equivalent linq. But linq I can't use, because I don't know how many ReferenceCodes will be defined by the user.:
//-------------------reference code filter---------------------------
var query = context.PumpStartUps.Where((p => p.DUT.ReferenceCode == "HKR566" ||
p.DUT.ReferenceCode == "HSH967" ||
.
.
.));
startUps = query.ToList();
For filtering by DeviceUnderTest part of the filter, my solution is:
// --------------------duts filter-----------------------------------
Expression dutsExpression = null;
Expression psuExpression = Expression.Parameter(typeof(PumpStartUp), "psu");
Expression psuDutIdExpression = Expression.Property(psuExpression, "DutDbId");
foreach (var dut in filter.Duts)
{
DeviceUnderTest deviceUnderTest = context.DevicesUnderTest.Where(d => d.AdminId == dut.Id).Single();
Expression dutIdExpression = Expression.Constant(deviceUnderTest.DbId);
Expression dutExpression = Expression.Equal(pumpStartUpDutIdExpression, dutIdExpression);
if (dutsExpression == null)
{
dutsExpression = dutExpression;
}
else
{
dutsExpression = Expression.Or(dutsExpression, dutExpression);
}
}
How can I filter by ReferenceCode in that manner:
Use this:
var dutExpression = Expression.Property(psuExpression, "DUT");
var referenceCodeExp = = Expression.Property(dutExpression, "ReferenceCode ");
var constExpr = Expression.Constant("HKR566");
var eqExp = Expression.Equal(referenceCodeExp , constExpr);
dutsExpression = Expression.Or(dutsExpression, eqExp);
If you have a limited amount of codes, you can always say
var query = context.PumpStartUps.Where(p => codes.Contains(p.DUT.ReferenceCode))
This works up until about 2000 parameters. If you need more, then you should probably send the codes somehow to a temp table (or rather a function returning a table, since ef does not support temp tables), and join on that since constructing an expression with more than 2000 ors is not gonna perform well.

Categories

Resources