How to "inject" a additional where clause into a given nHibernate linq? - c#

I have a given linq-sql like this:
var erg = from p in m_session.Query<LovTestData>()
select new
{
SomeString = p.SomeString,
SomeOtherString = p.SomeOtherString
};
This should be the "base"-query for a Lov-Dialog. So this is the query which defines the content of the Lov.
But there are fields in the LOV to search. So this is the query I have to use to fill the Lov at runtime:
var erg = from p in m_session.Query<LovTestData>()
where ((string.IsNullOrEmpty(someStringValueFilter) || p.SomeString.ToLower().Contains(someStringValueFilter.ToLower())) &&
(string.IsNullOrEmpty(someOtherStringFilter) || p.SomeOtherString.ToLower().Contains(someOtherStringFilter.ToLower())))
select new
{
SomeString = p.SomeString,
SomeOtherString = p.SomeOtherString
};
So I wonder how its possible to "inject" the where clause afterwards into the given query? This is how I think it should look like:
var erg = from p in m_session.Query<LovTestData>()
select new
{
SomeString = p.SomeString,
SomeOtherString = p.SomeOtherString
};
var additionalWhere = ... //Some way to define this part: ((string.IsNullOrEmpty(someStringValueFilter) || p.SomeString.ToLower().Contains(someStringValueFilter.ToLower())) && (string.IsNullOrEmpty(someOtherStringFilter) || p.SomeOtherString.ToLower().Contains(someOtherStringFilter.ToLower())))
erg = InjectWhere(erg, additionalWhere); //In this function the where is inserted into the linq so the result is the second query.
Updated:
The additionalWhere should be constructed out of the original query. So its not possible for me to write "p.SomeString" because the construction of the additionalWhere is universal. This is the way I get the fields
Type elementType = erg.ElementType;
foreach (PropertyInfo pi in elementType.GetProperties())
{
//pi.name...
}

If Query returns IQueryable then there is no problem at all.
List<LovTestData> GetTestData(Expression<Func<T, bool>> where)
{
var erg = from p in m_session.Query<LovTestData>()
select new
{
...
}
IQueryable result = erg.Where(where);
return result.ToList();
}
Now. IQueryable will NOT EXECUTE unitl you really use it. So you can do select, where, union and so on, but until you use the IQueryable it won't do anything. Here the real SQL will run in: result.ToList(). That's why you can build all there conditions earlier. Of course assuming that m_session.Query returns IQueryable but as far as I remember - it does.
So you can even do this without result variable that I have created. Just operate on erg.
Waayd's comment will also work.
OK, now about creating filter dynamically.
Let's take a simple class:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Now, let's create a list of records - database.
List<Person> list = new List<Person>
{
new Person {Name = "Adam Abc", Age = 15},
new Person {Name = "John Abc", Age = 23},
new Person {Name = "Steven Abc", Age = 26},
new Person {Name = "Adam Bca", Age = 21},
new Person {Name = "Adam Xyz", Age = 26},
};
Now, let's prepare a filter. You have to get filter data from a view, now let's just simulate this:
string nameIs = "Adam";
bool createAgeFilter = true;
int ageFilterMin = 20;
int ageFilterMax = 25;
So we need all Adam's that are in age between 20 and 25. Let's create this condition:
First condition on name:
Func<Person, bool> whereName = new Func<Person, bool>((p) =>
{
if (!string.IsNullOrWhiteSpace(nameIs))
return p.Name.Contains(nameIs);
else
return true;
}
);
Next condition on age:
Func<Person, bool> whereAge = new Func<Person, bool>((p) =>
{
if (createAgeFilter)
return p.Age >= ageFilterMin && p.Age <= ageFilterMax;
else
return true;
}
);
Next, let's have our IQueryable:
IQueryable<Person> q = list.AsQueryable();
And finally let's add where clauses:
List<Person> filteredList = q.Where(whereName)
.Where(whereAge)
.ToList();
That's it. The idea behind this is that you have to create several partial where clauses. Each for one thing you want to filter. But what I've done at the end will make "AND" between the filters. If you would like to "OR" them, you should do it in one other filter type - like in age filter.
I've just came up with this idea. So there may be a better solution. Maybe even some one liner.
Edit
If you can't use linq like that, there is another way. But not so simple.
There MUST be a point somewhere in your application that you build filter in LINQ style. For example in your view. So take this expression and call ToString(). You will get string representation of the linq query.
The next thing you have to do is to install Roslyn package.
Finally you can change string representation of LINQ expression to LINQ expression using some Roslyn magic:
public async static Task<Expression<Func<T, bool>>> ExpressionFromStr<T>(string expressionStr)
{
var options = ScriptOptions.Default.AddReferences(typeof(T).Assembly);
return await CSharpScript.EvaluateAsync<Expression<Func<T, bool>>>(expressionStr, options);
}
Usings:
using Microsoft.CodeAnalysis.CSharp.Scripting; //roslyn
using Microsoft.CodeAnalysis.Scripting; //roslyn
using System;
using System.Linq.Expressions;
using System.Threading.Tasks; //for async and Task.

