Creating a MongoDB UpdateOne generic type/property update function

41 views Asked by At

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.

0

There are 0 answers