6

I'm basically trying to construct a query, and I don't know why microsoft made this so difficult in Entity Framework and LINQ. I have various parameter STRINGS. So if you see a variable, assume it's a string passed in from somewhere.

             users = this.entities.tableUsers
                .Where(searchfield+" LIKE %@0%", search)
                .OrderBy(x => x.GetType().GetProperty(order_by).GetValue(x, null).ToString())
                .Skip(Convert.ToInt32(limit_begin))
                .Take(Convert.ToInt32(limit_end))
                .ToList();

My question is what to put inside "Where()" function in LINQ.

I want to search a field with string "searchfield", for the value .contains() "search".

Not sure why Visual Studio won't let me do this easily.

I've tried this as well, no luck:

.Where(x => x.GetType().GetProperty(searchfield).GetValue(x, null).ToList().Contains(search))

Note: I don't want to install any new libraries, this should be incredibly easy and simple for a modern language. I don't mind if the query returns all the rows and I search through it AFTER with .Contains().

Dexter
  • 6,170
  • 18
  • 74
  • 101
  • Did you try my answer in your other question? http://stackoverflow.com/questions/11992717/when-creating-dynamic-linq-sorting-and-searching-order-statements-in-entity-fram/11993005#11993005 – Gromer Aug 16 '12 at 19:47
  • Have you tried doing same thing *without* EF, in plain LINQ-to-Objects? – Sergei Rogovtcev Aug 16 '12 at 19:59
  • But why would EF prevent something that can be done in LINQ-to-objects? I thought EF was suppose to be more advanced and make things easier for querying. – Dexter Aug 16 '12 at 20:04
  • @Dexter you have already been answered that: EF allows only expressions that can be translated to SQL. This one can't be (or the translation is too hard). EF is *not* supposed to be more "advanced" than LINQ-to-Objects, it's another implementation with another goals. – Sergei Rogovtcev Aug 16 '12 at 20:07
  • Understood but I don't see what's hard with translating: a.string.Contains(). It works fine when you know the string and write it as a constant a.Field.Contains(). Why is it suddenly difficult when you don't know what string variable will be? – Dexter Aug 16 '12 at 20:15
  • Because it's not "variable" or "constant", it's property reference. It seems to me that you don't understand that lambda expression in your code is just a shortcut to building AST. Language designers decided to not implement dynamic property references into this shortcut (mostly because C# is - mostly - statically typed language). – Sergei Rogovtcev Aug 16 '12 at 20:28
  • Still doesn't make sense as to why it won't work with dynamic preferences. There's no negative consequences to allowing people to dynamically type out what fields they want from their database, it's silly. They should provide an easy way to convert those datatypes to whatever they want. – Dexter Aug 16 '12 at 20:31
  • Because there's no way of expressing dynamic property reference in C#. And regarding "dynamically type out what fields": I would repeat once again: there is *already* a dynamic data access mechanism in .net, it bases on `DataTable`s and `DataSet`s and it *works*. EF is a tool for those who want *static* references and *static* checking. – Sergei Rogovtcev Aug 16 '12 at 20:56
  • Like what, exactly? Nothing is implemented by default. You're not just asking if and how something can be done, but berating the lack of it being implemented. If you think it's such a no-brainer to add support for it, you should add that support yourself. – Jon Hanna Aug 17 '12 at 19:24
  • @JonHanna Yes, it should be implemented as in any modern ORM library. It is absolutely a no-brainer. And yes I am asking how to do it, that's why I made this post. I don't understand what you're trying to say, there certainly wasn't any berating either, so your implications are immature and unnecessary. – Dexter Aug 17 '12 at 20:02
  • Your statement that MS made it difficult. They didn't make it difficult, they failed to make it easy, which is very different. – Jon Hanna Aug 17 '12 at 21:15

4 Answers4

10

This is not trivial, but I believe it can be done. The following has not been tested. The code is borrowed from here.

Create a helper method somewhere like

public static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string containsValue)
{
    var parameterExp = Expression.Parameter(typeof(T), "type");
    var propertyExp = Expression.Property(parameterExp, propertyName);
    MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    var someValue = Expression.Constant(propertyValue, typeof(string));
    var containsMethodExp = Expression.Call(propertyExp, method, someValue);

    return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}

