Skip to content

Instantly share code, notes, and snippets.

@leandromoh
Created September 14, 2023 21:53
Show Gist options
  • Save leandromoh/a77eb7433f2c7088486f2b751fa880a7 to your computer and use it in GitHub Desktop.
Save leandromoh/a77eb7433f2c7088486f2b751fa880a7 to your computer and use it in GitHub Desktop.
C# extensions for MongoDB BsonDocument
public static class MongoExtensions
{
public static readonly Regex Indexer = new Regex(@"\[\d+\]", RegexOptions.Compiled);
public static void FullMerge(this BsonDocument b1, BsonDocument b2)
{
for (int i = 0; i < b2.ElementCount; i++)
{
var e2 = b2.ElementAt(i);
if (b1.TryGetElement(e2.Name, out var e1))
{
if (e2.Value.IsBsonDocument && e1.Value.IsBsonDocument)
{
FullMerge(e1.Value.AsBsonDocument, e2.Value.AsBsonDocument);
}
else if (e2.Value.IsBsonArray && e1.Value.IsBsonArray)
{
var a1 = e1.Value.AsBsonArray;
var a2 = e2.Value.AsBsonArray;
for (int j = 0; j < a2.Count; j++)
{
if (j < a1.Count)
{
FullMerge(a1[j].AsBsonDocument, a2[j].AsBsonDocument);
}
else
{
a1[j] = a2[j];
}
}
}
else
{
b1[e2.Name] = e2.Value;
}
}
else
{
b1[e2.Name] = e2.Value;
}
}
}
public static void RecursivelyRemove(this BsonDocument bson, string path)
{
var cleaned = Indexer.Replace(path, "[]");
var parts = cleaned.Split(".");
var last = parts[parts.Length - 1];
var current = bson;
var j = 0;
foreach (var member in parts.SkipLast(1))
{
j++;
var isCollection = member.EndsWith("]");
if (isCollection)
{
var field = member.Split('[')[0];
var array = current[field].AsBsonArray;
var subpath = string.Join(".", parts.Skip(j));
for (var i = 0; i < array.Count; i++)
{
RecursivelyRemove(array[i].AsBsonDocument, subpath);
}
return;
}
else
{
current = current[member].AsBsonDocument;
}
}
current.Remove(last);
}
public static void Iterate(this BsonValue bson, Action<BsonValue, string> action)
{
Iterate(bson, string.Empty, action);
}
private static void Iterate(BsonValue bson, string builder, Action<BsonValue, string> action)
{
if (bson.IsBsonDocument)
{
var document = bson.AsBsonDocument;
foreach (var el in document.Elements)
{
if (el.Value.IsBsonDocument)
{
Iterate(el.Value.AsBsonDocument, path(builder, el.Name), action);
}
else if (el.Value.IsBsonArray)
{
var array = el.Value.AsBsonArray;
for (int i = 0; i < array.Count; i++)
{
Iterate(array[i], path(builder, $"{el.Name}[{i}]"), action);
}
}
else
{
Iterate(el.Value, path(builder, el.Name), action);
}
}
}
else
{
action(bson, builder);
}
static string path(string source, string add)
{
return source.Length == 0
? add
: $"{source}.{add}";
}
}
}
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
public class MongoExtensionsTests
{
[Fact]
public void Iterate_should_reach_all_leaf_nodes()
{
// Arrange
var xpto = Fixture.Create<XPTO>();
var expected =
JObject
.FromObject(xpto)
.Descendants()
.OfType<JValue>()
.Select(jv => jv.Path)
.ToHashSet();
// dont know why but JObject consider this get only property
expected.Remove(nameof(XPTO.IsFinished));
// Act
var values = new HashSet<string>();
order
.ToBsonDocument()
.Iterate((_, path) => values.Add(path.Replace("_id", "Id")));
// Assert
// BeEquivalentTo method dont tell us what is missing/leftover
// so we check it manually
values.Except(expected).Should().BeEmpty();
expected.Except(values).Should().BeEmpty();
}
[Fact]
public void Simple_prop()
{
// Arrange
var bson = new
{
A = 1,
B = 2,
C = 3,
}
.ToBsonDocument();
var expected = new
{
A = 1,
//B = 2,
C = 3,
}
.ToBsonDocument();
// Act
bson.RecursivelyRemove("B");
// Assert
bson.Should().BeEquivalentTo(expected);
}
[Fact]
public void Simple_Nested_leaf_prop()
{
// Arrange
var bson = new
{
A = 1,
B = new
{
D = new
{
E = 5
},
F = 6,
},
C = 3,
}
.ToBsonDocument();
var expected = new
{
A = 1,
B = new
{
D = new
{
//E = 5
},
F = 6,
},
C = 3,
}
.ToBsonDocument();
// Act
bson.RecursivelyRemove("B.D.E");
// Assert
bson.Should().BeEquivalentTo(expected);
}
[Fact]
public void Simple_Nested_non_leaf_prop()
{
// Arrange
var bson = new
{
A = 1,
B = new
{
D = new
{
E = 5
},
F = 6,
},
C = 3,
}
.ToBsonDocument();
var expected = new
{
A = 1,
B = new
{
//D = new
//{
// E = 5
//},
F = 6,
},
C = 3,
}
.ToBsonDocument();
// Act
bson.RecursivelyRemove("B.D");
// Assert
bson.Should().BeEquivalentTo(expected);
}
[Fact]
public void Simple_Nested_array_leaf_prop()
{
// Arrange
var bson = new
{
A = 1,
B = new
{
D = new[]
{
new { E = 5, F = 6, G = 7 },
new { E = 5, F = 6, G = 7 },
},
},
C = 3,
}
.ToBsonDocument();
var expected = new
{
A = 1,
B = new
{
D = new[]
{
new { E = 5, /*F = 6,*/ G = 7 },
new { E = 5, /*F = 6,*/ G = 7 },
},
},
C = 3,
}
.ToBsonDocument();
// Act
bson.RecursivelyRemove("B.D[].F");
// Assert
bson.Should().BeEquivalentTo(expected);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment