LINQ member expression getting column name
Asked Answered
S

1

6

Hello,

I am using LINQ and EF with C# 4.0. I have dragged the basic ELMAH table into EF (built and saved many many times). All is working as one would expect.

But have tried to be too ambitious and need a little help - I am trying to get the Column name from an expression that is passed in as a variable.

What I want is this:

Pass in : x=>x.ErrorId

and get : "ErrorId"

public void GetColumnName(Expression<Func<T, object>> property)
{
  // The parameter passed in x=>x.Message
  // Message works fine (probably because its a simple string) using:
  string columnName = (property.Body as MemberExpression).Member.Name;

  // But if I attempt to use the Guid or the date field then it
  // is passed in as x => Convert(x.TimeUtc)
  // As a result the above code generates a NullReference exception
  // i.e. {"Object reference not set to an instance of an object."}

  // What is the correct code here to extract the column name generically?
  // Ideally in a way that won't bite me again in the future.

}

Thank you for your help! Dan.

Semiliterate answered 7/6, 2011 at 18:20 Comment(2)
So you're looking to determine the column name from a potentially more complicated expression than just x.ColumnName?Myriagram
try debugging and put property.Body as MemberExpression into the watch, once you hit the ErrorId you might see how to extract itTool
M
6

If you need to also decompose simple (or nearly simple) expressions, you'll need some extra legwork to handle the different situations. Here is some starter code that handles some common cases:

string GetColumnName<T,TResult>(Expression<Func<T,TResult>> property)
{
    var member = GetMemberExpression(property.Body);
    if (member == null)
        throw new ArgumentException("Not reducible to a Member Access", 
                                    "property");

    return member.Member.Name;
}

MemberExpression GetMemberExpression(Expression body)
{
    var candidates = new Queue<Expression>();
    candidates.Enqueue(body);
    while (candidates.Count > 0)
    {
        var expr = candidates.Dequeue();
        if (expr is MemberExpression)
        {
            return ((MemberExpression)expr);
        }
        else if (expr is UnaryExpression)
        {
            candidates.Enqueue(((UnaryExpression)expr).Operand);
        }
        else if (expr is BinaryExpression)
        {
            var binary = expr as BinaryExpression;
            candidates.Enqueue(binary.Left);
            candidates.Enqueue(binary.Right);
        }
        else if (expr is MethodCallExpression)
        {
            var method = expr as MethodCallExpression;
            foreach (var argument in method.Arguments)
            {
                candidates.Enqueue(argument);
            }
        }
        else if (expr is LambdaExpression)
        {
            candidates.Enqueue(((LambdaExpression)expr).Body);
        }
    }

    return null;
}

Which produces output like:

GetColumnName((x) => x.X): "X"
GetColumnName((x) => x.X + 2): "X"
GetColumnName((x) => 2 + x.X): "X"
GetColumnName((x) => -x.X): "X"
GetColumnName((x) => Math.Sqrt(x.Y)): "Y"
GetColumnName((x) => Math.Sqrt(Math.Abs(x.Y))): "Y"
Myriagram answered 7/6, 2011 at 18:42 Comment(4)
using almost exactly same code, especially to fix this silly Include statement in EF, which extremely hard to refactor because of strings passed as params.Dramatic
Superb answer! Works like a charm. Are there any limitations I should be aware of? Just so you know I have a HTMLHelper in MVC which takes these lambda expressions to build a customised list of fields to display (overridding the default) so should be super for what I need. Thanks!Semiliterate
@Dan: if you have more than one property in there it will return the left-most. Moreover it will return the left-most of any member access. This means some complicated expressions like (x) => y.PropA + x.PropB will report PropA.Myriagram
ok - great to know. You made my evening a very happy one! Thanks again!Semiliterate

© 2022 - 2024 — McMap. All rights reserved.