MongoDB C# Driver: flatten object - c#

Imagine I have a class structure like this
class Aggregate
{
string Id {get;set;}
Parameters Parameters {get;set;}
}
class Parameters
{
List<string> Values {get;set;}
// logic based on Values
}
Now I want mongo to store it like this
{
"_id": "...",
"parameters": ["param A", "param B"]
}
How do I configure it using BsonClassMap.RegisterClassMap?

I'm ignoring the fact that the members of your class are private at the moment, as that would mean you couldn't read anything from the class. So let's just make them public
class Aggregate
{
public string Id { get; set; }
public Parameters Parameters { get; set; }
}
class Parameters
{
public List<string> Values { get; set; }
}
So now we'll need a custom serializer to deal with flattering the object and then also reading back the flattened document.
Below is just a noddy example that delegate serializing Parameters to the BsonArraySerializer.
public class ParametersSerializer : IBsonSerializer
{
private readonly BsonArraySerializer _bsonArraySerializer;
public ParametersSerializer()
{
_bsonArraySerializer = new BsonArraySerializer();
}
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonArray = _bsonArraySerializer.Deserialize(context, args);
var netList = BsonTypeMapper.MapToDotNetValue(bsonArray, new BsonTypeMapperOptions()
{
MapBsonArrayTo = typeof(List<string>)
}) as List<string>;
return new Parameters
{
Values = netList
};
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
var typed = (Parameters) value;
var bsonArray = new BsonArray(typed.Values);
_bsonArraySerializer.Serialize(context, args, bsonArray);
}
public Type ValueType { get; } = typeof(Parameters);
}
Now we've got this we can just add an attribute to our class to tell Mongo driver to use it.
class Aggregate
{
public string Id { get; set; }
[BsonSerializer(typeof(ParametersSerializer))]
public Parameters Parameters { get; set; }
}
Now when we write and read documents to mongo:
var pack = new ConventionPack();
pack.Add(new CamelCaseElementNameConvention());
ConventionRegistry.Register(
"My Custom Conventions",
pack,
t => t == typeof(Aggregate) || t == typeof(Parameters));
var client = new MongoClient();
var database = client.GetDatabase("test");
var collection = database.GetCollection<Aggregate>("test");
await database.DropCollectionAsync(collection.CollectionNamespace.CollectionName);
await collection.InsertOneAsync(new Aggregate{Id = "...", Parameters = new Parameters{Values = {"1", "2"}}});
var all = await (await collection.FindAsync(Builders<Aggregate>.Filter.Empty)).ToListAsync();
we'll get the following save in MongoDB collection:
> db.test.find()
{ "_id" : "...", "parameters" : [ "1", "2" ] }
Also notice we're using a custom convention of camel case to deal with creating camel case properties too.

Related

MongoDB C# Driver GroupBy to get latest object of the same property. (With aggregation)

I have the following model of the collection "evaluations":
[BsonCollection("evaluations")]
public class Evaluation : Document
{
[BsonRepresentation(BsonType.ObjectId)]
public string AlertId { get; set; }
public string EvaluationStatus { get; set; }
public DateTime EvaluatedAt { get; set; }
}
What I would like to do is to retrieve a list with the last evaluation of each Alert object, which would mean grouping evaluations by AlertId then getting the latest EvaluatedAt of the group, and then return those evaluations.
With the result being a list of 'Evaluation' objects, (one evaluation per AlertId)
How would I construct that query using MongoDB driver with Aggregation?
Thanks for the help!
With a little research came up with this solution:
var alertIds = alertss.Select(x => x.Id.ToString()).ToList();
var sortByLatest = Builders<Evaluation>.Sort.Descending(x => x.EvaluatedAt);
var groupByAlertId = new BsonDocument
{
{ "_id", "$AlertId" },
{ "EvaluationId", new BsonDocument { { "$first", "$_id" } } },
{ "EvaluationStatus", new BsonDocument { { "$first", "$EvaluationStatus" } } },
{ "EvaluatedAt", new BsonDocument { { "$first", "$EvaluatedAt" } } },
};
ProjectionDefinition<BsonDocument> projection = new BsonDocument
{
{"_id", "$EvaluationId"},
{"AlertId", "$_id"},
{"EvaluationStatus", "$EvaluationStatus"},
{"EvaluatedAt", "$EvaluatedAt"},
};
var evaluations = evaluationsRepository.GetCollection().Aggregate()
.Match(x => alertIds.Contains(x.AlertId))
.Sort(sortByLatest)
.Group(groupByAlertId)
.Project(projection)
.As<Evaluation>().ToList();
return evaluations;

