I'm trying to create a MongoDB access layer function (series of functions) that allows me to update a specific property, or object property of a given record. The property or object property can be nested at multiple different layers within the record.
So far I have this:
public bool UpdateCell(MsCIC allData, Guid CICPageGUID, Guid CICChapterGUID, Guid CICRowGUID, Cell data)
{
// Get expression for cell
var (expressions, filters) = allData.GetCellLambda(allData.MsSubProjectGUID, CICPageGUID, CICChapterGUID, CICRowGUID, data.CICCellGUID);
string propertyPath = expressions.GetExpressionPathString();
return UpdateCell(expressions.Last(), propertyPath, data, filters);
}
Which calls .GetCellLambda(), GetExpressionPathString(), and lastly, UpdateCell()
.GetCellLambda():
public static (List<LambdaExpression> Expressions, List<ArrayFilterDefinition> Filters) GetCellLambda(this MsCIC cic, Guid MsSubProjectGUID, Guid CICPageGUID, Guid CICChapterGUID, Guid CICRowGUID, Guid CICCellGUID)
{
var (expressions, filters) = cic.GetRowLambda(MsSubProjectGUID, CICPageGUID, CICChapterGUID, CICRowGUID);
Expression<Func<Row, IEnumerable<Cell>>> cellsExpression = item => item.Cells;
Expression<Func<Cell, bool>> cellExpression = cell => cell.CICCellGUID == CICCellGUID;
expressions.Add(cellsExpression, cellExpression);
BsonDocumentArrayFilterDefinition<Guid> cellFilter = new BsonDocumentArrayFilterDefinition<Guid>(new BsonDocument("cICCellGUID", CICCellGUID));
filters.Add(cellFilter);
return (expressions, filters);
}
GetExpressionPathString()
public static string GetExpressionPathString(this IEnumerable<LambdaExpression> expressions)
{
List<string> path = new List<string>();
foreach (var expression in expressions)
{
var currentExpression = expression.Body;
while (currentExpression is UnaryExpression unaryExpression && (unaryExpression.NodeType == ExpressionType.Convert || unaryExpression.NodeType == ExpressionType.ConvertChecked))
{
currentExpression = unaryExpression.Operand;
}
string propertyName = GetMemberName(currentExpression);
path.Insert(0, propertyName);
}
return string.Join(".", path);
}
private static string GetMemberName(Expression expression)
{
switch (expression)
{
case MemberExpression memberExpression:
return memberExpression.Member.Name;
case MethodCallExpression methodCallExpression:
return methodCallExpression.Method.Name;
case BinaryExpression binaryExpression:
return GetMemberName(binaryExpression.Left);
default:
throw new NotSupportedException($"Expression type {expression.GetType()} is not supported");
}
}
UpdateCell():
private bool UpdateCell(LambdaExpression where, string propertyPath, Cell propertyValue, List<ArrayFilterDefinition> arrayFilters = null)
{
if (where is Expression<Func<Cell, bool>> whereExpression)
{
return _MsCICAccess.UpdateCell(whereExpression, propertyPath, propertyValue, arrayFilters);
}
throw new ArgumentException($"where statement is not of correct type. Type should be {typeof(Expression<Func<Cell, bool>>).Name} but is {where.Type.Name}");
}
This calls the access layer .UpdateCell():
public bool UpdateCell(Expression<Func<Cell, bool>> where, string propertyPath, Cell propertyValue, List<ArrayFilterDefinition> arrayFilters = null) =>
UpdateEntityProperty(DB.MasterspecDB, MongoCollection.MsCIC, where, propertyPath, propertyValue, arrayFilters);
Which calls the DB layer .UpdateEntityProperty()
public bool UpdateEntityProperty<T>(string dbName, string collectionName, Expression<Func<T, bool>> where, string path, T propertyValue, List<ArrayFilterDefinition> arrayFilters = null)
{
var db = client.GetDatabase(dbName);
var collection = db.GetCollection<T>(collectionName);
string[] propertyPath = path.Split('.');
List<string> nestedProperties = propertyPath.Reverse().ToList();
var update = Builders<T>.Update.Set(string.Join(".", propertyPath), propertyValue);
foreach (string currentProperty in nestedProperties.Skip(1))
{
update = Builders<T>.Update.Set(currentProperty, update);
}
var result = arrayFilters != null && arrayFilters.Any()
? collection.UpdateOne(where, update, new UpdateOptions { ArrayFilters = arrayFilters })
: collection.UpdateOne(where, update);
return result.ModifiedCount > 0;
}
However, I am getting an error on the collection.UpdateOne(where, update, new UpdateOptions { ArrayFilters = arrayFilters }) function call:
MongoDB.Driver.MongoWriteException: 'A write operation resulted in an error.
The array filter for identifier 'cICCellGUID' was not used in the update { $set: { CICCellGUID: { _t: "OperatorUpdateDefinition`2" } } }'
I thought this may because of the names of the properties not perfectly matching up, but when I originally had the identifier as: "CICCellGUID", it was erroring this error:
MongoDB.Driver.MongoWriteException: 'A write operation resulted in an error.
Error parsing array filter :: caused by :: The top-level field name must be an alphanumeric string beginning with a lowercase letter, found 'MsSubProjectGUID''
I'm not sure what I'm doing wrong here. I just need a way to generically update a property at any calculated nested level, based on the full record, and the given GUID's for the whole record -> page -> chapter -> row -> cell.