How to rewrite LINQ Any() to make it suitable for .NET 3.5 - linq

I have the following EF class:
class Product
{
public int ProdId { get; set; }
public int ProdDesc { get; set; }
public int ProdKeywords { get; set; }
}
Now I have to implement a search function that looks at ProdDesc and ProdKeywords. The keywords are registered in a array and the collection of products in a IQueryable
string[] keywordsArray = new string[] {"kw1", "kw2", ..., "kwN"};
IQueryable<Product> products = repository.GetProducts();
To see if there are products matching the keywords I use the following LINQ:
var matchingProducts = products.Where(p => keywordsArray.Any(k => p.ProdDesc.Contains(k) ||
p.ProdKeywords.Contains(k));
which works like a charm in .NET 4.
The BIG problem is that I am forced to use this code in .NET 3.5 and I just discovered that Any and Contains (the LINQ method, not the one applied to strings) don't work in that framework. That's a real pain. The code is too big to rewrite everything and the deadline is too close.
I found this article really interesting but I can't make it work in my case. Anybody might help?

What's about:
static class Extension
{
public static bool Contains(this IEnumerable<object> source, object value)
{
foreach (object o in source)
if (o.Equals(value)) return true;
return false;
}
}
var mylist = keywordsArray.ToList();
matchingProducts = products.Where(p => mylist.Exists(k => p.ProdDesc.Contains(k) ||
p.ProdKeywords.Contains(k));

you could query first the any and store that in a enumerable and the check if the count is bigger then 0

Related

Linq Expressions on Child entities

I am building dynamic linq expressions which is working fine for a single entity.
For example:
I have a class called Employee and empeduinfo
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class EmpEduInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int EmpId { get; set; }
}
I need to get all the the employees and empeduinfo class starts with "x"
I prepared expression for startswith("x")
var temp= entities.employees.Include("EmpEduInfo").Where(mydynamicexpression);
In this case it is filtering only parent table not on child.
I need to prepare generic expression so than i need to filter both parent and child objects dynamically.
Without using expression I know a solution:
var temp= (from ee in entities.Employee.Include("EmpEduInfo").Where(x => x.name.StartsWith("t"))
where ee.EmpEduInfo.Where(x => x.name.StartsWith("t")).Count()>0
select ee).ToList();
using expressions I am building generic expression to provide dynamic advance search rather than writing in each and every entity.
Here is my expression details
// Get the method information for the String.StartsWith() method
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
// Build the parameter for the expression
ParameterExpression empparam= Expression.Parameter(typeof(employee), "ename");;
// Build the member that was specified for the expression
MemberExpression field = Expression.PropertyOrField(empparam, "name");
// Call the String.StartsWith() method on the member
MethodCallExpression startsWith = Expression.Call(field, mi, Expression.Constant("t"));
var namelamda = Expression.Lambda<Func<employee, bool>>(startsWith, new ParameterExpression[] { empparam });
var temp = entities.employees.Include("empedudetails").Where(namelamda).ToList();
You can look at the Expression the compiler generates using IQueryable:
IQueryable<Employee> query =
from ee in entities.Employee ...
var expression = query.Expression;
Look at expression in a debugger to see what you need to generate - LINQPad is good for this.
You might want to simplify your query a bit first:
IQueryable<Employee> query =
from ee in entities.Employee.Include("EmpEduInfo")
where
ee.name.StartsWith("t") &&
ee.EmpEduInfo.Any(x => x.name.StartsWith("t"))
select ee;
I was trying in EF 4.0 either we have write DB extentions for the same.
Option is provided in EF 4.1
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
Thanks.

How to dynamically add properties to a LINQ GroupBy clause

I have a class Product:
class Product
{
int Id { get; set; }
string Name { get; set; }
int CategoryId { get; set; }
int PlantId { get; set; }
DateTime ProductionDate { get; set; }
}
I would like to use the LINQ GroupBy on multiple properties but I do not know in advance how many and which properties. For instance I might want to group by just CategoryId, just PlantId or both. I found an article on the net that describes how to use LINQ GrouBy dinamically.
This might work good indeed but if I want to perform the Group By on ProductionDate.Year and ProductionDate.Month without knowing the granularity in advance? As granularity I mean whether I want to group all the Products produced in a specific year or narrow the group by to the month.
The only logical solution that I found is:
public ProductInfo GetGroupedProducts(int? year, int? month, int? selectedCategoryId, int? selectedPlantId)
{
List<Product> products = GetProducts();
var groupedProducts = products.GroupBy(p => new { (year == null ? p.ProductionDate.Year : string.Empty),
(month == null ? p.ProductionDate.Month : string.Empty),
(selectedCategoryId == null ? p.CategoryId : string.Empty),
(selectedPlantId == null ? p.PlantId : string.Empty)
});
//perform some additional filtering and assignments
}
But I guess there could be a cleaner and more proper solution. With the old style way of building queries, based on strings, this task was much easier to accomplish. If there is no other way, I really think this is a part of LINQ that needs to be improved.
The cleaner solution is to use this extension method:
public static TResult With<TInput, TResult>(this TInput? o, Func<TInput, TResult>
selector, TResult defaultResult = default(TResult)) where TInput : struct
{
return o.HasValue ? selector(o.Value) : defaultResult;
}
Like this:
string result = year.With(T => p.ProductionDate.Year, string.Empty);
of this, if nulls are okay:
string result = year.With(T => p.ProductionDate.Year);
or something with T which is int in case the int? has value.
But, you know, the better solution is out there, so feel free to expand your code so I could analyze it.
If I understand what you are asking, I had a similar issue Reversing typeof to use Linq Field<T>
I would do something like
public static IEnumerable<IGrouping<string, TElement>> GroupMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
return elements.GroupBy(e => string.Join(":", groupSelectors.Select(s => s(e))));
}
then you can call your function like
var groupedProducts = products.GroupMany(p => p.CategoryId , p => p.ProductionDate.Month);
The function groups via a string of the properties divided by a colon. The reason why I did this is because the hashcode for strings are guaranteed to be the same as opposed to a class.