Related

Multiple condition on same column inside Linq where

How can I write a linq query to match two condition on same column in the table?
Here one person can be assigned to multiple types of works and it is store in PersonWorkTypes table containing the details of persons and their worktypes.
So I need to get the list of persons who have both fulltime and freelance works.
I have tried
people.where(w => w.worktype == "freelance" && w.worktype == "fulltime")
But it returns an empty result.
You can try this
public class Person {
public string Name {get;set;}
public List<PersonWorkType> PersonWorkTypes {get;set;}
}
public class PersonWorkType {
public string Type {get;set;}
}
public static void Main()
{
var people = new List<Person>();
var person = new Person { Name = "Toño", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" } } };
var person2 = new Person { Name = "Aldo", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" }, new PersonWorkType { Type = "fulltime" } } };
var person3 = new Person { Name = "John", PersonWorkTypes = new List<PersonWorkType>() { new PersonWorkType { Type = "freelance" }, new PersonWorkType { Type = "fulltime" } } };
people.Add(person);
people.Add(person2);
people.Add(person3);
var filter = people.Where(p => p.PersonWorkTypes.Any(t => t.Type == "freelance") && p.PersonWorkTypes.Any(t => t.Type == "fulltime"));
foreach(var item in filter) {
Console.WriteLine(item.Name);
}
}
This returns person that contains both types in PersonWorkTypes
AS already said, && operator means, that BOTH conditions has to be met. So in your condition it means that you want worktype type to by freelanceand fulltime at the same time, which is not possible :)
Most probably you want employees that have work type freelance OR fulltime, thus your condition should be:
people.Where(w=>w.worktype=="freelance" || w.worktype =="fulltime")
Or, if person can be set more than once in this table, then you could do:
people
.Where(w=>w.worktype=="freelance" || w.worktype =="fulltime")
// here I assume that you have name of a person,
// Basically, here I group by person
.GroupBy(p => p.Name)
// Here we check if any person has two entries,
// but you have to be careful here, as if person has two entries
// with worktype freelance or two entries with fulltime, it
// will pass condition as well.
.Where(grp => grp.Count() == 2)
.Select(grp => grp.FirstOrDefault());
w.worktype=="freelance"
w.worktype=="fulltime"
These are mutually exclusive to each other, and therefore cannot both be true to ever satisfy your AND(&&) operator.
I am inferring that you have two (or more) different rows in your table per person, one for each type of work they do. If so, the Where() method is going to check your list line-by-line individually and won't be able to check two different elements of a list to see if Alice (for example) both has en entry for "freelance" and an entry for "fulltime" as two different elements in the list. Unfortuantely, I can't think of an easy way to do this in a single query, but something like this might work:
var fulltimeWorkers = people.Where(w=>w.worktype=="fulltime");
var freelanceWorkers = people.Where(w=>w.worktype=="freelance");
List<Person> peopleWhoDoBoth = new List<Person>();
foreach (var worker in fulltimeWorkers)
{
if (freelanceWorkers.Contains(worker)
peopleWhoDoBoth.Add(worker);
}
This is probably not the most efficient way possible of doing it, but for small data sets, it shouldn't matter.

Is there a way to declare a C# lambda and immediately call it?

It's possible to declare a lambda function and immediately call it:
Func<int, int> lambda = (input) => { return 1; };
int output = lambda(0);
I'm wondering if it's possible to do so in one line, e.g. something like
int output = (input) => { return 1; }(0);
which gives a compiler error "Method name expected". Casting to Func<int, int> doesn't work either:
int output = (Func<int, int>)((input) => { return 1; })(0);
gives the same error, and for reasons mentioned below I'd like to avoid having to explicitly specify the input argument type (the first int).
You're probably wondering why I want to do this, instead of just embedding the code directly, e.g. int output = 1;. The reason is as follows: I've generated a reference for a SOAP webservice with svcutil, which because of the nested elements generates extremely long class names, which I'd like to avoid having to type out. So instead of
var o = await client.GetOrderAsync(request);
return new Order {
OrderDate = o.OrderDate,
...
Shipments = o.Shipment_Order == null ? new Shipment[0]
o.Shipment_Order.Select(sh => new Shipment {
ShipmentID = sh.ShipmentID,
...
Address = CreateAddress(sh.ReceiverAddress_Shipment);
}).ToArray()
};
and a separate CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address) method (real names are even longer, and I have very limited control about the form), I'd like to write
var o = await client.GetOrderAsync(request);
return new Order {
OrderDate = o.OrderDate,
...
Shipments = o.Shipment_Order == null ? new Shipment[0]
o.Shipment_Order.Select(sh => new Shipment {
ShipmentID = sh.ShipmentID,
...
Address = sh.ReceiverAddress_Shipment == null ? null : () => {
var a = sh.ReceiverAddress_Shipment.Address;
return new Address {
Street = a.Street
...
};
}()
}).ToArray()
};
I know I could write
Address = sh.ReceiverAddress_Shipment == null ? null : new Address {
Street = sh.ReceiverAddress_Shipment.Address.Street,
...
}
but even that (the sh.ReceiverAddress_Shipment.Address part) becomes very repetitive if there are many fields. Declaring a lambda and immediately calling it would be more elegant less characters to write.
Instead of trying to cast the lambda, I propose you use a small helper function:
public static TOut Exec<TIn, TOut>(Func<TIn, TOut> func, TIn input) => func(input);
which you could then use like this: int x = Exec(myVar => myVar + 2, 0);. This reads a lot nicer to me than the alternatives suggested here.
It's ugly, but it's possible:
int output = ((Func<int, int>)(input => { return 1; }))(0);
Anonymous functions, including lambda expressions, are implicitly convertible to a delegate that matches their signature, but this syntax requires the lambda to be enclosed in parentheses.
The above can be simplified as well:
int output = ((Func<int, int>)(input => 1))(0);
Lambda literals in C# have a curious distinction in that their meaning is dependent on their type. They are essentially overloaded on their return type which is something does not exist anywhere else in C#. (Numeric literals are somewhat similar.)
The exact same lambda literal can either evaluate to an anonymous function that you can execute (i.e. a Func/Action) or an abstract representation of the operations inside of the Body, kind of like an Abstract Syntax Tree (i.e. a LINQ Expression Tree).
The latter is, for example, how LINQ to SQL, LINQ to XML, etc. work: the lambdas do not evaluate to executable code, they evaluate to LINQ Expression Trees, and the LINQ provider can then use those Expression Trees to understand what the body of the lambda is doing and generate e.g. a SQL Query from that.
In your case, there is no way for the compiler to know wheter the lambda literal is supposed to be evaluated to a Func or a LINQ Expression. That is why Johnathan Barclay's answer works: it gives a type to the lambda expression and therefore, the compiler knows that you want a Func with compiled code that executes the body of your lambda instead of an un-evaluated LINQ Expression Tree that represents the code inside the body of the lambda.
You could inline the declaration of the Func by doing
int output = (new Func<int, int>(() => { return 1; }))(0);
and immediately invoking it.
You can also create the alias in the Select method
var o = await client.GetOrderAsync(request);
return new Order {
OrderDate = o.OrderDate,
...
Shipments = o.Shipment_Order == null ? new Shipment[0]
o.Shipment_Order.Select(sh => {
var s = sh.ReceiverAddress_Shipment;
var a = s.Address;
return new Shipment {
ShipmentID = sh.ShipmentID,
...
Address = s == null ?
null :
new Address {
Street = a.Street
...
}
};
}).ToArray()
};
or with the ?? operator
var o = await client.GetOrderAsync(request);
return new Order {
OrderDate = o.OrderDate,
...
Shipments = o.Shipment_Order?.Select(sh => {
var s = sh.ReceiverAddress_Shipment;
var a = s.Address;
return new Shipment {
ShipmentID = sh.ShipmentID,
...
Address = s == null ?
null :
new Address {
Street = a.Street
...
}
};
}).ToArray() ?? new Shipment[0]
};
If you don't mind violating a few of the extension methods design guidelines, extension methods combined with null-conditional operator ?. can take you reasonably far:
public static class Extensions
{
public static TOut Map<TIn, TOut>(this TIn value, Func<TIn, TOut> map)
where TIn : class
=> value == null ? default(TOut) : map(value);
public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> items)
=> items ?? Enumerable.Empty<T>();
}
will give you this:
return new Order
{
OrderDate = o.OrderDate,
Shipments = o.Shipment_Order.OrEmpty().Select(sh => new Shipment
{
ShipmentID = sh.ShipmentID,
Address = sh.ReceiverAddress_Shipment?.Address.Map(a => new Address
{
Street = a.Street
})
}).ToArray()
};
and if you mostly need arrays, then override ToArray extension method to encapsulate a few more method calls:
public static TOut[] ToArray<TIn, TOut>(this IEnumerable<TIn> items, Func<TIn, TOut> map)
=> items == null ? new TOut[0] : items.Select(map).ToArray();
resulting in:
return new Order
{
OrderDate = o.OrderDate,
Shipments = o.Shipment_Order.ToArray(sh => new Shipment
{
ShipmentID = sh.ShipmentID,
Address = sh.ReceiverAddress_Shipment?.Address.Map(a => new Address
{
Street = a.Street
})
})
};