public static Expression<Func<T, TKey>> GetPropertyExpression<T, TKey>(string propertyName)
{
    var parameterExp = Expression.Parameter(typeof(T), "type");
    var exp = Expression.Property(parameterExp, propertyName);
    return Expression.Lambda<Func<T, TKey>>(exp, parameterExp);
}

Use it like

users = this.entities.tableUsers
                     .Where(GetContainsExpression<User>(searchfield, search))
                     .OrderBy(GetPropertyExpression<User, string>(searchfield))
                     ...

UPDATE

As an alternative, you could create extension methods to provide a cleaner syntax. Create the following methods in a static class somewhere:

    public static IQueryable<T> WhereStringContains<T>(this IQueryable<T> query, string propertyName, string contains)
    {
        var parameter = Expression.Parameter(typeof(T), "type");
        var propertyExpression = Expression.Property(parameter, propertyName);
        MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        var someValue = Expression.Constant(contains, typeof(string));
        var containsExpression = Expression.Call(propertyExpression, method, someValue);

        return query.Where(Expression.Lambda<Func<T, bool>>(containsExpression, parameter));
    }

    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
    {
        var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
        var parameter = Expression.Parameter(typeof(T), "type");
        var propertyExpression = Expression.Property(parameter, propertyName);
        var lambda = Expression.Lambda(propertyExpression, new[] { parameter });

        return typeof(Queryable).GetMethods()
                                .Where(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
                                .Single()
                                .MakeGenericMethod(new[] { typeof(T), propertyType })
                                .Invoke(null, new object[] { query, lambda }) as IOrderedQueryable<T>;
    }

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName)
    {
        var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
        var parameter = Expression.Parameter(typeof(T), "type");
        var propertyExpression = Expression.Property(parameter, propertyName);
        var lambda = Expression.Lambda(propertyExpression, new[] { parameter });

        return typeof(Queryable).GetMethods()
                                .Where(m => m.Name == "OrderByDescending" && m.GetParameters().Length == 2)
                                .Single()
                                .MakeGenericMethod(new[] { typeof(T), propertyType })
                                .Invoke(null, new object[] { query, lambda }) as IOrderedQueryable<T>;
    }

Then you can call them like:

var users = this.entities.tableUsers.WhereStringContains(searchField, search)
                                    .OrderBy(searchField);
Community
  • 1
  • 1
jon
  • 728
  • 5
  • 12
  • I should add that I agree with other posters in that this is a bit like stuffing a square peg into a round hole. You would be better off using a library like Dynamic LINQ, it was built to do exactly what you are after. – jon Aug 16 '12 at 20:10
  • How is it stuffing ? Dynamic LINQ library is huge. This is a very compact quick solution for specifically this situation, since everything else I write is not "string-to-linq-to-EF" – Dexter Aug 16 '12 at 20:16
  • One of the main benefits of linq-to-ef as I see it is to provide a strongly typed wrapper around your data access code. With a solution like this you lose many of the benefits provided by that strong typing. – jon Aug 16 '12 at 20:23
  • Yes this works great. The only issue is I don't know how to convert this function to be used for OrderBy and OrderByDescending, as those are not working with string-variable properties either. – Dexter Aug 16 '12 at 20:24
  • I don't see the benefits in strong typing in this situation. I just want it to work without the hassle of types, I'd prefer if it just converted all the values to string to output on the website. – Dexter Aug 16 '12 at 20:25
  • 1
    Nevermind, I figured it out... users = this.entities.Users .Where(GetContainsExpression(searchfield, search)) .OrderByDescending(x => order_by) .Skip(Convert.ToInt32(limit_begin)) .Take(Convert.ToInt32(limit_end)) .ToList(); – Dexter Aug 16 '12 at 20:38
  • I updated the answer with how to solve the OrderBy problem. I can certainly understand wanting a solution that "just works". – jon Aug 16 '12 at 20:39
  • Yeah you got it. Thank you. Not sure why everyone wants me to install a whole new gigantic library, which I would have to learn and figure out, for something so simple. I don't know why by "x=>order_by" simply worked so I didn't exactly need your "GetPropertyExpression" function (yet). – Dexter Aug 16 '12 at 20:43
  • I've tried to modify your 2nd function, but run into errors using DateTime sorting. Says ParameterExpression of type 'DateTime' cannot be used for delegate parameter of type UserTable. public static Expression> GetPropertyExpression(string propertyName) { var parameterExp = Expression.Parameter(typeof(T), "type"); var parameterExp2 = Expression.Parameter(typeof(TKey), "type"); var exp = Expression.Property(parameterExp, propertyName); return Expression.Lambda>(exp, parameterExp2); } – Dexter Aug 17 '12 at 20:03
  • I updated the answer fixing the issue in the GetPropertyExpression method – jon Aug 20 '12 at 15:48
  • Hi I am Facing Problem when i am using "WhereStringContains" with Interger, DateTime, Guid data type properties. How to handle those ? – Kartheek Oct 11 '13 at 11:37
