How to build () => new { x.prop} lambda expression dynamically?

在这里输入图像描述 How to dynamically create the below linq expression.

IQueryable abc = QueryData.Select(a => new { a, TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] }).OrderBy(a => a.TempData).Select(a => aa);

public class Orders
{
    public long OrderID { get; set; }
    public string CustomerID { get; set; }
    public int EmployeeID { get; set; }
    public double Freight { get; set; }
    public string ShipCountry { get; set; }
    public string ShipCity { get; set; }

    public Customer[] customer {get; set;}
}

public class Customer
{
    public string OtherAddress { get; set; }
    public int CustNum { get; set; }
}

Actual data:

List<Orders> order = new List<Orders>();
Customer[] cs = { new Customer { CustNum = 5, OtherAddress = "Hello" }, new 
Customer { CustNum = 986, OtherAddress = "Other" } };
Customer[] cso = { new Customer { OtherAddress = "T", CustNum = 5 }, new 
Customer { CustNum = 777, OtherAddress = "other" } };
order.Add(new Orders(code + 1, "ALFKI", i + 0, 2.3 * i, "Mumbari", "Berlin", cs));
order.Add(new Orders(code + 2, "ANATR", i + 2, 3.3 * i, "Sydney", "Madrid", cso));
order.Add(new Orders(code + 3, "ANTON", i + 1, 4.3 * i, "NY", "Cholchester", cs));
order.Add(new Orders(code + 4, "BLONP", i + 3, 5.3 * i, "LA", "Marseille", cso));
order.Add(new Orders(code + 5, "BOLID", i + 4, 6.3 * i, "Cochin", "Tsawassen", cs));



public Orders(long OrderId, string CustomerId, int EmployeeId, double Freight, string ShipCountry, string ShipCity, Customer[] Customer = null)
    {
        this.OrderID = OrderId;
        this.CustomerID = CustomerId;
        this.EmployeeID = EmployeeId;
        this.Freight = Freight;
        this.ShipCountry = ShipCountry;
        this.ShipCity = ShipCity;
        this.customer = Customer;
    }

If i sort the OtherAddress field 0th index means Customer field only sorted. I need to sort the whole order data based on OtherAddress field.

I have tried the below way:

private static IQueryable PerformComplexDataOperation<T>(this IQueryable<T> dataSource, string select)
    {
        string[] selectArr = select.Split('.');
        ParameterExpression param = Expression.Parameter(typeof(T), "a");
        Expression property = param;
        for (int i = 0; i < selectArr.Length; i++)
        {
            int n;
            if (int.TryParse(selectArr[i + 1], out n))
            {
                int index = Convert.ToInt16(selectArr[i + 1]);
                property = Expression.PropertyOrField(Expression.ArrayIndex(Expression.PropertyOrField(property, selectArr[i]), Expression.Constant(index)), selectArr[i + 2]);
                i = i + 2;
            }
            else property = Expression.PropertyOrField(property, selectArr[i]);
        }
        var TempData = dataSource.Select(Expression.Lambda<Func<T, object>>(property, param));

       IQueryable<object> data = dataSource.Select(a => new { a, TempData = property});// Expression.Lambda<Func<T, object>>(property, param) });
        return data;
    }

Method call : PerformComplexDataOperation(datasource, "customer.0.OtherAddress")

I can get the value from this line : var TempData = dataSource.Select(Expression.Lambda>(property, param));

But i can't get the values in dataSource.Select(a => new { a, TempData = property});

It is working when we use the below code :

    var TempData = dataSource.Select(Expression.Lambda<Func<T, object>>(property, param)).ToList();
    IQueryable<object> data = dataSource.Select((a, i) => new { a, TempData = TempData[i] });

Is it proper solution ?


XY problem?

This feels like it's a case of the XY problem. Your solution is contrived (no offense intended), and the problem you're trying to solve is not apparent by observing your proposed solution.

However, I do think there is technical merit to your question when I read the intention of your code as opposed to your described intention.


Redundant steps

IQueryable abc = QueryData
                    .Select(a => new { 
                             a, 
                             TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] })
                    .OrderBy(a => a.TempData)
                    .Select(a => a.a);

First of all, when you inline this into a single chained command, TempData becomes a redundant step. You could simply shift the first TempData logic (from the first Select ) directly into the OrderBy lambda:

IQueryable abc = QueryData
                    .OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
                    .AsQueryable();

As you can see, this also means that you no longer need the second Select (since it existed only to undo the earlier Select )


Parametrization and method abstraction

