We are using Expressions to build a refined select statement (being used by GraphQL .Net) that only queries the properties asked for.
We parse the raw query and use a recursive method to build an expression that we use to generate our select (via entity framework)
Our code is HEAVILY based on this answer provided by #Svyatoslav Danyliv.
Our expressionbuilder class looks like this:
public class ExpressionBuilder
{
public Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(string members)
{
return BuildSelector<TSource, TTarget>(members.Split(',').Select(m => m.Trim()));
}
public Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(IEnumerable<string> members)
{
var parameter = Expression.Parameter(typeof(TSource), "e");
var body = NewObject(typeof(TTarget), parameter, members.Select(m => m.Split('.')));
return Expression.Lambda<Func<TSource, TTarget>>(body, parameter);
}
private Expression NewObject(Type targetType, Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
{
var bindings = new List<MemberBinding>();
var target = Expression.Constant(null, targetType);
foreach (var memberGroup in memberPaths.GroupBy(path => path[depth]))
{
var memberName = memberGroup.Key;
var targetMember = Expression.PropertyOrField(target, memberName);
var sourceMember = Expression.PropertyOrField(source, memberName);
var childMembers = memberGroup.Where(path => depth + 1 < path.Length).ToList();
Expression targetValue = null;
if (!childMembers.Any())
{
targetValue = sourceMember;
}
else
{
if (IsEnumerableType(targetMember.Type, out var sourceElementType) &&
IsEnumerableType(targetMember.Type, out var targetElementType))
{
var sourceElementParam = Expression.Parameter(sourceElementType, "e");
targetValue = NewObject(targetElementType, sourceElementParam, childMembers, depth + 1);
targetValue = Expression.Call(typeof(Enumerable), nameof(Enumerable.Select),
new[] { sourceElementType, targetElementType }, sourceMember,
Expression.Lambda(targetValue, sourceElementParam));
targetValue = CorrectEnumerableResult(targetValue, targetElementType, targetMember.Type);
}
else
{
targetValue = NewObject(targetMember.Type, sourceMember, childMembers, depth + 1);
}
}
bindings.Add(Expression.Bind(targetMember.Member, targetValue));
}
return Expression.MemberInit(Expression.New(targetType), bindings);
}
private bool IsEnumerableType(Type type, out Type elementType)
{
foreach (var intf in type.GetInterfaces())
{
if (intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
elementType = intf.GetGenericArguments()[0];
return true;
}
}
elementType = null;
return false;
}
private bool IsSameCollectionType(Type type, Type genericType, Type elementType)
{
var result = genericType.MakeGenericType(elementType).IsAssignableFrom(type);
return result;
}
private Expression CorrectEnumerableResult(Expression enumerable, Type elementType, Type memberType)
{
if (memberType == enumerable.Type)
return enumerable;
if (memberType.IsArray)
return Expression.Call(typeof(Enumerable), nameof(Enumerable.ToArray), new[] { elementType }, enumerable);
if (IsSameCollectionType(memberType, typeof(List<>), elementType)
|| IsSameCollectionType(memberType, typeof(ICollection<>), elementType)
|| IsSameCollectionType(memberType, typeof(IReadOnlyList<>), elementType)
|| IsSameCollectionType(memberType, typeof(IReadOnlyCollection<>), elementType))
return Expression.Call(typeof(Enumerable), nameof(Enumerable.ToList), new[] { elementType }, enumerable);
throw new NotImplementedException($"Not implemented transformation for type '{memberType.Name}'");
}
}
and the usage is as follows:
var builder = new ExpressionBuilder();
var statement = builder.BuildSelector<Payslip, Payslip>(selectStatement);
The select statement above looks like "id,enrollment.id" where we are selecting parentobject.id and parentobject.enrollment.id
This statement variable is then used in a EntityQueryable.Select(statement) against a filtered database collection there we have essentially done the 'where' aspect of the query.
When working correctly the sql generated is ONLY the fields requested, and it works great. However in this instance it is trying to access the .Id property of a null value and constantly fails.
It is currently failing because the "Enrollment" property of the parentObject is not set, thus null.
We get the following error
System.InvalidOperationException: Nullable object must have a value.
at System.Nullable`1.get_Value()
at lambda_method2198(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
I've tried to implement a try catch expression but keep failing, and end up with the exact same error. At this time, we aren't hitting the database, so we don't know if the value will be null or not (it works fine if it's not null).
I am by no means an expert on expressions, and am really struggling to find just where I should be wrapping this value, or even how. Ideally I'd just like to return null, but worst case a default enrollment object could be detected client side and possibly handled ok.
** UPDATE!! **
Reading into this I think that this is a result of this issue. The workaround would involve building something like this null check into the expression builder.
.Select(r => new Something()
{
X1 = r.X1,
X2 = r.X2,
X = new X()
{
Name= r.X.Name
},
XX = r.XXNullableId == null ? null : new XX()
{
XNumber = r.XX.XNumber
}
})
I have created a VERY simple EF Core Application here that creates a simple three table database and recreates the problem.
I believe the workaround mentioned above will work, it's just a matter of figuring out how to add it to the expression builder.
So I figured this one out (sort of) by special casing the property that causes the issue.
Due to the nature of our model, this situation will only occur in a few places, it's easier to add some workarounds for them, that have the expression builder do some strange null check on every property.
I created a simple attribute that I put on the 'Object' Property and have it point to the property I need to check for null.
That looks like this:
[System.AttributeUsage(System.AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
public class ExpressionBuilderNullCheckForPropertyAttribute : Attribute
{
public ExpressionBuilderNullCheckForPropertyAttribute(string propertyName)
{
this.PropertyName = propertyName;
}
public string PropertyName { get; set; }
}
So now, my 'Person' Class (from the linked example repo above) now looks like this.
public class Person
{
[Required]
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Nullable<Guid> VetId { get; set; }
[ExpressionBuilderNullCheckForProperty(nameof(VetId))]
public Vet Vet { get; set; }
public List<Pet> Pets { get; set; }
}
Then I added a check into the ExpressionBuilder class to check for that attribute, if it is present, lookup the property it has mentioned, then perform a null check. If that property is null, then return a default object for that type. Which, in this case, is null.
if (targetMember.Member.GetCustomAttributesData().Any(x => x.AttributeType == typeof(ExpressionBuilderNullCheckForPropertyAttribute)))
{
var att = (ExpressionBuilderNullCheckForPropertyAttribute)targetMember.Member.GetCustomAttributes(true).First(x => x.GetType() == typeof(ExpressionBuilderNullCheckForPropertyAttribute));
var propertyToCheck = att.PropertyName;
var valueToCheck = Expression.PropertyOrField(source, propertyToCheck);
var nullCheck = Expression.Equal(valueToCheck, Expression.Constant(null, targetMember.Type));
var checkForNull = Expression.Condition(nullCheck, Expression.Default(targetMember.Type), targetValue);
bindings.Add(Expression.Bind(targetMember.Member, checkForNull));
}
else
{
bindings.Add(Expression.Bind(targetMember.Member, targetValue));
}
I have updated the example repo if anyone wants to see the fix working.
Related
We are currently rewriting a lot of duplicate code to manage our Database Queries (with EF) in a very generic fashion. Right now, we are facing some problems with resolving the correct Property Value to use in Expressions.
private Expression<Func<TEntity, bool>> BuildGenericSearchExpression()
{
Expression<Func<TEntity, bool>> expression = x => false;
var searchFields = GenericTableOptions.SearchColumns;
var propertyInfos = GetSearchColumnsAsProperties(typeof(TEntity), searchFields).ToList();
if (propertyInfos.Count == 0)
{
return expression;
}
foreach (var propertyInfo in propertyInfos)
{
var value = propertyInfo.Name;
expression = x => propertyInfo.GetValue(x, null).ToString().Contains("Hans");
}
return expression;
}
private static IEnumerable<PropertyInfo> GetSearchColumnsAsProperties(Type targetType, IEnumerable<string> properties)
{
IList<PropertyInfo> propertyInfos = new List<PropertyInfo>();
if (targetType == null)
{
throw new GenericTableException($"Type has no value set");
}
foreach (var property in properties)
{
var propertyInfo = targetType?.GetProperty(property);
if (propertyInfo == null)
{
throw new GenericTableException($"Property {property} is not available on Type {targetType}");
}
propertyInfos.Add(propertyInfo);
}
return propertyInfos;
}
For a quick Test, I have created a basic User class.
public class User
{
public string FirstName { get; set; }
}
FirstName is set as the Search Column and "Hans" is used as the search term.
The internal test list uses two users with different FirstNames, but it always returns both of them.
public Task<IList<User>> FindPaginatedAsync(Expression<Func<User, bool>> predicate, int page, int size)
{
IList<User> users = new List<User> { new User { FirstName = "Hans" }, new User { FirstName = "Franz" }};
IList<User> result = users.AsQueryable().Where(predicate);
return Task.FromResult(result);
}
The created Expression is passed as the predicate, page and size aren't used yet. Any Ideas on that how to get me out of that rabbit hole.
Ok, just if anyone falls into the same trap.
AsQueryable returns an IEnumerable which does not execute the Query immediately. When you eventually convert it to a List the query gets executed and works as expected.
In our case, this will do the trick.
var result = users.AsQueryable().Where(predicate).ToList();
In the end we will keep working with the IEnumerable and convert it as late as possible.
Check this fiddle for the error: https://dotnetfiddle.net/tlz4Qg
I have two classes like this:
public class ParentType{
private ParentType(){}
public int Id { get; protected set; }
public SubType Sub { get; protected set; }
}
public class SubType{
private SubType(){}
public int Id { get; protected set; }
}
I am going to transform a multilevel anonymous expression to a multilevel non-anonymous expression. To achieve this I have an expression like the below-mentioned one:
x => new
{
x.Id,
Sub = new
{
x.Sub.Id
}
}
To achieve that goal, I have transformed it to an expression like this:
x => new ParentType()
{
Id = x.Id,
Sub = new SubType()
{
Id = x.Sub.Id
},
}
But when I call Compile() method, I get the following error:
Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined
Here is my visitor class:
public class ReturnTypeVisitor<TIn, TOut> : ExpressionVisitor
{
private readonly Type funcToReplace;
private ParameterExpression currentParameter;
private ParameterExpression defaultParameter;
private Type currentType;
public ReturnTypeVisitor() => funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));
protected override Expression VisitNew(NewExpression node)
{
if (!node.Type.IsAnonymousType())
return base.VisitNew(node);
if (currentType == null)
currentType = typeof(TOut);
var ctor = currentType.GetPrivateConstructor();
if (ctor == null)
return base.VisitNew(node);
NewExpression expr = Expression.New(ctor);
IEnumerable<MemberBinding> bindings = node.Members.Select(x =>
{
var mi = currentType.GetProperty(x.Name);
//if the type is anonymous then I need to transform its body
if (((PropertyInfo)x).PropertyType.IsAnonymousType())
{
//This section is became unnecessary complex!
//
var property = (PropertyInfo)x;
var parentType = currentType;
var parentParameter = currentParameter;
currentType = currentType.GetProperty(property.Name).PropertyType;
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
//I pass the inner anonymous expression to VisitNew and make the non-anonymous expression from it
var xOriginal = VisitNew(node.Arguments.FirstOrDefault(a => a.Type == property.PropertyType) as NewExpression);
currentType = parentType;
currentParameter = parentParameter;
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
else//if type is not anonymous then simple find the property and make the memberbinding
{
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
});
return Expression.MemberInit(expr, bindings);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T) != funcToReplace)
return base.VisitLambda(node);
defaultParameter = node.Parameters.First();
currentParameter = defaultParameter;
var body = Visit(node.Body);
return Expression.Lambda<Func<TIn, TOut>>(body, currentParameter);
}
}
And use it like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
var visitor = new ReturnTypeVisitor<Tin, Tout>();
var result = (Expression<Func<Tin, Tout>>)visitor.Visit(source);
return result;// result.Compile() throw the aforementioned error
}
Here is the extension methods used inside my Visitor class:
public static ConstructorInfo GetPrivateConstructor(this Type type) =>
type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
// this hack taken from https://stackoverflow.com/a/2483054/4685428
// and https://stackoverflow.com/a/1650895/4685428
public static bool IsAnonymousType(this Type type)
{
var markedWithAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), inherit: false).Any();
var typeName = type.Name;
return markedWithAttribute
&& (typeName.StartsWith("<>") || type.Name.StartsWith("VB$"))
&& typeName.Contains("AnonymousType");
}
Update
Here is the .Net Fiddle link for the problem: https://dotnetfiddle.net/tlz4Qg
Update
I have removed the extra codes that seems to be out of the problem scope.
The cause of the problem in question is the line
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
inside VisitNew method.
With your sample, it creates a new parameter called "x.Sub", so if we mark the parameters with {}, the actual result is
Sub = new SubType()
{
Id = {x.Sub}.Id
},
rather than expected
Sub = new SubType()
{
Id = {x}.Sub.Id
},
In general you should not create new ParameterExpressions except when remapping lambda expressions. And all newly created parameters should be passed to Expression.Lambda call, otherwise they will be considered "not defined".
Also please note that the visitor code has some assumptions which doesn't hold in general. For instance
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
won't work inside nested new, because there you need access to a member of the x parameter like x.Sub.Id rather than x.Id. Which is basically the corersonding expression from NewExpression.Arguments.
Processing nested lambda expressions or collection type members and LINQ methods with expression visitors requires much more state control. While converting simple nested anonymous new expression like in the sample does not even need a ExpressionVisitor, because it could easily be achieved with simple recursive method like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
return Expression.Lambda<Func<Tin, Tout>>(
Transform(source.Body, typeof(Tout)),
source.Parameters);
}
static Expression Transform(Expression source, Type type)
{
if (source.Type != type && source is NewExpression newExpr && newExpr.Members.Count > 0)
{
return Expression.MemberInit(Expression.New(type), newExpr.Members
.Select(m => type.GetProperty(m.Name))
.Zip(newExpr.Arguments, (m, e) => Expression.Bind(m, Transform(e, m.PropertyType))));
}
return source;
}
I have a method that orders a queryable by a property via reflection, like so:
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var parts = orderByProperty.Split('.');
var parameter = Expression.Parameter(typeof(TEntity), "p");
Expression body = parameter;
if (parts.Length > 1)
{
for (var i = 0; i < parts.Length - 1; i++)
{
body = Expression.Property(body, parts[i]);
}
}
var property = body.Type.GetProperty(parts.LastOrDefault())
body = Expression.Property(body, property.Name);
var orderByExpression = Expression.Lambda(body, parameter);
resultExpression = Expression.Call(typeof(Queryable), command,
new Type[] { typeof(TEntity), property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}
And it works if I use it for the object's properties or its children's properties, but it doesn't work when a child property is a List.
// Success
db.Users.OrderBy("Name", true)
db.Users.OrderBy("CurrentAddress.City.Name", false)
// Fail
db.Users.OrderBy("PhoneLines.RenewalDate", true)
My model is like so:
public class User {
public string Name { get; set; }
public Address CurrentAddress{ get; set; }
public List<Phone> PhoneLines { get; set; }
}
And I am trying to do something like this work:
db.Users.OrderByDescending(x =>
x.PhoneLines
.OrderByDescending(y => y.RenewalDate)
.Select(y => y.RenewalDate).FirstOrDefault()
);
Right now I am only interested in making this work for first level List children, that is, I am not concerned about a cascading list scenario, just one child in the chain is a list.
So far my attempts have failed. I am checking if the last property is a collection and if so i tried creating a lambda for the list and another lambda for the class' element target order property.
if (body.Type.Namespace.Equals("System.Collections.Generic"))
{
var mainType = body.Type.GenericTypeArguments.FirstOrDefault();
var parameterInner = Expression.Parameter(mainType, "x");
var mainProperty = Expression.Property(parameterInner, property.Name);
var orderByExpressionInner = Expression.Lambda(mainProperty, parameterInner);
var orderByExpression = Expression.Lambda(body, parameter);
var selM = typeof(Enumerable).GetMethods()
.Where(x => x.Name == command)
.First().MakeGenericMethod(mainType, property.PropertyType);
var resultExpressionOuter = Expression.Call(typeof(Queryable),
command, new Type[] { typeof(TEntity), body.Type },
source.Expression,
Expression.Call(null, selM, parameterInner, orderByExpressionInner
));
}
My knowledge with reflection is limited, is it possible to achieve this via reflection?
One option is to place a new property on your parent class and make this responsible for returning the value which you require. Such as this;
public class User
{
public string Name { get; set; }
public Address CurrentAddress { get; set; }
public List<Phone> PhoneLines { get; set; }
private DateTime _dummyValue;
public DateTime GetRenewalDate
{
get
{
if (this.PhoneLines == null || this.PhoneLines.Count == 0)
return DateTime.MinValue;
else
{
return this.PhoneLines.OrderByDescending(y => y.RenewalDate).Select(y => y.RenewalDate).FirstOrDefault();
}
}
set
{
_dummyValue = value;
}
}
}
Make sure you have default handling in case your list is empty or null, here I return MinValue of DateTime.
Edit:
To get around the other issue you have put in the comments
The specified type member 'GetRenewalDate' is not supported in LINQ to
Entities. Only initializers, entity members, and entity navigation
properties are supported.
You could try adding a dummy set, this might also work as a workaround (but is not elegant).
I have edited the code above to show this
I'd like to be able to update a key/value pair within a Document in a Collection without regard to the _id. This SO response appears to provide a way to do what I want, but it requires a focus on the _id key.
Essentially, what can I do to get my code, as shown below, to work without adding the [BsonIgnoreExtraElements] attribute to the class?
Based on the code which I'll show below, I think I'm close, and I do have a possible workaround, using the attribute, which I'll also show. However, I'd like to do this without decorating the classes with any attributes if possible.
Why no _id? Personally, I find the _id field just gets in the way. It's not part of any of my classes, and MongoDB isn't relational, so I simply don't use it. I freely admit that I may be completely missing the intent and idea behind the use of the _id field, but I simply don't see a need for it in MongoDB. Nevertheless, if possible, I'd like to focus on working without a focus on the _id field.
With that said, I have methods for retrieving a single document or an entire collection along with inserting a single document or entire collection, generically, without regard to the _id field.
I found that the key to ignoring the _id in a "select" is to use a Projection. I'm not all that familiar with the driver, but here are the lines of code that do the magic:
public const string ELEMENT_ID = "_id";
ProjectionDefinition<T> projection = Builders<T>.Projection.Exclude(ELEMENT_ID);
IFindFluent<T, T> found = mongoCollection.Find(bsonDocument).Project<T>(projection);
For background info, here is a retrieval method, ignoring _id:
public T GetSingle<T>(string property, string value) where T : class, new()
{
T tObject = null;
try
{
if (MongoContext.MongoClient != null && MongoContext.MongoDatabase != null)
{
string className = typeof(T).ToString();
int lastPeriod = className.LastIndexOf('.');
int length = className.Length - lastPeriod;
className = className.Substring(lastPeriod + 1, length - 1);
if (!string.IsNullOrEmpty(className))
{
IMongoCollection<T> mongoCollection = MongoContext.MongoDatabase.GetCollection<T>(className);
if (mongoCollection != null)
{
BsonDocument bsonDocument = new BsonDocument();
ProjectionDefinition<T> projection = Builders<T>.Projection.Exclude(ELEMENT_ID);
PropertyInfo[] propertyInfo = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null && propertyInfo.Length > 0)
{
IEnumerable<PropertyInfo> piExisting = propertyInfo.Where(pi => pi.Name.Equals(property, StringComparison.CurrentCultureIgnoreCase));
if (piExisting != null && piExisting.Any())
{
BsonValue bsonValue = BsonValue.Create(value);
BsonElement bsonElement = new BsonElement(property, bsonValue);
if (bsonElement != null)
{
bsonDocument.Add(bsonElement);
}
IFindFluent<T, T> found = mongoCollection.Find(bsonDocument).Project<T>(projection);
if (found != null)
{
tObject = found.FirstOrDefault<T>();
}
}
}
}
}
}
}
catch (Exception ex)
{
Logger.WriteToLog(Logger.LoggerMessage(ex));
}
return tObject;
}
Similar to other another SO response, I've tried to use FindOneAndUpdate, but I receive the following error:
Element '_id' does not match any field or property of class
ClassThingy.Suffix.
If I could apply the Projection to the FindOneAndUpdate somehow, I think that might resolve my issue, but I'm not able to find a way to do that application.
Here is my code:
public T UpdateSingle<T>(T item, string property, object originalValue, object newValue) where T : class, new()
{
string className = string.Empty;
T updatedDocument = null;
try
{
if (MongoContext.MongoClient != null && MongoContext.MongoDatabase != null)
{
className = ClassUtility.GetClassNameFromObject<T>(item);
if (!string.IsNullOrEmpty(className))
{
IMongoCollection<T> mongoCollection = MongoContext.MongoDatabase.GetCollection<T>(className);
if (mongoCollection != null)
{
BsonDocument bsonDocument = new BsonDocument();
ProjectionDefinition<T> projection = Builders<T>.Projection.Exclude(ELEMENT_ID);
PropertyInfo[] propertyInfo = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (propertyInfo != null && propertyInfo.Length > 0)
{
IEnumerable<PropertyInfo> piExisting = propertyInfo.Where(pi => pi.Name.Equals(property, StringComparison.CurrentCultureIgnoreCase));
if (piExisting != null && piExisting.Any())
{
BsonValue bsonValue = BsonValue.Create(originalValue);
BsonElement bsonElement = new BsonElement(property, bsonValue);
if (bsonElement != null)
{
bsonDocument.Add(bsonElement);
}
IFindFluent<T, T> found = mongoCollection.Find(bsonDocument).Project<T>(projection);
if (found != null)
{
FilterDefinition<T> filterDefinition = Builders<T>.Filter.Eq(property, originalValue);
UpdateDefinition<T> updateDefinition = Builders<T>.Update.Set(property, newValue);
updatedDocument = mongoCollection.FindOneAndUpdate<T>(filterDefinition, updateDefinition);
}
}
}
}
}
}
}
catch (Exception ex)
{
Logger.WriteToLog(Logger.LoggerMessage(ex));
}
return updatedDocument;
}
Interestingly enough, while the FindOneAndUpdate method actually appears to succeed and the "Suffix" collection does, in fact, get modified. Also, the return doesn't contain the modification. Instead, it's the "original" (when I use the workaround as shown below).
More Info:
Suffix Class:
public class Suffix
{
public string Code { get; set; }
public string Description { get; set; }
}
Suffix suffix = new Suffix();
MongoRepository.MongoRepository mongoRepository = new MongoRepository.MongoRepository("MyDataBase");
mongoRepository.UpdateSingle<Suffix>(suffix, "Description", "Jr", "Junior");
Workaround:
[BsonIgnoreExtraElements]
public class Suffix
{
public string Code { get; set; }
public string Description { get; set; }
}
But, much rather not use the attribute if at all possible.
One thing you're missing here is the third parameter of .FindOneAndUpdate() method which is of type FindOneAndUpdateOptions<T,T>. That's the place where you can define if you want to get document After or Before the modification. Before is the default value Moreover you can specify the projection and exclude _id property. Try:
FilterDefinition<T> filterDefinition = Builders<T>.Filter.Eq(property, originalValue);
UpdateDefinition<T> updateDefinition = Builders<T>.Update.Set(property, newValue);
ProjectionDefinition<T, T> projection = Builders<T>.Projection.Exclude("_id");
var options = new FindOneAndUpdateOptions<T>()
{
Projection = projection,
ReturnDocument = ReturnDocument.After
};
updatedDocument = mongoCollection.FindOneAndUpdate<T>(filterDefinition, updateDefinition, options);
I have searched, and found similar posts pertaining to my issue, however nothing seems to solve my problem.
I am fairly new to C#, and this is my first attempt at building an expression tree. (please go easy ;-)
I am trying to create an expression tree which would, once compiled, filter values on a set of data.
Here is my expression method:
private static Expression<Func<TItem, bool>> CreateFilterExpression<TItem>(string propertyName, string expressionType, dynamic filterValue)
{
if (param == null)
{
param = Expression.Parameter(typeof(TItem), "item");
}
MemberExpression member = GetMemberExpression<TItem>(propertyName);
//When we call our method, we need to evaluate on the same type
//we convert the filter value to the type of the property we are evaluating on
dynamic convertedValue = Convert.ChangeType(filterValue, member.Type);
MethodInfo method = member.Type.GetMethod(expressionType, new[] { member.Type });
ConstantExpression constantValue = Expression.Constant(convertedValue, member.Type);
Expression containsMethodExp;
if (expressionType == "NotEqual")
{
method = member.Type.GetMethod("Equals", new[] { member.Type });
}
if (member.Type.ToString().ToLower() == "system.string")
{
//We need to compare the lower case of property and value
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
MethodCallExpression filterValueToLowerCase = Expression.Call(constantValue, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
containsMethodExp = Expression.Call(propertyValueToLowerCase, method, filterValueToLowerCase);
}
else if (member.Type.ToString().ToLower() == "system.datetime")
{
//we need to compare only the dates
MemberExpression dateOnlyProperty = Expression.Property(member, "Date");
containsMethodExp = Expression.Call(dateOnlyProperty, method, constantValue);
}
else
{
containsMethodExp = Expression.Call(member, method, constantValue);
}
if (expressionType == "NotEqual")
{
containsMethodExp = Expression.Not(containsMethodExp);
}
return Expression.Lambda<Func<TItem, bool>>(containsMethodExp, param);
}
private static MemberExpression GetMemberExpression<TItem>(string propertyName)
{
if (param == null)
{
param = Expression.Parameter(typeof(TItem), "item");
}
MemberExpression member = null;
//Check if we have a nested property
if (propertyName.Contains('.'))
{
Expression nestedProperty = param;
string[] properies = propertyName.Split('.');
int zeroIndex = properies.Count() - 1;
for (int i = 0; i <= zeroIndex; i++)
{
if (i < zeroIndex)
{
nestedProperty = Expression.PropertyOrField(nestedProperty, properies[i]);
}
else
{
member = Expression.Property(nestedProperty, properies[i]);
}
}
}
else
{
member = Expression.Property(param, propertyName);
}
return member;
}
Example usage would be like so:
var lambda = CreateFilterExpression<T>("Some.Nested.Object", "Equals", "Some value");
var compiled = lambda.Compile();
gridData = gridData.Where(compiled);
An example of the data I trying to ultimately bind to my grid looks like this:
public class Some : BaseClass
{
public decimal NumberAvailable { get; set; }
public DateTime EffectiveDate { get; set; }
public Batch Batch { get; set; }
public decimal Price { get; set; }
public decimal Limit { get; set; }
public NestedClass Nested { get; set; }
public int? CompanyId { get; set; }
public decimal Amount { get; set; }
}
public class NestedClass : BaseClass
{
public int RequestId { get; set; }
public string Code { get; set; }
public string Company { get; set; }
public string Reference { get; set; }
}
The problem occurs when we have null value on an object, like "Some.Nested = null", and then trying to convert "Reference" to lowercase. Here:
MethodCallExpression propertyValueToLowerCase = Expression.Call(member, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Here is the result in debugger:
How can I check for null values, on nested objects, and return empty string if it is null?
I hope I explained my question well enough. Thank you in advance!
What you want to do is to generate an expression like this:
Some == null ? null : Some.Nested == null ? null : Some.Nested.Object
This unfortunately is no longer a member expression, so GetMemberExpression wouldn’t work for this. Instead you need a chain of conditional expression that accesses one more level at a time.
Once you have that, you could then do <memberExpression> ?? string.Empty to get a string which you can safely operate on.
To generate the latter expression, you can use Expression.Coalesce:
Expression.Coalesce(memberExpression, Expression.Constant(string.Empty))
For the member expression itself, you could write something like this:
Expression AccessMember(Expression obj, string propertyName)
{
string[] parts = propertyName.Split(new char[] { '.' }, 2);
Expression member = Expression.PropertyOrField(obj, parts[0]);
if (parts.Length > 1)
member = AccessMember(member, parts[1]);
return Expression.Condition(Expression.Equal(obj, Expression.Constant(null)),
Expression.Constant(null, member.Type), member);
}
This can be used like this:
string path = "Some.Nested.Object";
string[] parts = path.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(T), parts[0]);
Expression memberAccess = AccessMember(param, parts[1]);
memberAccess would then be exactly the above chained conditional expression.
Combined into your function (simplified only for strings for now), it could look like this:
Expression<Func<TObj, bool>> BuildFilterExpression<TObj, TMember>(string propertyPath, TMember comparisonValue, TMember defaultValue)
{
string[] parts = propertyPath.Split(new char[] { '.' }, 2);
ParameterExpression param = Expression.Parameter(typeof(TObj), parts[0]);
// get member access expression
Expression memberExpression = AccessMember(param, parts[1]);
// coalesce the member with the default value
memberExpression = Expression.Coalesce(memberExpression, Expression.Constant(defaultValue));
// get the comparison value as expression
Expression comparisonExpression = Expression.Constant(comparisonValue);
// type specific logic
if (memberExpression.Type == typeof(string))
{
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
memberExpression = Expression.Call(memberExpression, toLowerMethod);
comparisonExpression = Expression.Call(comparisonExpression, toLowerMethod);
}
// create the comparison expression
Expression filterExpression = Expression.Equal(memberExpression, comparisonExpression);
return Expression.Lambda<Func<TObj, bool>>(filterExpression, param);
}
Used like this:
BuildFilterExpression<SomeType, string>("Some.Nested.Object", "foo bar", string.Empty)
… it essentially creates the following lambda expression:
(Some) => ((Some == null ? null : Some.Nested == null ? null : Some.Nested.Object) ?? string.Empty).ToLower() == "foo bar"
Above code assumes that for a property expression Some.Nested.Object, Some is the object that is being passed to the lambda, so the first property that would be accessed is Nested. The reason is that I simply didn’t know your example object structure, so I had to come up with something.
If you want Some be the first property that is accessed for the passed object, you can easily change that though. To do that, modify the beginning of BuildFilterExpression so that the propertyPath is not split up. Pass some random name (or no name even) to Expression.Parameter, and pass the full propertyPath to AccessMember:
// don’t split up the propertyPath
// let’s call the parameter `obj`
ParameterExpression param = Expression.Parameter(typeof(TObj), "obj");
// get member access expression—for the full property path
Expression memberExpression = AccessMember(param, propertyPath);