5

this should be incredibly easy and simple for a modern language

No, it should not if it goes against that language paradigm. LINQ and Entity Framework (as well as any other decent ORM out there) are made precisely to avoid what you're trying to accomplish: non-typed and non-compiler-verifiable queries. So basically you're forcing square peg into round hole.

You can still take a look at Dynamic LINQ.

Sergei Rogovtcev
  • 5,804
  • 2
  • 22
  • 35
  • Languages/frameworks should not be designed to be babysitters to prevent you from making security problems. They should be designed to give you the full power, necessary to create dynamic instances so you don't have to type a mountain of code, or install a huge new library to do something very simple like generate a dynamic table with various fields from the database, depending on the page you are loading. I'm not using Dynamic LINQ and I don't feel I would need to unless it's the only way. It's overkill for what I need to do, which I think can be accomplished with reflection. – Dexter Aug 16 '12 at 19:46
  • Furthermore, there's no reason why a framework designer should allow you to type "Where(x => x.Field1.Contains(search))" but not allow you to be able to do "Where(x => x.GetPropertyByName(searchfield).Contains(search))" --- an intelligently designed framework would allow that unless you see some problem that I am not seeing. Otherwise it's easier for me to just type out Switch-Case, for every field name, rather than install a library. – Dexter Aug 16 '12 at 19:49
  • LINQ *allows* you to do that. EF doesn't. – Sergei Rogovtcev Aug 16 '12 at 19:50
  • Regarding your first comment: why use strong-typed tool where you obviously need dynamic? Why can't you just use `DataTable`? – Sergei Rogovtcev Aug 16 '12 at 19:53
  • Dexter, LINQ interprets the argument to `Select` as an expression tree and translates that expression tree to SQL. There is a SQL equivalent to `Contains`, but not to `GetPropertyByName`. Solving the general problem of converting reflection code like that is probably uncomputable. – Thom Smith Aug 16 '12 at 19:53
  • IF it were up to me, I'd use Python and Django and be done with it. But yes I'm improving upon an ASP.net project. DataTable was what I was using before, but now I want to create it dynamically because DataTable loses many things and doesn't work well with EntityFramework. Hence I'd rather just do it from scratch. – Dexter Aug 16 '12 at 19:56
  • @ThomSmith so you're saying what I want to do is impossible unless I know the field the user wants to search. – Dexter Aug 16 '12 at 19:57
  • `DataTable` doesn't work with EF at all (it seems to me that you don't get what EF is *for*). What "things" does `DataTable` lose? – Sergei Rogovtcev Aug 16 '12 at 19:58
  • So what I would do, is I would create my specific query with EF. Then create a DataTable object and bind that. And thus, when someone sorts something, it works---except when they go to the next page, it loses the sort because it rebinds. It seems it's time to build it from scratch and put my own GET variables in the URL to track pagination. Yes I'm presuming EF is for trying to make querying easier, with lazy-loading etc., but so far it has proven just as difficult. – Dexter Aug 16 '12 at 20:00
  • You have to keep your sorting information between pages. That's entirely another problem (and easily solvable). – Sergei Rogovtcev Aug 16 '12 at 20:01
  • @Dexter: It is not impossible, you just have to build up an expression tree. – Thom Smith Aug 16 '12 at 20:02
  • @ThomSmith how do I build an expression tree? – Dexter Aug 16 '12 at 20:03
  • @ThomSmith or replace `IQueryable` with `IEnumerable` and use delegates with whatever logic he wants inside. – Sergei Rogovtcev Aug 16 '12 at 20:04
  • @Dexter: I added an example to my answer below. It should be mostly-working code. – Thom Smith Aug 16 '12 at 20:10
2

You'll have to build an expression tree to pass to the Where method. Here's a loose adaptation of some code I have lying about:

string searchfield, value; // Your inputs
var param = Expression.Parameter(typeof(User), "user");