How to extract a list from appsettings.json in .net core

I have an appsettings.json file which looks like this:
{
"someSetting": {
"subSettings": [
"one",
"two",
"three"
]
}
}
When I build my configuration root, and do something like config["someSetting:subSettings"] it returns null and the actual settings available are something like this:
config["someSettings:subSettings:0"]
Is there a better way of retrieving the contents of someSettings:subSettings as a list?
Assuming your appsettings.json looks like this:
{
"foo": {
"bar": [
"1",
"2",
"3"
]
}
}
You can extract the list items like so:
Configuration.GetSection("foo:bar").Get<List<string>>()
In .NetCore this is what I did:
Normal Setup:
In your appsettings.json create a configuration section for your custom definitions:
"IDP": [
{
"Server": "asdfsd",
"Authority": "asdfasd",
"Audience": "asdfadf"
},
{
"Server": "aaaaaa",
"Authority": "aaaaaa",
"Audience": "aaaa"
}
]
Create a class to model the objects:
public class IDP
{
public String Server { get; set; }
public String Authority { get; set; }
public String Audience { get; set; }
}
in your Startup -> ConfigureServices
services.Configure<List<IDP>>(Configuration.GetSection("IDP"));
Note: if you need to immediately access your list within your ConfigureServices method
you can use...
var subSettings = Configuration.GetSection("IDP").Get<List<IDP>>();
Then in your controller something like this:
Public class AccountController: Controller
{
private readonly IOptions<List<IDP>> _IDPs;
public AccountController(IOptions<List<Defined>> IDPs)
{
_IDPs = IDPs;
}
...
}
just as an example I used it elsewhere in the above controller like this:
_IDPs.Value.ForEach(x => {
// do something with x
});
Edge Case
In the case that you need multiple configs but they can't be in an array and you have no idea how many sub-settings you will have at any one time. Use the following method.
appsettings.json
"IDP": {
"0": {
"Description": "idp01_test",
"IDPServer": "https://intapi.somedomain.com/testing/idp01/v1.0",
"IDPClient": "someapi",
"Format": "IDP"
},
"1": {
"Description": "idpb2c_test",
"IDPServer": "https://intapi.somedomain.com/testing/idpb2c",
"IDPClient": "api1",
"Format": "IDP"
},
"2": {
"Description": "MyApp",
"Instance": "https://sts.windows.net/",
"ClientId": "https://somedomain.com/12345678-5191-1111-bcdf-782d958de2b3",
"Domain": "somedomain.com",
"TenantId": "87654321-a10f-499f-9b5f-6de6ef439787",
"Format": "AzureAD"
}
}
Model
public class IDP
{
public String Description { get; set; }
public String IDPServer { get; set; }
public String IDPClient { get; set; }
public String Format { get; set; }
public String Instance { get; set; }
public String ClientId { get; set; }
public String Domain { get; set; }
public String TenantId { get; set; }
}
Create Extension for Expando Object
public static class ExpandObjectExtension
{
public static TObject ToObject<TObject>(this IDictionary<string, object> someSource, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public)
where TObject : class, new()
{
Contract.Requires(someSource != null);
TObject targetObject = new TObject();
Type targetObjectType = typeof(TObject);
// Go through all bound target object type properties...
foreach (PropertyInfo property in
targetObjectType.GetProperties(bindingFlags))
{
// ...and check that both the target type property name and its type matches
// its counterpart in the ExpandoObject
if (someSource.ContainsKey(property.Name)
&& property.PropertyType == someSource[property.Name].GetType())
{
property.SetValue(targetObject, someSource[property.Name]);
}
}
return targetObject;
}
}
ConfigureServices
var subSettings = Configuration.GetSection("IDP").Get<List<ExpandoObject>>();
var idx = 0;
foreach (var pair in subSettings)
{
IDP scheme = ((ExpandoObject)pair).ToObject<IDP>();
if (scheme.Format == "AzureAD")
{
// this is why I couldn't use an array, AddProtecedWebApi requires a path to a config section
var section = $"IDP:{idx.ToString()}";
services.AddProtectedWebApi(Configuration, section, scheme.Description);
// ... do more stuff
}
idx++;
}
You can use the Configuration binder to get a strong type representation of the configuration sources.
This is an example from a test that I wrote before, hope it helps:
[Fact]
public void BindList()
{
var input = new Dictionary<string, string>
{
{"StringList:0", "val0"},
{"StringList:1", "val1"},
{"StringList:2", "val2"},
{"StringList:x", "valx"}
};
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddInMemoryCollection(input);
var config = configurationBuilder.Build();
var list = new List<string>();
config.GetSection("StringList").Bind(list);
Assert.Equal(4, list.Count);
Assert.Equal("val0", list[0]);
Assert.Equal("val1", list[1]);
Assert.Equal("val2", list[2]);
Assert.Equal("valx", list[3]);
}
The important part is the call to Bind.
The test and more examples are on GitHub
var settingsSection = config.GetSection["someSettings:subSettings"];
var subSettings = new List<string>;
foreach (var section in settingsSection.GetChildren())
{
subSettings.Add(section.Value);
}
This should give you the values you need, stored in subSettings
Apologies for bringing up a semi-old thread. I had difficulty finding an answer as a good amount of methods are deprecated, like Get and GetValue. This should be fine if you only need a simple solution without the configuration binder. :)
In my case configuration
services.Configure<List<ApiKey>>(Configuration.GetSection("ApiKeysList"));
wasn't loaded because the properties were read-only and there were no default constructor
//Not working
public class ApiKey : IApiKey
{
public ApiKey(string key, string owner)
{
Key = key;
OwnerName = owner;
}
public string Key { get; }
public string OwnerName { get;}
}
//Working
public class ApiKey : IApiKey
{
public ApiKey(){}//Added default constructor
public ApiKey(string key, string owner)
{
Key = key;
OwnerName = owner;
}
public string Key { get; set; } //Added set property
public string OwnerName { get; set; } //Added set property
}
Just getting the whole section will populate the List property; in a settings class.
services.Configure<Settings>(configuration.GetSection("Another:Some:Example"));
But... do remember that if defaults are set in the settings class for that List... that the configuration settings will be additive and so not overwriting the original values.
So these defaults will remain and so are really "no way you can delete them via any additional configuration"
public List<string> NonEditableStuff { get; set; } = new() { "XYZ1", "LTOY3" };
Also, if you also have turned on the Ini file provider might be be handy to know that to specify the list there the keys do not really matter as long as they are unique, so it makes sense to keep the key and the values there the same to end up in the list.
[Another:Some:Example:NonEditableStuff]
value=value
whatever2=whatever2