LINQ - NHibernate: one list items contains all other list items

I have this structure:
class Foo {
IList<FooAttribute> Attributes { get; set; }
}
class FooAttribute {
bool IsSelected { get; set; }
string Value { get; set; }
}
And I have objects like:
IQuerable<Foo> foos; // Database repository object .AsQuerable()
IList<FooAttribute> attrs;
I need to filter only those items of foos that have all attributes of attrs list.
I tried this:
foos = foos.Where(foo =>
attrs.All(a =>
foo.Attributes.Any(at => at.Value == a)));
var filteredFoos = foos.ToList();
and i think it would work, but would be super slow and... it throws NotSupportedException...
By the way... I use ASP.NET MVC 3 and C# 4.0, so even the newest solutions are very welcome.
Thanks in advance.
FooAttribute fooAttributeAlias=null;
Session.QueryOver<Foo>().Inner.JoinAlias(x=>x.Attributes,()=>fooAttributeAlias)
.WhereRestrictionOn(()=>fooAttributeAlias).IsNotEmpty
.List();
I did not understand the query that you have written. I am not sure if the above query does what you expect, see the generated sql and see if it is correct.
Also what might help is the sql query that you expect to see which will give you the correct result.

Passing an expression to a method in NHibernate results in Object of type 'ConstantExpression' cannot be converted to type 'LambdaExpression'

This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:
System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.
Classes involvend:
public class A
{
public virtual int Id { get; set; }
public virtual ISet<B> DataFields { get; set; }
}
public class B
{
public virtual int Id { get; set; }
}
Sample test code:
[TestMethod]
public void TestMethod1()
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
Console.Write("Number of records is {0}", records.Count());
}
}
[TestMethod]
public void TestMethod2()
{
GetAsWhereB(b => b.Id == 1);
}
private void GetAsWhereB(Func<B, bool> where)
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(where));
Console.Write("Number of records is {0}", records.Count());
}
}
This is one problem:
private void GetAsWhereB(Func<B, bool> where)
That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:
private void GetAsWhereB(Expression<Func<B, bool>> where)
As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:
var records = session.Query<A>().Where<A>(a => a.DataFields.
Any(b => b.Id == 1));
you make it clear that the "Any" call is on DataFields:
var records = session.Query<A>().Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)
Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.
private void GetAsWhereB(Func<B, bool> where)
{
Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(w));
Console.Write("Number of records is {0}", records.Count());
}
}

How to implement an IN clause in LinQ

I have two ILIst of these objects:
class ProductionMachineType
{
string code { get; set; }
IEnumerable<string> ProductionToolsLink { get; set; }
}
class ProductionTools
{
string code { get; set; }
}
I am looking for a fast Linq method that make me able to query the IList<ProductionMachineType> that contains at least one ProductionToolsLink contained inside the ILIst<ProductionTools>.
In SQL I would wite something like this:
SELECT
*
FROM
IList<ProductionMachineType>
WHERE
IList<ProductionMachineType>.ProductionToolsLink IN ILIst<ProductionTools>
Is there a way to do this?
Contains method can help you:
var names = new string[] { "Alex", "Colin", "Danny", "Diego" };
var matches = from person in people
where names.Contains(person.Firstname)
select person;
This will do it, but I can't guarantee how efficient it is...
var output = machines.Where(machine =>
machine.ProductionToolsLink
.Any(link => tools.Select(tool => tool.code).Contains(link)));

Resources