2

How do I extract a value from a Member Expression where the expression within the Member Expression is not a Constant, but a Parameter Expression.

I am building a small Linq to MDX ORM for our company.

In my generator template, each Dimension found in the database is a class, and in each dimension, there are the Attribute properties that are found for that dimension. After all the dimension classes are generated, a higher level Cube class is generated that contains the Dimensions as properties, as well as the Measures for that cube. After all the cube classes are generated, a final class is built that contains the cube classes that were generated as Cube<MyCube> properties, where the Cube<> is my IQueryable type. Here's an example.

//Generated cs file example:
public partial MyDatabase : CubeBase
{
    //Some base implementation goes here        

    public Cube<FooCube> FooCube { get { return new Cube<FirstCube>(new Provider("connection string"); } }

    //Any other cubes would follow here that were found on this database.
} 

//A calendar dimension
public class FooCube_CalendarDimension
{
    public Attribute WeekEnding { get { return new Attribute("[Calendar].[Week Ending]"); } }

    public Attribute Month { get { return new Attribute("[Calendar].[Month]"); } }

    public Attribute Year { get { return new Attribute("[Calendar].[Year]"); } }
}

//The "FooCube" 
public partial class FooCube
{
    //List Dimensions
    public FooCube_Calendar_Dimension Calendar { get { return new FooCube_Calendar_Dimension(); }     }
    //Other dimensions here

    [Measure]
    public string RetailDollars { get { return "[Retail Dollars]"; } }
    // Other measures here
}

Now, an example of a very basic linq query to query the cube:

//using MyDatabase = db
var mdx = from cube in db.FooCube
          where cube.Calendar.Year == "2014"
          select new
          {
              Month = cube.Calendar.Month.Children
              Dollars = cube.RetailDollars
          }

For example, I'm trying to get the value from cube.Calendar.Month.Children, which comes from the Attribute object that is a property of the FooCube_Calendar_Demsion class, that is in itself a property in the "FooCube" class.

I tried the answer from Access the value of a member expression, but I get the error, "the 'cube' parameter was not referenced" when it tries to compile the lambda expression. The value that it passes to the attribute class's constructor is stored in a property, and that's the value (one of them) that I want to access.

Community
  • 1
  • 1
mHallmark
  • 33
  • 1
  • 5
  • 1
    What value are you trying to get out out that query, specifically? There are lots of member expressions in that query; you need to be clear which ones you're trying to evaluate. – Servy Dec 03 '14 at 17:40
  • There is a "Tag" property in the Attribute class. Thats where the "[Calendar].[Month]" string is stored. There's also a "Children" property that simply concatenates ".CHILDREN" to the value and returns "[Calendar].[Month].CHILDREN. These are the values i want. – mHallmark Dec 03 '14 at 17:43
  • You should use C# attributes for your properties for information about that property used in creating the query. The value returned from the property should be the results of the query, not the values used in creating the query. When your properties are using C# attributes on them then you can inspect those values without needing to have specific instances of those types. Look at something like EF as an example here. – Servy Dec 03 '14 at 17:45
  • That is what I resorted to, for example [Tag(Tag="[Calendar].[Month]")], which works fine, but I also wanted to give the user the ability to add query scoped user Members and Sets in another partial class so they can call those in the linq query, and I wanted them to be able to do so using the generated objects. Also, I wanted users to be able to add these calculated members on the fly using the "let" expression in the linq query. The only way to be able to do this is to somehow extract the values from these finicky Member Expressions. – mHallmark Dec 03 '14 at 17:50

2 Answers2

1

Basically, you can't. At least, not in any sensible way. Currently all that you have is a query. You don't actually have a collection of objects, you just have information about what you need to do to create those objects. It is the job of the query provider that you're currently in the process of writing to actually build the objects that the query defines and return them.

You've designed your program such that the query provider that creates the objects needs to have the already created objects already in order to properly build the query. It's impossible to already have the objects defined by the query that you haven't built yet. You've created a circular dependency for yourself.

It's important for you to provide the information needed to build the query somewhere other than in instances the objects that the query itself creates. Typically this is done with attributes on the properties, or by basing the query on the other existing C# metadata about the type itself. This type data exists, and is accessible to your query provider without needing any actual instances of the objects you're tasked with creating.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • This makes sense. I'm still very new to expressions and linq queries, but I'm understanding what you're saying. If there wasn't the whole user created members and sets thing i would be golden. Maybe I'll just have to implement those in a different manner. – mHallmark Dec 03 '14 at 18:04
-1

I'm adding this answer because I found two ways of extracting the value that I want. I want to thank Servey though for his answer as he was indeed correct about not doing it in any sensible way in that I had my code written.

What I found was two ways of going about this.

  1. Using a generic translator class with a generic parameter that was the class type of the parameter that my lambda expression was wanting, and then using that generic parameter as the input parameter on the Func<object, T> delgate. This is the best and fastest way of doing it, as there is no dynamic operations going on during runtime.

var lambda = Expression.Lambda<Func<object, T>>(//Expressions here);

  1. The second way I found is slower as it involves the Delegate.DynamicInvoke() method, but it does work.

    var lambda = Expression.Lambda(member, (ParameterExpression)member.Expression); var d = lambda.Compile(); return d.DynamicInvoke(member.Expression.Type.GetConstructors()[0].Invoke(new object[0]));

This will get the object's value but is costly due to the dynamic invoke.

mHallmark
  • 33
  • 1
  • 5