How do deserialize JSON with non-standard (and varying) property names (in .NET)

I have to read a JSON stream (which I have no control over), which is in the form:
{"files":
{
"/some_file_path.ext": {"size":"1000", "data":"xxx", "data2":"yyy"},
"/other_file_path.ext": {"size":"2000", "data":"xxx", "data2":"yyy"},
"/another_file_path.ext": {"size":"3000", "data":"xxx", "data2":"yyy"},
}
}
So, I have an object named files, which has a number of properties, which have 1) different names every time, 2) different number of them every time, and 3) names with characters which can't be used in C# properties.
How do I deserialize this?
I'm putting this into a Portable Library, so I can't use the JavaScriptSerializer, in System.Web.Script.Serialization, and I'm not sure about JSON.NET. I was hoping to use the standard DataContractJsonSerializer.
UPDATE: I've changed the sample data to be closer to the actual data, and corrected the JSON syntax in the area the wasn't important. (Still simplified quite a bit, but the other parts are fairly standard)
You can model your "files" object as a Dictionary keyed by the JSON property name:
public class RootObject
{
public Dictionary<string, PathData> files { get; set; }
}
public class PathData
{
public int size { get; set; }
public string data { get; set; }
public string data2 { get; set; }
}
Then, only if you are using .Net 4.5 or later, you can deserialize using DataContractJsonSerializer, but you must first set DataContractJsonSerializerSettings.UseSimpleDictionaryFormat = true:
var settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true };
var root = DataContractJsonSerializerHelper.GetObject<RootObject>(jsonString, settings);
With the helper method:
public static class DataContractJsonSerializerHelper
{
public static T GetObject<T>(string json, DataContractJsonSerializer serializer = null)
{
using (var stream = GenerateStreamFromString(json))
{
var obj = (serializer ?? new DataContractJsonSerializer(typeof(T))).ReadObject(stream);
return (T)obj;
}
}
public static T GetObject<T>(string json, DataContractJsonSerializerSettings settings)
{
return GetObject<T>(json, new DataContractJsonSerializer(typeof(T), settings));
}
private static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
}
}
Alternatively, you can install Json.NET and do:
var root = JsonConvert.DeserializeObject<RootObject>(jsonString);
Json.NET automatically serializes dictionaries to JSON objects without needing to change settings.
We need to first convert this Invalid JSON to a Valid JSON. So a Valid JSON should look like this
{
"files":
{
"FilePath" : "C:\\some\\file\\path",
"FileData" : {
"size": 1000,
"data": "xxx",
"data2": "yyy"
},
"FilePath" :"C:\\other\\file\\path",
"FileData" : {
"size": 2000,
"data": "xxx",
"data2": "yyy"
},
"FilePath" :"C:\\another\\file\\path",
"FileData" : {
"size": 3000,
"data": "xxx",
"data2": "yyy"
}
}
}
To make it a valid JSON we might use some string functions to make it looks like above. Such as
MyJSON = MyJSON.Replace("\\", "\\\\");
MyJSON = MyJSON.Replace("files", "\"files\"");
MyJSON = MyJSON.Replace("data:", "\"data:\"");
MyJSON = MyJSON.Replace("data2", "\"data2\"");
MyJSON = MyJSON.Replace(": {size", ",\"FileData\" : {\"size\"");
MyJSON = MyJSON.Replace("C:", "\"FilePath\" :\"C:");
Than we can create a class like below to read the
public class FileData
{
public int size { get; set; }
public string data { get; set; }
public string data2 { get; set; }
}
public class Files
{
public string FilePath { get; set; }
public FileData FileData { get; set; }
}
public class RootObject
{
public Files files { get; set; }
}
Assuming you have a valid JSON you could use JavaScriptSerializer to return a list of objects
string json = "{}"
var serializer = new JavaScriptSerializer();
var deserializedValues = (Dictionary<string, object>)serializer.Deserialize(json, typeof(object));
Alternatively you could specify Dictionary<string, List<string>> as the type argument
strign json = "{}";
JavaScriptSerializer serializer = new JavaScriptSerializer();
var deserializedValues = serializer.Deserialize<Dictionary<string, List<string>>>(json);
foreach (KeyValuePair<string, List<string>> kvp in deserializedValues)
{
Console.WriteLine(kvp.Key + ": " + string.Join(",", kvp.Value));
}

