How to disable discriminator field in MongoDb (C# driver) - c#

Is there a way to disable completely (for all classes) the discriminator ("_t") fields from being added to the bson documents?
I am referring to: mongo-csharp-driver/polymorphism

Let's say we have a Square and Rectangle that inherit from Shape.
public abstract class Shape
{
public ObjectId Id { get; set; }
}
public sealed class Square : Shape
{
public int Size { get; set; }
}
public sealed class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
}
Like you said if we run the following code.
var client = new MongoClient();
var db = client.GetDatabase("test");
var shapes = db.GetCollection<Shape>("shapes");
await shapes.InsertManyAsync(new Shape[]
{
new Square{Size = 10},
new Rectangle{Height = 5, Width = 4}
});
We'll get the following inserted into MongoDB
db.shapes.find()
{ "_id" : ObjectId("5f4e2affc23dde5a501bdf0b"), "_t" : "Square", "Size" : 10 }
{ "_id" : ObjectId("5f4e2affc23dde5a501bdf0c"), "_t" : "Rectangle", "Width" : 4, "Height" : 5 }
Initially, I thought we'd be able to set the DiscriminatorIsRequired flag on the BsonClassMap and wrap that in a convention, however, from trying this it seems to fail due to the following bit of logic in the MongoDB C# Driver.
private bool ShouldSerializeDiscriminator(Type nominalType)
{
return (nominalType != _classMap.ClassType || _classMap.DiscriminatorIsRequired || _classMap.HasRootClass) && !_classMap.IsAnonymous;
}
https://github.com/mongodb/mongo-csharp-driver/blob/9e567e23615c8bb5c7ac1489427c2d15b2124522/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs#L722
So because there's no way for us to tell the serializer we don't want to include a discriminator we'll have to give it a convention instead that does nothing.
If we create an IDiscriminatorConvention that pretty much does nothing and returns back null for the discriminator then the driver won't add this to the document.
public class NullDiscriminatorConvention : IDiscriminatorConvention
{
public static NullDiscriminatorConvention Instance { get; }
= new NullDiscriminatorConvention();
public Type GetActualType(IBsonReader bsonReader, Type nominalType)
=> nominalType;
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
=> null;
public string ElementName { get; } = null;
}
This discriminator convention then needs to be registered against each type.
BsonSerializer.RegisterDiscriminatorConvention(typeof(Square), NullDiscriminatorConvention.Instance);
BsonSerializer.RegisterDiscriminatorConvention(typeof(Rectangle), NullDiscriminatorConvention.Instance);
Alternately if we want it on all types you could do a little bit of reflection.
var shapeTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes(),
(domainAssembly, assemblyType) => new {domainAssembly, assemblyType})
.Where(t => #t.assemblyType.IsSubclassOf(typeof(Shape)))
.Select(t => #t.assemblyType).ToArray();
foreach (var shapeType in shapeTypes)
{
BsonSerializer.RegisterDiscriminatorConvention(shapeType, NullDiscriminatorConvention.Instance);
}
Now if we re-run our code.
var shapeTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes(),
(domainAssembly, assemblyType) => new {domainAssembly, assemblyType})
.Where(t => #t.assemblyType.IsSubclassOf(typeof(Shape)))
.Select(t => #t.assemblyType).ToArray();
foreach (var shapeType in shapeTypes)
{
BsonSerializer.RegisterDiscriminatorConvention(shapeType, NullDiscriminatorConvention.Instance);
}
var client = new MongoClient();
var db = client.GetDatabase("test");
var shapes = db.GetCollection<Shape>("shapes");
await shapes.InsertManyAsync(new Shape[]
{
new Square{Size = 10},
new Rectangle{Height = 5, Width = 4}
});
we'll get our expected output.
db.shapes.find()
{ "_id" : ObjectId("5f4e2d63ed12d7c5d3638d36"), "Size" : 10 }
{ "_id" : ObjectId("5f4e2d63ed12d7c5d3638d37"), "Width" : 4, "Height" : 5 }

One option is using Newtonsoft bson serializer (Newtonsoft.Json.Bson) which gives a lot of serialization options.
It isn't efficient since you need to write the bson to a stream and then read it from there with MongoDb reader but it provides lots of customization option.
Example code:
class BsonDocBuilder
{
private readonly MemoryStream _memStream = new MemoryStream();
private readonly JsonSerializerSettings _serializeSettings = new JsonSerializerSettings();
private readonly JsonSerializer _jsonSerializer;
public BsonDocBuilder()
{
_jsonSerializer = JsonSerializer.Create(_serializeSettings);
}
public BsonDocument ToBson<T>(T value)
{
BsonDocument bd;
try
{
using (BsonDataWriter dataWriter = new BsonDataWriter(_memStream))
{
dataWriter.CloseOutput = false;
_jsonSerializer.Serialize(dataWriter, value);
}
bd= BsonSerializer.Deserialize<BsonDocument>(_memStream.ToArray());
}
finally
{
_memStream.SetLength(0);
}
return bd;
}
}

Related

New virtual creates a new method instead of hiding

The requirement here is to write test cases for the Opc Ua NodeManager and it uses NodeId from Opc.Ua class.
Methods/properties in NodeId class cannot be moq because they are Non-Overridable methods and have ony get in them.
So I created a wrapper on top of NodeId class and tried to Moq that class. It works fine but now I have 2 methods/Properties
public class NodeIdTestClass : NodeId
{
public NodeIdTestClass()
{
}
public new virtual object Identifier
{
get => base.Identifier;
}
public new virtual ushort NamespaceIndex
{
get => base.NamespaceIndex;
}
public new virtual bool IsNullNodeId
{
get => base.IsNullNodeId;
}
}
//Arrange
var nodeIdMock = new Mock<NodeIdTestClass>() { CallBase = true };
nodeIdMock.Setup(x => x.Identifier).Returns(nodeIdMock.Object.Identifier);
nodeIdMock.Setup(x => x.NamespaceIndex).Returns(1);
nodeIdMock.Setup(x => x.IsNullNodeId).Returns(false);
_nodemanager.SetNamespaces(new string[] { "0", "1", "2" });
//Act
var result = _nodemanager.GetManagerHandle(nodeIdMock.Object);
The problem :
Is there something wrong with code?
Do you need a mock?
Wouldn't this be enough?
var nodeId = new NodeId(value: 17, namespaceIndex: 1);
var result = _nodemanager.GetManagerHandle(nodeId);

How to use (pack) Google.Protobuf.WellknownTypes.Any in protobuf-net code first gRPC

I am creating an gRPC service and we decided to choose the code first approach with protobuf-net.
Now I am running into a scenario where we have a couple of classes that need to be wrapped.
We do not want to define KnownTypes in the MyMessage class (just a sample name to illustrate the problem).
So I am trying to use the Any type which currently gives me some struggle with packing.
The sample code has the MyMessage which defines some header values and has to possiblity to deliver any type as payload.
[ProtoContract]
public class MyMessage
{
[ProtoMember(1)] public int HeaderValue1 { get; set; }
[ProtoMember(2)] public string HeaderValue2 { get; set; }
[ProtoMember(3)] public Google.Protobuf.WellknownTypes.Any Payload { get; set; }
}
[ProtoContract]
public class Payload1
{
[ProtoMember(1)] public bool Data1 { get; set; }
[ProtoMember(2)] public string Data2 { get; set; }
}
[ProtoContract]
public class Payload2
{
[ProtoMember(1)] public string Data1 { get; set; }
[ProtoMember(2)] public string Data2 { get; set; }
}
Somewhere in the code I construct my message with a payload ...
Payload2 payload = new Payload2 {
Data1 = "abc",
Data2 = "def"
};
MyMessage msg = new MyMessage
{
HeaderValue1 = 123,
HeaderValue2 = "iAmHeaderValue2",
Payload = Google.Protobuf.WellknownTypes.Any.Pack(payload)
};
Which doesn't work because Payload1 and Payload2 need to implement Google.Protobuf.IMessage.
Since I can't figure out how and do not find a lot information how to do it at all I am wondering if I am going a wrong path.
How is it intedend to use Any in protobuf-net?
Is there a simple (yet compatible) way to pack a C# code first class into Google.Protobuf.WellknownTypes.Any?
Do I really need to implement Google.Protobuf.IMessage?
Firstly, since you say "where we have a couple of classes that need to be wrapped" (emphasis mine), I wonder if what you actually want here is oneof rather than Any. protobuf-net has support for the oneof concept, although it isn't obvious from a code-first perspective. But imagine we had (in a contract-first sense):
syntax = "proto3";
message SomeType {
oneof Content {
Foo foo = 1;
Bar bar = 2;
Blap blap = 3;
}
}
message Foo {}
message Bar {}
message Blap {}
This would be implemented (via the protobuf-net schema tools) as:
private global::ProtoBuf.DiscriminatedUnionObject __pbn__Content;
[global::ProtoBuf.ProtoMember(1, Name = #"foo")]
public Foo Foo
{
get => __pbn__Content.Is(1) ? ((Foo)__pbn__Content.Object) : default;
set => __pbn__Content = new global::ProtoBuf.DiscriminatedUnionObject(1, value);
}
public bool ShouldSerializeFoo() => __pbn__Content.Is(1);
public void ResetFoo() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__Content, 1);
[global::ProtoBuf.ProtoMember(2, Name = #"bar")]
public Bar Bar
{
get => __pbn__Content.Is(2) ? ((Bar)__pbn__Content.Object) : default;
set => __pbn__Content = new global::ProtoBuf.DiscriminatedUnionObject(2, value);
}
public bool ShouldSerializeBar() => __pbn__Content.Is(2);
public void ResetBar() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__Content, 2);
[global::ProtoBuf.ProtoMember(3, Name = #"blap")]
public Blap Blap
{
get => __pbn__Content.Is(3) ? ((Blap)__pbn__Content.Object) : default;
set => __pbn__Content = new global::ProtoBuf.DiscriminatedUnionObject(3, value);
}
public bool ShouldSerializeBlap() => __pbn__Content.Is(3);
public void ResetBlap() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__Content, 3);
optionally with an enum to help:
public ContentOneofCase ContentCase => (ContentOneofCase)__pbn__Content.Discriminator;
public enum ContentOneofCase
{
None = 0,
Foo = 1,
Bar = 2,
Blap = 3,
}
This approach may be easier and preferable to Any.
On Any:
Short version: protobuf-net has not, to date, had any particular request to implement Any. It probably isn't a huge amount of work - simply: it hasn't yet happened. It looks like you're referencing both protobuf-net and the Google libs here, and using the Google implementation of Any. That's fine, but protobuf-net isn't going to use it at all - it doesn't know about the Google APIs in this context, so: implementing IMessage won't actually help you.
I'd be more than happy to look at Any with you, from the protobuf-net side. Ultimately, time/availability is always the limiting factor, so I prioritise features that are seeing demand. I think you may actually be the first person asking me about Any in protobuf-net.
My Any implementation.
[ProtoContract(Name = "type.googleapis.com/google.protobuf.Any")]
public class Any
{
/// <summary>Pack <paramref name="value"/></summary>
public static Any Pack(object? value)
{
// Handle null
if (value == null) return new Any { TypeUrl = null!, Value = Array.Empty<byte>() };
// Get type
System.Type type = value.GetType();
// Write here
MemoryStream ms = new MemoryStream();
// Serialize
RuntimeTypeModel.Default.Serialize(ms, value);
// Create any
Any any = new Any
{
TypeUrl = $"{type.Assembly.GetName().Name}/{type.FullName}",
Value = ms.ToArray()
};
// Return
return any;
}
/// <summary>Unpack any record</summary>
public object? Unpack()
{
// Handle null
if (TypeUrl == null || Value == null || Value.Length == 0) return null;
// Find '/'
int slashIx = TypeUrl.IndexOf('/');
// Convert to C# type name
string typename = slashIx >= 0 ? $"{TypeUrl.Substring(slashIx + 1)}, {TypeUrl.Substring(0, slashIx)}" : TypeUrl;
// Get type (Note security issue here!)
System.Type type = System.Type.GetType(typename, true)!;
// Deserialize
object value = RuntimeTypeModel.Default.Deserialize(type, Value.AsMemory());
// Return
return value;
}
/// <summary>Test type</summary>
public bool Is(System.Type type) => $"{type.Assembly.GetName().Name}/{type.FullName}" == TypeUrl;
/// <summary>Type url (using C# type names)</summary>
[ProtoMember(1)]
public string TypeUrl = null!;
/// <summary>Data serialization</summary>
[ProtoMember(2)]
public byte[] Value = null!;
/// <summary></summary>
public static implicit operator Container(Any value) => new Container(value.Unpack()! );
/// <summary></summary>
public static implicit operator Any(Container value) => Any.Pack(value.Value);
/// <summary></summary>
public struct Container
{
/// <summary></summary>
public object? Value;
/// <summary></summary>
public Container()
{
this.Value = null;
}
/// <summary></summary>
public Container(object? value)
{
this.Value = value;
}
}
}
'System.Object' can be used as a field or property in a surrounding Container record:
[DataContract]
public class Container
{
/// <summary></summary>
[DataMember(Order = 1, Name = nameof(Value))]
public Any.Container Any { get => new Any.Container(Value); set => Value = value.Value; }
/// <summary>Object</summary>
public object? Value;
}
Serialization
RuntimeTypeModel.Default.Add(typeof(Any.Container), false).SetSurrogate(typeof(Any));
var ms = new MemoryStream();
RuntimeTypeModel.Default.Serialize(ms, new Container { Value = "Hello world" });
Container dummy = RuntimeTypeModel.Default.Deserialize(typeof(Container), ms.ToArray().AsMemory()) as Container;

Two classes but same object

I have 2 tables, tblA and tblB, with same fields and types. I get datas through linq to sql so I have 2 partial classes clsTblA and clsTblB.
I have a combo to choose tblA or tblB and I have to read in that table and do some query.
What I'am trying to do is evitate to duplicate code to run the same methods.
So now I have (in pseudo-code):
if (combo == "A")
{
List<clsTblA> listUserNow = ctx.clsTblA.Where(p => p.blabla).ToList();
List<clsTblA> listUserLastYear = ctx.clsTblA.Where(q => q.blabla).ToList();
}
if (combo == "B")
{
List<clsTblB> listUserNow = ctx.clsTblB.Where(p => p.blabla).ToList();
List<clsTblB> listUserLastYear = ctx.clsTblB.Where(q => q.blabla).ToList();
}
But I have in mind something like this (in pseudo-code):
SupClsTable clsTblX = null;
if (combo == A)
clsTblX = new clsTblA();
if (combo == B)
clsTblX = new clsTblB();
List<clsTblX> listUserNow = tblX.QueryForNow();
List<clsTblX> listUserLastYear = tblX.QueryForLastYear();
Does it exist something like this?
I also searched in design pattern but without results.
Thanks in advance.
EDIT 1:
At this moment the code is like:
if (combo == A)
{
using (DbDataContext ctx = new DbDataContext())
{
List<clsTblA> listUserNow = ctx.clsTblA.Where(p => p.blabla).ToList();
List<clsTblA> listUserLastYear = ctx.clsTblA.Where(q => q.blabla).ToList();
}
}
if (combo == B)
{
using (DbDataContext ctx = new DbDataContext())
{
List<clsTblB> listUserNow = ctx.clsTblB.Where(p => p.blabla).ToList();
List<clsTblB> listUserLastYear = ctx.clsTblB.Where(q => q.blabla).ToList();
}
}
so I have twice listUserNow and listUserLastYear.
How can I let me return a unique
using (DbDataContext ctx = new DbDataContext())
{
List<*something*> listUserNow = ctx.*something*.Where(p => p.blabla).ToList();
List<*something*> listUserLastYear = ctx.*something*.Where(p => p.blabla).ToList();
}
indipendent from "if combo"?
Thanks in advence
It seems like what you're looking for are Interfaces. i.e.
interface ISampleInterface
{
void SampleMethod();
}
class ImplementationClass : ISampleInterface
{
// Explicit interface member implementation:
void ISampleInterface.SampleMethod()
{
// Method implementation.
}
}
You can learn more about that on the Microsoft documentation site Microsoft Docs
EDIT: To clearify. Once both Classes inerhit from the Interface you could write the following
if (combo == A)
clsTblX = new clsTblA();
if (combo == B)
clsTblX = new clsTblB();
as follows
IClsTable clsTbl = (combo == A)? new ClsTblA() : new ClsTblB() // example
and work with the clsTbl like you would've previously with ClsTblA or ClsTblB since both follow the same structure and have same properties and methods.
Maybe this dotnetfiddle example helps you understand the concept of this solution.
While personally I think interfacing is better option for your use case, you could also implement something like this that will make it easy for you to extend. Same can be casted against interface as well. So every time a new combo is added just add a case and it extends easily.
public class ComboFactory
{
public static SuperCombo GetComboClassInstance(string comboCode)
{
switch(comboCode)
{
case "A":
return new ComboA();
case "B":
return new ComboB();
//and so on
}
}
}
Try to see if this is what you need:
public interface iTbl
{
int Property1 { get; set; }
string Property2 { get; set; }
void WhatAreYou();
}
public class clsTblA : iTbl
{
public int Property1 { get; set; }
public string Property2 { get; set; }
public void WhatAreYou()
{
Console.WriteLine("I am a clsTblA!");
}
}
public class clsTblB : iTbl
{
public int Property1 { get; set; }
public string Property2 { get; set; }
public void WhatAreYou()
{
Console.WriteLine("I am a clsTblB!");
}
}
public class Program
{
public static void Main()
{
List<iTbl> tbls = new List<iTbl>()
{
new clsTblA(),
new clsTblB(),
new clsTblB(),
new clsTblA(),
new clsTblA()
};
foreach (var tbl in tbls)
{
tbl.WhatAreYou();
}
}
}
Output:
I am a clsTblA!
I am a clsTblB!
I am a clsTblB!
I am a clsTblA!
I am a clsTblA!

Nancy Model Binding to subclass. Autodetection?

I'm wondering if I can get Nancy to auto bind a request to a subclass of the type I specify in the type parameters. Here's an example of what I'm trying to achieve:
Given these classes:
public class Shape
{
public int Height { get; set; }
public int Width { get; set; }
}
public class Triangle : Shape
{
public string SomeTriangleOnlyProp { get; set; }
}
public class Square : Shape
{
public string SomeSquareOnlyProp { get; set; }
}
Then given this Json:
{
"Height" : 10,
"Width" : 20
}
Then this is my desired result:
var shape = this.Bind<Shape>(); //Returns a Shape object
Given this Json:
{
"Height" : 10,
"Width" : 20,
"SomeTriangleOnlyProp" : "Triangle"
}
Then this is my desired result:
var shape = this.Bind<Shape>(); //Returns a Triangle object
Given this Json:
{
"Height" : 10,
"Width" : 20,
"SomeSquareOnlyProp" : "Square"
}
Then this is my desired result:
var shape = this.Bind<Shape>(); //Returns a Square object
Could I only achieve this with a custom binding class? I could try to bind to each type separately and handle any errors but that seems really unoptimal.
I've found a solution, I'm not sure if it's the best one but it's working how I need it so it's what I'll go with for now. I've written a custom model binder that looks at the json to try and determine what object it should serialize to:
public class ShapeBinder : IModelBinder
{
public object Bind(NancyContext context, Type modelType, object instance, BindingConfig configuration, params string[] blackList)
{
using (var sr = new StreamReader(context.Request.Body))
{
var json = sr.ReadToEnd();
if (json.Contains("SomeTriangleOnlyProp"))
{
var triangle = new Nancy.Json.JavaScriptSerializer().Deserialize<Triangle>(json);
return triangle;
}
else if (json.Contains("SomeSquareOnlyProp"))
{
var square = new Nancy.Json.JavaScriptSerializer().Deserialize<Square>(json);
return square;
}
else
{
var shape = new Nancy.Json.JavaScriptSerializer().Deserialize<Shape>(json);
return shape;
}
}
}
public bool CanBind(Type modelType)
{
return modelType == typeof(Shape);
}
}
Calling this in the NancyModule with var shape = this.Bind<Shape>(); returns a Triangle if the JSON had the triangle prop, a Square if the JSON had the square prop, or just the regular base Shape class if the JSON had neither.

MongoDB C# 2.4 - Find distinct values of nested array

I have documents that look like below:
{
"_id" : ObjectId("58148f4337b1fc09b8c2de9k"),
"Price" : 69.99,
"Attributes" : [
{
"Name" : "Color",
"Value" : "Grey",
},
{
"Name" : "Gender",
"Value" : "Mens",
}
]
}
I am looking to get a distinct list of Attributes.Name (so if I just had the one document as above, I would get 'Color' and 'Gender' returned).
I was able to easily get what I needed through mongo shell (db.getCollection('myCollection').distinct('Attributes.Name'), but I'm really struggling with the C# driver (version 2.4). Can someone please help me translate the shell command to C#?
I tried something like below (and many variations). I'm new to the Mongo C# driver and am just feeling a bit lost. Any help would be appreciated.
var database = client.GetDatabase("mymongodb");
IMongoCollection<BsonDocument> collection = database.GetCollection<BsonDocument>("mycollection");
var filter = new BsonDocument();
var distinctAttributeNames = collection.Distinct<BsonDocument>("Attributes.Name", filter);
var tryAgain = collection.Distinct<BsonDocument>("{Attributes.Name}", filter);
There you go:
public class Foo
{
public ObjectId Id;
public double Price = 69.99;
public Attribute[] Attributes = {
new Attribute { Name = "Color", Value = "Grey" },
new Attribute { Name = "Gender", Value = "Men" }
};
}
public class Attribute
{
public string Name;
public string Value;
}
public class Program
{
static void Main(string[] args)
{
MongoClient client = new MongoClient();
var collection = client.GetDatabase("test").GetCollection<Foo>("test");
collection.InsertOne(new Foo());
var distinctItems = collection.Distinct(new StringFieldDefinition<Foo, string>("Attributes.Name"), FilterDefinition<Foo>.Empty).ToList();
foreach (var distinctItem in distinctItems)
{
Console.WriteLine(distinctItem);
// prints:
// Color
// Gender
}
Console.ReadLine();
}
}

Categories

Resources