You mentioned you're looking for a usage similar to:

PerformComplexDataOperation(datasource, "customer.0.OtherAddress")

However, this doesn't quite make sense, since you've defined an extension method :

private static IQueryable PerformComplexDataOperation<T>(this IQueryable<T> dataSource, string select)

I think you need to reconsider your intended usage, and also the method as it is currently defined.

  • Minor note, the return type of the method should be IQueryable<T> instead of IQueryable . Otherwise, you lose the generic type definition that LINQ tends to rely on.
  • Based on the method signature, your expected usage should be myData = myData.PerformComplexDataOperation("customer.0.OtherAddress") .
  • Strings are easy hacks to allow you to circumvent an otherwise strongly typed system. While your strign usage is technically functional, it is non-idiomatic and it opens the door to unreadable and/or bad code .
  • Using strings leads to a contrived string parsing logic. Look at your method definition, and count how many lines are there simply to parse the string and translate that into actual code again.
  • Strings also mean that you get no Intellisense, which can cause unseen bugs further down the line.
  • So let's not use strings. Let's look back at how I initially rewrote the `OrderBy:

    .OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
    

    When you consider OrderBy as an ordinary method, no different from any custom method you and I can develop, then you should understand that a => a.customer.Select(b => b.OtherAddress).ToList()[0] is nothing more than a parameter that's being passed.

    The type of this parameter is Func<A,B> , where:

  • A equals the type of your entity. So in this case, A is the same as T in your existing method.
  • B equals the type of your sorting value .
  • OrderBy(x => x.MyIntProp) means that B is of type int .
  • OrderBy(x => x.MyStringProp) means that B is of type string .
  • OrderBy(x => x.Customer) means that B is of type Customer .
  • Generally speaking, the type of B doesn't matter for you (since it will only be used by LINQ's internal ordering method).

    Let's look at a very simple extension method that uses a parameter for its OrderBy :

        public static IQueryable<A> OrderData<A, B>(this IQueryable<A> data, Func<A, B> orderbyClause)
        {
            return data
                    .OrderBy(orderbyClause)
                    .AsQueryable();
        }
    

    Using the method looks like:

    IQueryable<MyEntity> myData = GetData(); //assume this returns a correct value
    
    myData = myData.OrderData(x => x.MyIntProperty);
    

    Notice how I did not need to specify either of the generic type arguments when calling the method.

  • A is already known to be MyEntity , because we're calling the method on an object of type IQueryable<MyEntity> .
  • B is already known to be an int , since the used lambda method returns a value of type int (from MyIntProperty )
  • As it stands, my example method is just a boring wrapper that does nothing different from the existing OrderBy method. But you can change the method's logic to suit your needs, and actually make it meaningfully different from the existing OrderBy method.


    Your expectations

    Your description of your goals makes me think that you're expecting too much.

    I need to sort "customer.0.OtherAddress" nested file compared to whole base data . But it sorted only for that field. For this case, I find that field value and stored it to TempData. Then Sorting the TempData field.

    i need to sort the parent nodes not an sibling alone. QueryData.Select(a => new { a, TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] }).OrderBy(a => a.TempData).Select(a => aa); I sorting a original data based on temp data. Then i split the original data alone.

    It's not possible to sort an entire nested data structure based on a single OrderBy call. OrderBy only sorts the collection on which you call Orderby , nothing else.

    If you have a list of Customer entities, who each have a list of Adress entities, then you are working with many lists (a list of customer and several lists of adresses). OrderBy will only sort the list that you ask it to sort, it will not look for any nested lists.

    You mention that your TempData solution works. I actually wrote an entire answer contesting that notion (it should be functionally similar to my suggested alternatives, and it should always order the original list, not any nested list), until I noticed that you've made it work for a very insidious and non-obvious reason:

    .Select(a => new { 
                    a, 
                    TempData = a.customer.Select(b => b.OtherAddress).ToList()[0] 
            })
    

    You are calling .ToList() , which changes how the code behaves. You started off with an IQueryable<> , which means that LINQ was preparing an SQL command to retrieve the data when you enumerate it .
    This is the goal of an IQueryable<> . Instead of pulling all the data into memory and then filtering it according to your specifications, it instead constructs a complex SQL query, and will then only need to execute a single (constructed) query.

    The execution of that constructed query occurs when you try to access the data (obviously, the data needs to be fetched if you want to access it). A common method of doing so is by enumerating the IQueryable<> into an IEnumerable<> .

    This is what you've done in the Select lambda. Instead of asking LINQ to enumerate your list of orders, you've asked it to enumerate every list of addresses from every customer from every order in the list of orders.

    But in order to know which adresses need to be enumerated, LINQ must first know which customers it's supposed to get the adresses from. And to find out which customers it needs, it must first figure out which orders you're working with. The only way it can figure all of that out is by enumerating everything .

    My initial suggestion, that you should avoid using the TempData solution, is still valid. It's a redundant step that serves no functional purpose. However, the enumeration that also takes place may actually be of use to you here, because it changes LINQ's behavior slightly. You claim that it fixes your problem, so I'm going to take your statement at face value and assume that the slightly different behavior between LINQ-to-SQL and LINQ-to-Entities solves your problem.

    You can keep the enumeration and still omit the TempData workaround:

    IQueryable abc = QueryData
                        .OrderBy(a => a.customer.Select(b => b.OtherAddress).ToList()[0])
                        .AsEnumerable()
                        .AsQueryable();
    

    Some footnotes:

  • You can use ToList() instead of AsEnumerable() , the result is the same.
  • When you use First() or Single() , enumeration will inherently take place, so you don't need to call AsEnumerable() beforehand.
  • Notice that I cast the result to an IEnumerable<> , but then I immediately re-cast it to IQueryable<> . Once a collection has been enumerated, any further operation on it will occur in-memory . Casting it back to an IQueryable<> does not change the fact that the collection has already been enumerated.

  • But does it work?

    Now, I think that this still doesn't sort all of your nested lists with a single call. However, you claim it does . If you still believe that it does, then you don't need to read on (because your problem is solved). Otherwise, the following may be useful to you.

    SQL, and by extension LINQ, has made it possible to sort a list based on information that is not found in the list. This is essentially what you're doing, you're asking LINQ to sort a list of orders based on a related address (regardless of whether you want the adresses to be retrieved from the database or not!) You're not asking it to sort the customers, or the addresses. You're only asking it to sort the orders .

    Your sort logic feels a bit dirty to me. You are supplying an Address entity to your OrderBy method, without specifiying any of its (value type) properties. But how are you expecting your addresses to be sorted? By alphabetical street name? By database id? ...

    I would expect you to be more explicit about what you want, eg OrderBy(x => x.Address.Street).ThenBy(x => x.Address.HouseNumber) (this is a simplified example).

    After enumeration, since all the (relevant) data is in-memory, you can start ordering all the nested lists. For example:

    foreach(var order in myOrders)
    {
        order.Customer.Addresses = order.Customer.Addresses.OrderBy(x => x.Street).ToList();
    }
    

    This orders all the lists of addresses. It does not change the order of the list of orders .

    Do keep in mind that if you want to order data in-memory, that you do in fact need the data to be present in-memory. If you never loaded the customer's addresses, you can't use addresses as a sorting argument.

  • Ordering the list of orders should be done before enumeration. It's generally faster to have it handled by your SQL database, which is what happens when you're working with LINQ-to-SQL.
  • Ordering nested lists should be done after enumeration, because the order of these lists is unrelated to the original IQueryable<Order> , which only focused on sorting the orders, not its nested related entities (during enumeration, the included entities such as Customer and Address are retrieved without ordering them).

  • You can transform your OrderBy so you don't need an anonymous type (though I like the Perl/Lisp Schwartzian Transform) and then it is straightforward to create dynamically (though I am not sure how dynamically you mean).

    Using the new expression:

    var abc = QueryData.OrderBy(a => a.customer[0].OtherAddress);
    

    Not being sure what you mean by dynamic, you can create the lambda

    x => x.OrderBy(a => a.customer[0].Otheraddress)
    

    using Expression as follows:

    var parmx = Expression.Parameter(QueryData.GetType(), "x");
    var parma = Expression.Parameter(QueryData[0].GetType(), "a");
    var abc2 = Expression.Lambda(Expression.Call(MyExtensions.GetMethodInfo((IEnumerable<Orders> x)=>x.OrderBy(a => a.customer[0].OtherAddress)),
                                                 new Expression[] { parmx,
                                                                    Expression.Lambda(Expression.Property(Expression.ArrayIndex(Expression.Property(parma, "customer"), Expression.Constant(0)), "OtherAddress"), new[] { parma }) }),
                                 new[] { parmx });
    
    链接地址: http://www.djcxy.com/p/68304.html

    上一篇: 如何从Bash脚本检测操作系统?

    下一篇: 如何动态构建()=> new {x.prop} lambda表达式?