Noob using C# classes and accessing them

I'm working with JSON (using json.net) and a C# console application and I am trying to set some values for a JSON POST to a server.
I can set some of the values, but accessing others is giving me fits.
My JSON looks like this:
{
"params" : [
{
"url" : "sys/login/user",
"data" : [
{
"passwd" : "pwd",
"user" : "user"
}
]
}
],
"session" : 1,
"id" : 1,
"method" : "exec"
}
I ran that through json2csharp and it generated me the following classes.
public class Datum
{
public string passwd { get; set; }
public string user { get; set; }
}
public class Param
{
public string url { get; set; }
public List<Datum> data { get; set; }
}
public class RootObject
{
public List<Param> #params { get; set; }
public string session { get; set; }
public int id { get; set; }
public string method { get; set; }
}
I then created this object for testing in my Main method
RootObject temp = new RootObject()
temp.id = 1;
temp.method = "exec";
temp.session = "1";
and those parameters get set just fine.
I can also set the URL param using the following:
temp.#params.Add(new Param { url = "some/url", });
It is setting the public List<Datum> data { get; set; } item that is the problem. I cannot figure out how to access that and set the user and password items.
If I add this to the Param class I can set the values, but this seems to be the wrong way/place to me.
public Param()
{
data = new List<Datum>();
data.Add(new Datum { user = "user", passwd = "pass" });
}
Well, you create your RootObject like this:
RootObject temp = new RootObject()
temp.id = 1;
temp.method = "exec";
temp.session = "1";
Then you create the params list and fill it with one Param:
temp.#params = new List<Param>();
temp.#params.Add(new Param { url = "some/url" });
You can then set the data for one param in the list (in this example the first one):
temp.#params[0].data = new List<Datum>();
temp.#params[0].data.Add(new Datum { user = "user", passwd = "pass" });
This is necessary, because #params is a list of Param objects. You could also fill the data when creating the Param instance before adding it to the list (easier, because you otherwise need to know the list index).
temp.#params = new List<Param>();
Param p = new Param { url = "some/url" };
p.data = new List<Datum>();
p.data.Add(new Datum() { ... });
temp.#params.Add(p);
Usually you'd change change the default constructors to initialize the lists already and prevent the list instances from being replaced by changing the properties to read-only, but that might not work well with JSON deserialization, so you really need to try this. It would look like this:
public class Param
{
public Param()
{
data = new List<Datum>();
}
public string url { get; set; }
public List<Datum> data { get; private set; }
}
public class RootObject
{
public RootObject()
{
#params = new List<Param>();
}
public List<Param> #params { get; private set; }
public string session { get; set; }
public int id { get; set; }
public string method { get; set; }
}