Filter Entity model by any property and value dynamically passed

I have more than 1 Entity framework Model related to patients.
Now i want to Filter records by Some List of Property value.
I will passed List to on which filed i need to apply filter that is dynamically.
Ex: Class with 2 Properties 1) PropertyName & 2) FilterValue
I will pass List of values in below format
Row1) "TableName.PropertyName" & Value = "value to filter"
Row1) "TableName.PropertyName" & Value = "value to filter"
etc...
So i don't know in advance which filed i need to apply filter its dynamically decided and it may be for multiple properties.
When speaking of dynamic for Entity Framework, there is often 2 easy solutions (one free, one paid).
For both solutions, you will need to build a string containing your dynamic projection.
Free
LINQ Dynamic: https://github.com/StefH/System.Linq.Dynamic.Core
A very popular library that allows easily to handle this scenario
var query = context.Customers.Where("City == #0", "London");
I recommend this library if you want to handle most basic scenario
Paid
Disclaimer: I'm the owner of the project Eval-Expression.NET
This library is more powerful by using Expression Tree. It supports exactly the same syntax as C#
var query = context.Customers.Where(c => "c.City < #0", "London");
var query = context.Customers.Where(c => "c.City < city", new { city = "London"});
var query = context.Customers.Where(c => "c.City < city", entityWithCityProperty);
This library can be used for more complex scenarios. It let you compile and execute dynamic C# code at runtime.
You can achieve that using reflection, but be aware that it will be slow on a large dataset.
the output for the following code is :
Name: John, Age: 24
class Patient
{
public int Age { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
var patients = new List<Patient> {
new Patient { Age = 24, Name = "John"},
new Patient { Age = 36, Name = "Johny"},
new Patient { Age = 24, Name = "Jonas"},
new Patient { Age = 18, Name = "John"},
};
var filters = new Dictionary<string,object>();
filters.Add("Age", 24);
filters.Add("Name", "John");
var query = patients.AsQueryable();
foreach (var filter in filters)
{
query = query.Where(p => p.GetType().GetProperty(filter.Key).GetValue(p, null).ToString() == filter.Value.ToString());
}
foreach (var patient in query.ToList())
{
System.Console.WriteLine($"Name: {patient.Name}, Age: {patient.Age}");
}
}

Use reflection to make dynamic LINQ statements in C#

If I have a LINQ statement like
x = Table.SingleOrDefault(o => o.id == 1).o.name;
how can I replace "id" and "name" with passed in variables using reflection? I keep getting object reference not set to instance of an object errors when I try. My attempts are things like
x = (string)Table.SingleOrDefault(o => (int?)o.GetType().GetProperty(idString)
.GetValue(o, null) == 1).GetType().GetField(nameString).GetValue(x);
Any help would be great. Thanks.
You should use Expression Trees instead of reflection. It will perform better, and you'll be able to use it with both LINQ to Objects and LINQ to SQL/Entities.
var source = new List<Test> { new Test { Id = 1, Name = "FirsT" }, new Test { Id = 2, Name = "Second" } };
var idName = "Id";
var idValue = 1;
var param = Expression.Parameter(typeof(Test));
var condition =
Expression.Lambda<Func<Test, bool>>(
Expression.Equal(
Expression.Property(param, idName),
Expression.Constant(idValue, typeof(int))
),
param
).Compile(); // for LINQ to SQl/Entities skip Compile() call
var item = source.SingleOrDefault(condition);
then, you can get Name property using reflection (you'll do it just once, so it's fine to do it using reflection.
var nameName = "Name";
var name = item == null ? null : (string) typeof(Test).GetProperty(nameName).GetValue(item);
Test class is defined as
public class Test
{
public int Id { get; set; }
public string Name { get; set; }
}
You can't use reflection, but you can use dynamic Linq as described here. If you are using Entity Framework for this, you should also be able to use Entity SQL, which is basically a hard-coded SQL string.

Linq access property by variable

Let's say I have a class like:
public class Foo
{
public string Title {get;set;}
}
Now, let's assume I have a public List<Foo> myList which I want to filter by Linq as so:
var x = myList.Where(f => f.Title == myValue);
Everything is nice and clear until now.
But how can access the property by variable? Something like:
string myProperty = "Title";
var x = myList.Where(f => f.myProperty == myValue);
You can write an extension method
public static class MyExtensions
{
public static object GetProperty<T>(this T obj, string name) where T : class
{
Type t = typeof(T);
return t.GetProperty(name).GetValue(obj, null);
}
}
and use it like this
var x = myList.Where(f => f.GetProperty("Title") == myValue);
This is not the type of situation that LINQ is used for. LINQ is a fluent interface for manipulating collections. Accessing members via a textual representation is done with reflection.
object GetProperty(Foo f, string propertyName) {
var type = typeof(Foo);
var propInfo = type.GetProperty(propertyName);
return propInfo.GetValue(f, null);
}
If you need to compose your queries dynamically on the fly, you can use the LINQ Dynamic Query library, a sample from Microsoft:
This sample shows a technique for composing LINQ statements on the
fly, dynamically, at run time.
Reference the library in your code:
using System.Linq.Dynamic;
Your query would look like this:
// You can use a string as the argument for the Where method
// meaning you can compose this string dynamically
string myProperty = "Title";
var x = myList.Where(myProperty + " = " + myValue);
It's also possible to use placeholder in the query string, which improves readability (somewhat):
var x = myList.Where("#0 = #1", myProperty, myValue);
See also this post from Scott Guthrie: Dynamic LINQ Part 1: Using the LINQ Dynamic Query Library (I don't think there ever was a part 2...)
Note: you have to compile the sample code from Microsoft and reference the built assembly, or you could include the code in your own project.
I know this is an old thread but here is another way to do it. This has the advantage of being significantly faster if you need to do it in a loop. I have converted the result out of "func" to be object to make it a bit more general purpose.
var p = Expression.Parameter(typeof(string));
var prop = Expression.Property(p, "Length");
var con = Expression.Convert(prop, typeof(object));
var exp = Expression.Lambda(con, p);
var func = (Func<string, object>)exp.Compile();
var obj = "ABC";
int len = (int)func(obj);
In the original question the code was being used inside linq so speed could be good. It would be possible to use "func" direct in the where clause also if it was built correctly, eg
class ABC
{
public string Name { get; set; }
}
var p = Expression.Parameter(typeof(ABC));
var prop = Expression.Property(p, "Name");
var body = Expression.Equal(prop, Expression.Constant("Bob"));
var exp = Expression.Lambda(body, p);
var func = (Func<ABC, bool>)exp.Compile();
ABC[] items = "Fred,Bob,Mary,Jane,Bob".Split(',').Select(s => new ABC() { Name = s }).ToArray();
ABC[] bobs = items.Where(func).ToArray();
you cant use linq dynamic query from microsoft
here is sample code
var query = db.Customers.Where("City == #0 and Orders.Count >= #1", "London", 10).
OrderBy("CompanyName").
Select("New(CompanyName as Name, Phone)");

Categories