How to apply a filter on LINQtoSQL results?

限于喜欢 提交于 2019-12-12 18:34:34

问题


With the ListBox control it is possible to feed it a DataSource, name a DisplayMember and a ValueMember and through some magic it will display a field from the DataSource and return a selected ValueMember. It can work wit a linq-to-sql result without even knowing anyting specific about the table it is feed with.

Isn't Reflection and Attributes doing some magic? How does it work! I have a need to do something similar but I do not know where to start. I'm a beginner for LINQtoSQL.

This is what I want to do. I have a source table that I want to filter. The source table can be anything but will be originating from some DataContext.

var MySourceTable =
    from MyRecord in Context.GetTable<MySourceTable>()
    select new
    {
        Value = MyRecord.ID,
        Display = MyRecord.Name,
        FilterValue = MyRecord.Value
    };

In my control I want to be able to filter MySourceTable on some given value. The control does not know what table is used (MySourceTable in the example above) and the control does only know the three names, ID, Name and Value of the fields in the record it should use.

The filter query should look like the example below.

var MyTable
    from Record in MySourceTable
    where FilterValue == GivenValue
    select new
    {
        Value = Record.ID,
        Display = Record.Name,
    };

Can somebody advise me on where to start?


回答1:


I wrote a filter engine that takes in a Property and Value as a string, and is able to use that as a where clause.

IQueryable<T> FilterFunction<T>(IQueryable<T> query)
{
    ParameterExpression p = Expression.Parameter(typeof(T), "notused");

    Expression<Func<T, bool>> wherePredicate =
      Expression.Lambda<Func<T, bool>>(
          Expression.Equal(
            Expression.Call(Expression.Property(p, FilterProperty), "ToString", new Type[0]),
            Expression.Constant(FilterValue)), p);

    return query.Where(wherePredicate);
}

You should be able to pass in an Expression<Func<T, TResult>> built in a similar way into query.Select()

If I am understanding your question correctly, I believe this will work:

string DisplayProperty = "Name";
string ValueProperty = "ID";

IQueryable<Record> SelectRecordProperties<T>(IQueryable<T> query)
{
    ParameterExpression p = Expression.Parameter(typeof(T), "notused");

    MethodInfo ctorMethod = typeof(Record).GetMethod("Create");

    Expression<Func<T, Record>> selectPredicate =
      Expression.Lambda<Func<T, Record>>(
        Expression.Call(ctorMethod,
            Expression.PropertyOrField(p, DisplayProperty),
            Expression.PropertyOrField(p, ValueProperty)), p);

    return query.Select(selectPredicate);
}
class Record
{
    public static Record Create(string display, string value)
    {
        return new Record() { Display = display, Value = value };
    }
    public object Display { get; set; }
    public object Value { get; set; }
}

So for your full function you'd need to combine these two ideas so that your filtering works.

By the way, there are many possible ways to build the expression tree for this, there was some tool I've found at one point which would show you the expression tree I think, so you could manually write the linq query and see how .Net builds the expression, then modify this code to build it based on that to possibly get a more efficient expression tree.




回答2:


It looks like what you are missing is in the where condition on your query. It should look like this:

var MyTable = 
    from Record in MySourceTable
    where Record.FilterValue == GivenValue
    select new
    {
        Value = Record.ID,
        Display = Record.Name,
    };

GivenValue is presumably a local variable or property containing whatever you want to compare FilterValue against. But FilterValue is a property of the anonymous type that you created in your first query that created MySourceTable. In your second query, Record is an instance of that anonymous type, and you have to use that reference to the instance in all other parts of the query to reference the instance that you are checking for the where clause or selecting for the select clause. If you just put FilterValue there, then it has no idea what you mean.




回答3:


I found a way, it works but is not a completely satisfying method.

The 'problem' (compared to my original question) is that it does not use linq-to-sql for filtering the source. But it works and that is at the moment fine for me.

Recap: At a high level module I prepare a LINQtoSQL statement which will result in some IQueriable<Object>, effectively an IQueriable object. This object is given to a lower level module together with three names, one for the ID, one for the Display and one for filtering. When I need more control over the source, e.g. for unfiltered data that will result in huge results or for complex LINQtoSQL queries I will use a delegate function. That will give me all the control I want.

At a low level I have a IQueriable<Object> and no knowledge at all about the Object, except for wat Reflection can tell me. I generate a table of results that I want to use in a control-specific format. (the Record class). For anything more complex that my standard code cannot handle I offer a delegate that must result in some list of object in which the object must have at least a property named 'Display' and a property named 'Value'. Other properties are possible but will not be used.

This is the solution that I finaly got to work:

public partial class MySelector : UserControl
{
    class Record
    {
        public object Display { get; set; }
        public object Value { get; set; }
    }

    ....

    public string MyDisplayMember { get; set; }
    public string MyValueMember { get; set; }
    public string MyExternalMember { get; set; }

    ....

    static Object Filter(MySelector sender, Object criterium)
    {
        IQueryable source = sender.MySource as IQueryable;
        if (source == null) return null;

        List<Record> result = new List<Record>();

        // drawback: this foreach loop will trigger a unfiltered SQL command.
        foreach (var record in source)
        {
            MethodInfo DisplayGetter = null;
            MethodInfo ValueGetter = null;
            bool AddRecord = false;

            foreach (PropertyInfo property in record.GetType().GetProperties())
            {
                if (property.Name == sender.MyDisplayMember) 
                {
                    DisplayGetter = property.GetGetMethod();
                }
                else if (property.Name == sender.MyValueMember)
                {
                    ValueGetter = property.GetGetMethod();
                }
                else if (property.Name == sender.MyExternalMember)
                {
                    MethodInfo ExternalGetter = property.GetGetMethod();
                    if (ExternalGetter == null)
                    {
                        break;
                    }
                    else
                    {
                        object external = ExternalGetter.Invoke(record, new object[] { });
                        AddRecord = external.Equals(criterium);
                        if (!AddRecord)
                        {
                            break;
                        }
                    }
                }
                if (AddRecord && (DisplayGetter != null) && (ValueGetter != null))
                {
                    break;
                }
            }
            if (AddRecord && (DisplayGetter != null) && (ValueGetter != null))
            {
                Record r = new Record();
                r.Display = (DisplayGetter == null) 
                    ? null 
                    : DisplayGetter.Invoke(record, new object[] { });
                r.Value = (ValueGetter == null) 
                    ? null 
                    : ValueGetter.Invoke(record, new object[] { });
                result.Add(r);
            }
        }
        return result;
    }
}


来源:https://stackoverflow.com/questions/13147049/how-to-apply-a-filter-on-linqtosql-results

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!