How to remove DataContractSerializer verbosity

We are attempting to serialize an object tree. And while we have been successful. I was hoping to find a way to simplify down the generated xml.
The objects look something like the following:
public class RuleSet<T>
{
public IEnumerable<IState<T>> States { get; set; }
public IEnumerable<ICondition<T>> Conditions { get; set; }
}
public class State<T> : IState<T>
{
public string Id { get; set; }
public List<ITransition<T>> Transitions { get; set; }
}
public class Transition<T> : ITransition<T>
{
public ICondition<T> Condition { get; set; }
public IState<T> Next { get; set; }
}
public class Condition<T> : ICondition<T>
{
public string Id { get; set; }
public string Name { get; set; }
}
We are using a really simple serialization code at the moment:
public void blah()
{
var condition1 = new Condition<object>() {
Id = "C1", AttributeName = "Foo", ExpectedValue = "Bar"
};
var condition2 = new Condition<object>() {
Id = "C2", AttributeName = "Bar", ExpectedValue = "Foo"
};
var state1Transitions = new List<ITransition<object>>();
var state2Transitions = new List<ITransition<object>>();
var state3Transitions = new List<ITransition<object>>();
var state = new State<object> {
Id = "S1", Transitions = state1Transitions
};
var state2 = new State<object> {
Id = "S2", Transitions = state2Transitions
};
var state3 = new State<object> {
Id = "S3", Transitions = state3Transitions
};
state1Transitions.Add(new Transition<object> {
Condition = condition1, Next = state2
});
state1Transitions.Add(new Transition<object> {
Condition = condition2, Next = state3
});
state2Transitions.Add(new Transition<object> {
Condition = condition2, Next = state3
});
var ruleSet = new RuleSet<object> {
States = new List<IState<object>> {state, state2, state3},
Conditions = new List<ICondition<object>>{condition1, condition2}
};
var stream1 = new MemoryStream();
var serializer = new DataContractSerializer(typeof(RuleSet<object>),
new List<Type> {
typeof(State<object>),
typeof(Transition<object>),
typeof(AttributeEqualTo<object>)
});
serializer.WriteObject(stream1, ruleSet);
stream1.Position = 0;
var xml = new StreamReader(stream1).ReadToEnd();
Console.WriteLine(xml);
}
When the XML is generated the output for each level is complete instead of only containing the reference to the object. Basically for each Transition<T> we get a complete object definition for each state and condition even if they are defined elsewhere.
Is there a way to get those to simply be references?
It all comes down to how you create your DataContractSerializer.
You'll want to call the overload of the constructor that allows you to indicate that you will preserve object references, with the following signature:
public DataContractSerializer(
Type type,
IEnumerable<Type> knownTypes,
int maxItemsInObjectGraph,
bool ignoreExtensionDataObject,
bool preserveObjectReferences,
IDataContractSurrogate dataContractSurrogate
)
You can pass default values for most of the parameters. In your case, the call to the DataContractSerializer constructor will look like this:
var serializer = new DataContractSerializer(typeof(RuleSet<object>),
new [] {
typeof(State<object>),
typeof(Transition<object>),
typeof(AttributeEqualTo<object>)
},
Int32.MaxValue,
false,
/* This is the important flag to set. */
true,
null
);
Note, from the preserveObjectReferences parameter documentation, it uses non-standard XML (emphasis mine):
preserveObjectReferences
Type: System.Boolean
true to use non-standard XML constructs to preserve object reference data; otherwise, false.
If you need other code outside of .NET to interpret this, then untangling the references might prove to be difficult (but shouldn't be impossible).
However, it prevents the object graph from replicating itself over and reduce the size of your XML (possibly considerably, given how deep your references go).

Categories

Resources