I'm having issues trying to get the value of an object out of the Expression Tree without using .Compile()
The object is quite simple.
var userModel = new UserModel { Email = "[email protected]"};
The method giving me issues looks like this.
private void VisitMemberAccess(MemberExpression expression, MemberExpression left)
{
var key = left != null ? left.Member.Name : expression.Member.Name;
if (expression.Expression.NodeType.ToString() == "Parameter")
{
// add the string key
_strings.Add(string.Format("[{0}]", key));
}
else
{
// add the string parameter
_strings.Add(string.Format("@{0}", key));
// Potential NullReferenceException
var val = (expression.Member as FieldInfo).GetValue((expression.Expression as ConstantExpression).Value);
// add parameter value
Parameters.Add("@" + key, val);
}
}
The tests I'm running are quite simple
[Test] // PASS
public void ShouldVisitExpressionByGuidObject ()
{
// Setup
var id = new Guid( "CCAF57D9-88A4-4DCD-87C7-DB875E0D4E66" );
const string expectedString = "[Id] = @Id";
var expectedParameters = new Dictionary<string, object> { { "@Id", id } };
// Execute
var actualExpression = TestExpression<UserModel>( u => u.Id == id );
var actualParameters = actualExpression.Parameters;
var actualString = actualExpression.WhereExpression;
// Test
Assert.AreEqual( expectedString, actualString );
CollectionAssert.AreEquivalent( expectedParameters, actualParameters );
}
[Test] // FAIL [System.NullReferenceException : Object reference not set to an instance of an object.]
public void ShouldVisitExpressionByStringObject ()
{
// Setup
var expectedUser = new UserModel {Email = "[email protected]"};
const string expectedString = "[Email] = @Email";
var expectedParameters = new Dictionary<string, object> { { "@Email", expectedUser.Email } };
// Execute
var actualExpression = TestExpression<UserModel>( u => u.Email == expectedUser.Email );
var actualParameters = actualExpression.Parameters;
var actualString = actualExpression.WhereExpression;
// Assert
Assert.AreEqual( expectedString, actualString );
CollectionAssert.AreEquivalent( expectedParameters, actualParameters );
}
I should note that changing
var val = (expression.Member as FieldInfo).GetValue((expression.Expression as ConstantExpression).Value);
to
var val = Expression.Lambda( expression ).Compile().DynamicInvoke().ToString();
will allow the test to pass, however this code needs to run on iOS, and therefore can't use .Compile()
TLDR;
Reflection is ok to use as long as you're not using Emit or Compile. In the question, the value is being extracted for FieldInfo, but it is not being extracted for PropertyInfo. Make sure you can get BOTH.
if ((expression.Member as PropertyInfo) != null)
{
// get the value from the PROPERTY
}
else if ((expression.Member as FieldInfo) != null)
{
// get the value from the FIELD
}
else
{
throw new InvalidMemberException();
}
Long-winded version
So the comments pointed me in the right direction. I struggled slightly with getting the PropertyInfo, but in the end, here's what I came up with.
private void VisitMemberAccess(MemberExpression expression, MemberExpression left)
{
// To preserve Case between key/value pairs, we always want to use the LEFT side of the expression.
// therefore, if left is null, then expression is actually left.
// Doing this ensures that our `key` matches between parameter names and database fields
var key = left != null ? left.Member.Name : expression.Member.Name;
// If the NodeType is a `Parameter`, we want to add the key as a DB Field name to our string collection
// Otherwise, we want to add the key as a DB Parameter to our string collection
if (expression.Expression.NodeType.ToString() == "Parameter")
{
_strings.Add(string.Format("[{0}]", key));
}
else
{
_strings.Add(string.Format("@{0}", key));
// If the key is being added as a DB Parameter, then we have to also add the Parameter key/value pair to the collection
// Because we're working off of Model Objects that should only contain Properties or Fields,
// there should only be two options. PropertyInfo or FieldInfo... let's extract the VALUE accordingly
var value = new object();
if ((expression.Member as PropertyInfo) != null)
{
var exp = (MemberExpression) expression.Expression;
var constant = (ConstantExpression) exp.Expression;
var fieldInfoValue = ((FieldInfo) exp.Member).GetValue(constant.Value);
value = ((PropertyInfo) expression.Member).GetValue(fieldInfoValue, null);
}
else if ((expression.Member as FieldInfo) != null)
{
var fieldInfo = expression.Member as FieldInfo;
var constantExpression = expression.Expression as ConstantExpression;
if (fieldInfo != null & constantExpression != null)
{
value = fieldInfo.GetValue(constantExpression.Value);
}
}
else
{
throw new InvalidMemberException();
}
// Add the Parameter Key/Value pair.
Parameters.Add("@" + key, value);
}
}
Essentially, if the Member.NodeType is a Parameter, then I'm going to use it as a SQL Field. [FieldName]
Otherwise, I'm using it as a SQL Parameter @FieldName ... backwards I know.
If the Member.NodeType is NOT a Parameter, then I check to see if it's either a Model Fieldor a Model Property. From there, I get the appropriate value, and add the Key/Value pair to a Dictionary to be used as SQL Parameters.
The end result is that I build a string that looks something like
SELECT * FROM TableName WHERE
[FieldName] = @FieldName
Then the parameters are passed
var parameters = new Dictionary<string, object> Parameters;
parameters.Add("@FieldName", "The value of the field");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With