return Expression.Lambda<Func<T, bool>>(
    Expression.Call(
        Expression.Property(
            param,
            typeof(User).GetProperty(searchfield)),
        typeof(string).GetMethod("Contains"),
        Expression.Constant(value)),
    param);

That will generate an appropriate expression to use as the parameter to Where.

EDIT: FYI, the resultant expression will look something like user => user.Foo.Contains(bar).

EDIT: To sort, something like this (ripped from my DynamicOrderList class):

private IQueryable<T> OrderQuery<T>(IQueryable<T> query, OrderParameter orderBy)
{
    string orderMethodName = orderBy.Direction == SortDirection.Ascending ? "OrderBy" : "OrderByDescending";
    Type t = typeof(T);

    var param = Expression.Parameter(t, "user");
    var property = t.GetProperty(orderBy.Attribute);

    return query.Provider.CreateQuery<T>(
        Expression.Call(
            typeof(Queryable),
            orderMethodName,
            new Type[] { t, typeof(string) },
            query.Expression,
            Expression.Quote(
                Expression.Lambda(
                    Expression.Property(param, property),
                    param))
        ));
}
Thom Smith
  • 13,916
  • 6
  • 45
  • 91
  • YEs excellent this works great. But I don't know how to change it for OrderBy and OrderByDescending based on a string property name? – Dexter Aug 16 '12 at 20:22
0

My answer to your other question about this:

When creating dynamic linq sorting and searching order statements in Entity Framework

Community
  • 1
  • 1
Gromer
  • 9,861
  • 4
  • 34
  • 55
  • 1
    Yes a great answer, but I don't want to list out 10 Switch Cases, there should be a way to do this dynamically. – Dexter Aug 16 '12 at 19:58
  • But why care so much about the dynamic nature of it? Right, maybe there *should* be a way, but do you really change the properties on your entities that often? I saw do the switch and be done with it. EF is never worth this kind of stress! – Gromer Aug 16 '12 at 20:01
  • 1
    You're right, but it does change sometimes. And it will be used on a lot of different classes with different properties. I want a dynamic system so I can pass in an array of fields I want from the database and that's it. – Dexter Aug 16 '12 at 20:23
  • @Dexter Once again: if you want "dynamic system", use `DataTable`s. They are just the thing for your task. – Sergei Rogovtcev Aug 16 '12 at 20:29
  • I was using DataTables, it doesn't work. It takes a gargantuan amount of effort to do it that way. And you are forced to use these stupid little asp tags everywhere that don't give you the ability to customize anything. I don't see why I can't use EF and LINQ and dynamically generate my tables and type out what fields I want from the database and type out how to present them. Why is this simple concept so difficult to understand? I'm this close to literally typing out my own SQL statements and using my own query-building functions because apparently LINQ and EF aren't helping. – Dexter Aug 16 '12 at 20:32
  • In the end this is just code that is suppose to convert my statements to string SQL queries, that are sent directly to MSSQL, how is it that I can easily type out my SQL statements in MSSQL's stored procedures, but it becomes a terribly difficult and confusing task with lambdas and other confusing objects when it comes to LINQ AND EF? – Dexter Aug 16 '12 at 20:36
  • @SergRogovtsev don't you think that's a bit funny that I have to type 20 lines of code for something that can be done in 1-2 lines in MSSQL stored procedures or Python, or PHP, or even strongly-typed C++, or any other library that doesn't involve EF or LINQ? – Dexter Aug 16 '12 at 20:41
  • 1
    @Dexter No, I don't. Because as I've already said you should choose instrument for your task. And you "can't use EF and LINQ and dynamically generate my tables" because EF is designed to provide static typing (as most ORM do). When you need dynamic data access, you use `DataTable`s (or even `DataReader`s). And it seems to me that you don't understand how `DataTable` works because it has *nothing* to do with "stupid little asp tags". – Sergei Rogovtcev Aug 16 '12 at 20:53
  • @SergRogovtsev ORMs goal is to make things easier and more dynamic, with less coding. Not the other way around. It is certainly not what EF should be doing. It's just what Microsoft chose as the best way. I've used DataTable plenty of times, but you still have to use EF for ORM or you would be forced to use SQL statements typed out using SqlClient etc. I don't see why you keep mentioning DataTable when it's just a container object. – Dexter Aug 17 '12 at 19:21
  • @Dexter you always say "dynamic" but I don't think I understand what you mean by that. – Sergei Rogovtcev Aug 17 '12 at 22:08