MongoDb c# driver consecutive SelectMany - c#

If I have objects, lets call them Group that has list of some other objects I will call it Brand, and this object has a list of objects called Model.
Is there a way to get only list of Models using MongoDb c# driver.
I tried using SelectMany multiple times but with no success. If I put more than one SelectMany I always get an empty list.
Code should be self-explanatory.
At the end is comment that explains what confuses me.
class Group
{
[BsonId(IdGenerator = typeof(GuidGenerator))]
public Guid ID { get; set; }
public string Name { get; set; }
public List<Brand> Brands { get; set; }
}
class Brand
{
public string Name { get; set; }
public List<Model> Models { get; set; }
}
class Model
{
public string Name { get; set; }
public int Produced { get; set; }
}
class Program
{
static void Main(string[] args)
{
MongoClient client = new MongoClient("mongodb://127.0.0.1:32768");
var db = client.GetDatabase("test");
var collection = db.GetCollection<Group>("groups");
var fca = new Group { Name = "FCA" };
var alfaRomeo = new Brand { Name = "Alfra Romeo" };
var jeep = new Brand { Name = "Jeep" };
var ferrari = new Brand { Name = "Ferrari"};
var laFerrari = new Model { Name = "LaFerrari", Produced = 4 };
var wrangler = new Model { Name = "Wrangler", Produced = 3 };
var compass = new Model { Name = "Compass", Produced = 8 };
var giulietta = new Model { Name = "Giulietta", Produced = 7 };
var giulia = new Model { Name = "Giulia", Produced = 8 };
var _4c = new Model { Name = "4C", Produced = 6 };
fca.Brands = new List<Brand> { ferrari, alfaRomeo, jeep };
ferrari.Models = new List<Model> { laFerrari };
jeep.Models = new List<Model> { wrangler, compass };
alfaRomeo.Models = new List<Model> { giulietta, giulia, _4c };
collection.InsertOne(fca);
Console.WriteLine("press enter to continue");
Console.ReadLine();
var models = collection.AsQueryable().SelectMany(g => g.Brands).SelectMany(b => b.Models).ToList();
Console.WriteLine(models.Count); //returns 0 I expected 6
Console.ReadLine();
}
}

Try
var models = collection.AsQueryable()
.SelectMany(g => g.Brands)
.Select(y => y.Models)
.SelectMany(x=> x);
Console.WriteLine(models.Count());
Working output (with extra Select())
aggregate([{
"$unwind": "$Brands"
}, {
"$project": {
"Brands": "$Brands",
"_id": 0
}
}, {
"$project": {
"Models": "$Brands.Models",
"_id": 0
}
}, {
"$unwind": "$Models"
}, {
"$project": {
"Models": "$Models",
"_id": 0
}
}])
OP Output without extra Select()
aggregate([{
"$unwind": "$Brands"
}, {
"$project": {
"Brands": "$Brands",
"_id": 0
}
}, {
"$unwind": "$Models"
}, {
"$project": {
"Models": "$Models",
"_id": 0
}
}])

Related

Serialize Json Array with Asp.net MVC

Could someone helpe me?
How can I code 'Shippings' class as an Array to get the json example below?
{
"seller_id": "123",
"amount": 100,
"order": {
"order_id": "1111",
"sales_tax": 0,
"product_type": "service"
},
"shippings": [{
"first_name": "John",
"phone_number": "5551999887766",
"shipping_amount": 3000,
"address": {
"street": "Street 35 Conts",
"number": "1000",
"complement": "ap1",
"postal_code": "90230060"
}
}],
"credit": {
"delayed": false,
"authenticated": false
}
}
I am doing this below, using asp.net mvc, but don't know how to get Shippings as Array [].
Can someone give me some exemplo or anything else... I'll appreciate.
var request = new GetNetRoot() {
SellerId = seller_id,
Amount = orderItens.Amount
Order = new GetNetPagOrder() {
OrderId = order.id.ToString(),
SalesTax = orderItens.Tax,
ProductType = orderItens.ProdType
},
Shippings = new GetNetPagShippings() {
FirstName = "",
PhoneNumber = usr.PhoneNumber,
ShippingAmount = orderItens.AmountShip,
Address = new GetNetPagAddress() {
Street = catEnd.IdEnderecoLogradouroNavigation.IdRuaNavigation.Nome,
Number = catEnd.NumEndereco,
Complement = catEnd.Complemento,
PostalCode = catEnd.IdEnderecoLogradouroNavigation.Cep
}
},
Credit = new GetNetPagCredit() {
Delayed = false,
Authenticated = false
}
};
var requestBody = JsonConvert.SerializeObject(request)
You should initialize Shippings like an array:
Shippings = new[] {
new GetNetPagShippings()
{
FirstName = "",
PhoneNumber = usr.PhoneNumber,
ShippingAmount = orderItens.AmountShip,
Address = new GetNetPagAddress()
{
Street = catEnd.IdEnderecoLogradouroNavigation.IdRuaNavigation.Nome,
Number = catEnd.NumEndereco,
Complement = catEnd.Complemento,
PostalCode = catEnd.IdEnderecoLogradouroNavigation.Cep
}
}
public class GetNetPagamentoRoot
{
...
public GetNetPagShippings[] Shippings { get; set; }
}
You can use array initializer syntax. Simplistic example would be array of int:
int[] myArray = new [] { 1, 2, 3 }
In your code Shippings should be initialized as an array.
var request = new GetNetRoot()
{
SellerId = seller_id,
Amount = orderItens.Amount
Order = new GetNetPagOrder()
{
OrderId = order.id.ToString(),
SalesTax = orderItens.Tax,
ProductType = orderItens.ProdType
},
// Array initializer with 2 elements.
Shippings = new[] {
new GetNetPagShippings()
{
FirstName = "",
PhoneNumber = usr.PhoneNumber,
ShippingAmount = orderItens.AmountShip,
Address = new GetNetPagAddress()
{
Street = catEnd.IdEnderecoLogradouroNavigation.IdRuaNavigation.Nome,
Number = catEnd.NumEndereco,
Complement = catEnd.Complemento,
PostalCode = catEnd.IdEnderecoLogradouroNavigation.Cep
}
},
new GetNetPagShippings()
{
FirstName = "",
PhoneNumber = usr.PhoneNumber,
ShippingAmount = orderItens.AmountShip,
Address = new GetNetPagAddress()
{
Street = catEnd.IdEnderecoLogradouroNavigation.IdRuaNavigation.Nome,
Number = catEnd.NumEndereco,
Complement = catEnd.Complemento,
PostalCode = catEnd.IdEnderecoLogradouroNavigation.Cep
}
}
},
Credit = new GetNetPagCredit()
{
Delayed = false,
Authenticated = false
}
};
var requestBody = JsonConvert.SerializeObject(request);
Your GetNetRoot outer class needs to define the "Shippings" property as a list or array of the GetNetPagShippings class as opposed to a single instance. When you serialize it to JSON, it will be represented as a JSON array of the object.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var myClassInstance = new MyOuterClass()
{
OuterProperty = "Outer Property",
MyInnerClassList = new List<MyInnerClass>()
{
new MyInnerClass()
{
InnerProperty = "Inner Property Item 1"
},
new MyInnerClass()
{
InnerProperty = "Inner Property Item 2"
}
}
};
string json = JsonConvert.SerializeObject(myClassInstance);
Console.WriteLine(json);
}
}
public class MyOuterClass
{
public string OuterProperty { get; set; }
public List<MyInnerClass> MyInnerClassList { get; set; }
}
public class MyInnerClass
{
public string InnerProperty { get; set; }
}
}

Can I use LINQ GroupBy to do this more cleanly?

In this contrived example, which closely resembles my real-world problem, I have a data set coming from an external source. Each record from the external source takes the following form:
[Classification] NVARCHAR(32),
[Rank] INT,
[Data] NVARCHAR(1024)
I am looking to build an object where the Rank and Data are patched into a single instance of a response object that contains list properties for the three hard-coded Classification values, ordered by Rank.
I have something that works, but I can't help but think that it could be done better. This is what I have:
public static void Main()
{
IEnumerable<GroupingTestRecord> records = new List<GroupingTestRecord>
{
new GroupingTestRecord { Classification = "A", Rank = 1, Data = "A1" },
new GroupingTestRecord { Classification = "A", Rank = 2, Data = "A2" },
new GroupingTestRecord { Classification = "A", Rank = 3, Data = "A3" },
new GroupingTestRecord { Classification = "B", Rank = 1, Data = "B1" },
new GroupingTestRecord { Classification = "B", Rank = 2, Data = "B2" },
new GroupingTestRecord { Classification = "B", Rank = 3, Data = "B3" },
new GroupingTestRecord { Classification = "C", Rank = 1, Data = "C1" },
new GroupingTestRecord { Classification = "C", Rank = 2, Data = "C2" },
new GroupingTestRecord { Classification = "C", Rank = 3, Data = "C3" },
};
GroupTestResult r = new GroupTestResult
{
A = records.Where(i => i.Classification == "A").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
B = records.Where(i => i.Classification == "B").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
C = records.Where(i => i.Classification == "C").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
};
The source record DTO:
public class GroupingTestRecord
{
public string Classification { get; set; }
public int? Rank { get; set; }
public string Data { get; set; }
}
The destination single class:
public class GroupTestResult
{
public IEnumerable<GroupTestResultItem> A { get; set; }
public IEnumerable<GroupTestResultItem> B { get; set; }
public IEnumerable<GroupTestResultItem> C { get; set; }
}
The distination child class:
public class GroupTestResultItem
{
public int? Rank { get; set; }
public string Data { get; set; }
}
Ouput
{
"A":[
{
"Rank":1,
"Data":"A1"
},
{
"Rank":2,
"Data":"A2"
},
{
"Rank":3,
"Data":"A3"
}
],
"B":[
{
"Rank":1,
"Data":"B1"
},
{
"Rank":2,
"Data":"B2"
},
{
"Rank":3,
"Data":"B3"
}
],
"C":[
{
"Rank":1,
"Data":"C1"
},
{
"Rank":2,
"Data":"C2"
},
{
"Rank":3,
"Data":"C3"
}
]
}
Fiddle
Is there a better way to achieve my goal here?
The same JSON output was achieved using GroupBy first on the Classification and applying ToDictionary on the resulting IGrouping<string, GroupingTestRecord>.Key
var r = records
.GroupBy(_ => _.Classification)
.ToDictionary(
k => k.Key,
v => v.Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank).ToArray()
);
var json = JsonConvert.SerializeObject(r);
Console.WriteLine(json);
which should easily deserialize to the destination single class (for example on a client)
var result = JsonConvert.DeserializeObject<GroupTestResult>(json);
is it possible to get the top level result into a GroupTestResult object?
Build the result from the dictionary
var result = new GroupTestResult {
A = r.ContainsKey("A") ? r["A"] : Enumerable.Empty<GroupTestResultItem>();,
B = r.ContainsKey("B") ? r["B"] : Enumerable.Empty<GroupTestResultItem>();,
C = r.ContainsKey("C") ? r["C"] : Enumerable.Empty<GroupTestResultItem>();,
};
Or this
var result = records.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, x => x.Select(y => new {y.Rank, y.Data})
.OrderBy(y => y.Rank));
Console.WriteLine(JsonConvert.SerializeObject(result));
Full Demo Here

How can I create a JsonPatchDocument from comparing two c# objects?

Given I have two c# objects of the same type, I want to compare them to create a JsonPatchDocument.
I have a StyleDetail class defined like this:
public class StyleDetail
{
public string Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public decimal OriginalPrice { get; set; }
public decimal Price { get; set; }
public string Notes { get; set; }
public string ImageUrl { get; set; }
public bool Wishlist { get; set; }
public List<string> Attributes { get; set; }
public ColourList Colours { get; set; }
public SizeList Sizes { get; set; }
public ResultPage<Style> Related { get; set; }
public ResultPage<Style> Similar { get; set; }
public List<Promotion> Promotions { get; set; }
public int StoreStock { get; set; }
public StyleDetail()
{
Attributes = new List<string>();
Colours = new ColourList();
Sizes = new SizeList();
Promotions = new List<Promotion>();
}
}
if I have two StyleDetail objects
StyleDetail styleNew = db.GetStyle(123);
StyleDetail styleOld = db.GetStyle(456);
I now want to create a JsonPatchDocument so I can send the differences to my REST API... How to do this??
JsonPatchDocument patch = new JsonPatchDocument();
// Now I want to populate patch with the differences between styleNew and styleOld - how?
in javascript, there is a library to do this https://www.npmjs.com/package/rfc6902
Calculate diff between two objects:
rfc6902.createPatch({first: 'Chris'}, {first: 'Chris', last:
'Brown'});
[ { op: 'add', path: '/last', value: 'Brown' } ]
but I am looking for a c# implementation
Let's abuse the fact that your classes are serializable to JSON!
Here's a first attempt at a patch creator that doesn't care about your actual object, only about the JSON representation of that object.
public static JsonPatchDocument CreatePatch(object originalObject, object modifiedObject)
{
var original = JObject.FromObject(originalObject);
var modified = JObject.FromObject(modifiedObject);
var patch = new JsonPatchDocument();
FillPatchForObject(original, modified, patch, "/");
return patch;
}
static void FillPatchForObject(JObject orig, JObject mod, JsonPatchDocument patch, string path)
{
var origNames = orig.Properties().Select(x => x.Name).ToArray();
var modNames = mod.Properties().Select(x => x.Name).ToArray();
// Names removed in modified
foreach (var k in origNames.Except(modNames))
{
var prop = orig.Property(k);
patch.Remove(path + prop.Name);
}
// Names added in modified
foreach (var k in modNames.Except(origNames))
{
var prop = mod.Property(k);
patch.Add(path + prop.Name, prop.Value);
}
// Present in both
foreach (var k in origNames.Intersect(modNames))
{
var origProp = orig.Property(k);
var modProp = mod.Property(k);
if (origProp.Value.Type != modProp.Value.Type)
{
patch.Replace(path + modProp.Name, modProp.Value);
}
else if (!string.Equals(
origProp.Value.ToString(Newtonsoft.Json.Formatting.None),
modProp.Value.ToString(Newtonsoft.Json.Formatting.None)))
{
if (origProp.Value.Type == JTokenType.Object)
{
// Recurse into objects
FillPatchForObject(origProp.Value as JObject, modProp.Value as JObject, patch, path + modProp.Name +"/");
}
else
{
// Replace values directly
patch.Replace(path + modProp.Name, modProp.Value);
}
}
}
}
Usage:
var patch = CreatePatch(
new { Unchanged = new[] { 1, 2, 3, 4, 5 }, Changed = "1", Removed = "1" },
new { Unchanged = new[] { 1, 2, 3, 4, 5 }, Changed = "2", Added = new { x = "1" } });
// Result of JsonConvert.SerializeObject(patch)
[
{
"path": "/Removed",
"op": "remove"
},
{
"value": {
"x": "1"
},
"path": "/Added",
"op": "add"
},
{
"value": "2",
"path": "/Changed",
"op": "replace"
}
]
You could use my DiffAnalyzer. It's based on reflection and you can configure the depth you want to analyze.
https://github.com/rcarubbi/Carubbi.DiffAnalyzer
var before = new User { Id = 1, Name="foo"};
var after= new User { Id = 2, Name="bar"};
var analyzer = new DiffAnalyzer();
var results = analyzer.Compare(before, after);
You can use this
You can install using NuGet, see SimpleHelpers.ObjectDiffPatch at NuGet.org
PM> Install-Package SimpleHelpers.ObjectDiffPatch
Use:
StyleDetail styleNew = new StyleDetail() { Id = "12", Code = "first" };
StyleDetail styleOld = new StyleDetail() { Id = "23", Code = "second" };
var diff = ObjectDiffPatch.GenerateDiff (styleOld , styleNew );
// original properties values
Console.WriteLine (diff.OldValues.ToString());
// updated properties values
Console.WriteLine (diff.NewValues.ToString());

Find Id and insert a BsonArray into collection MongoDB

I have this collection
db.UserWatchtbl.insert( {
fbId: "",
Name: "user3",
pass: "pass3",
Watchtbl:
[
{
wid: "1350",
name: "bought stock1",
Symboles: [ { Name: "AAA" }, { Name: "BSI" } ]
},
{
wid: "1350",
name: "bought stock2",
Symboles: [ { Name: "AAA" }, { Name: "BSI" }, { Name: "EXXI" } ]
},
{
wid: "1350",
name: "bought stock2",
Symboles: [ { Name: "AAA" }, { Name: "BSI" }, { Name: "EXXI" } ]
}
]
} )
My form is loading the Id list from the MongoDB then I select the ID that I want to insert the new WatchTbl with the data.
And I try to find an Id then I insert into Watchtbl their Data.
private async void button1_Click(object sender, EventArgs e)
{
// add user into datagridview from MongoDB Colelction Watchtbl
var client = new MongoClient("mongodb://dataservername:27017");
var database = client.GetDatabase("WatchTblDB");
var collectionWatchtbl = database.GetCollection<BsonDocument>("UserWatchtbl");
var document = new BsonDocument();
BsonArray arrSym = new BsonArray();
BsonArray arrWatc = new BsonArray();
document.Add("wid", WIDTextBox.Text.ToString());
document.Add("name", NameComboBox.SelectedItem.ToString());
foreach (var item in SymbolesListBox.SelectedItems)
{
arrSym.Add(new BsonDocument("Name", item.ToString()));
}
document.Add("Symboles", arrSym);
arrWatc.Add(new BsonDocument("Watchtbl", document));
var result = await collectionWatchtbl.FindOneAndUpdateAsync(
Builders<BsonDocument>.Filter.Eq("_id", UsersComboBox.SelectedItem.ToString()),
Builders<BsonDocument>.Update.Set("Watchtbl", arrWatc)
);
}
Bu It looks my code not working, So any help with that?
Update
After Add the code of ntohl
I face this problem when I try to insert into collection
I have a little advantage here, because I have answered Your previous post, and I could use it as a base. I have added the collection initialization part also, because the type of the elements are important. You need to have SymboleCls in the SymbolesListBox.ItemsSource to have it work for example. And UsersComboBox must have ObjectIds. You don't have to create the whole array, or You need to fill it with previous elements, if You use Update.Set. Instead I used AddToSet.
private readonly IMongoCollection<BsonDocument> collectionWatchtbl;
public MainWindow()
{
InitializeComponent();
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
collectionWatchtbl = database.GetCollection<BsonDocument>("UserWatchtbl");
var filter = new BsonDocument();
var user = new List<UserWatchTblCls>();
var cursor = collectionWatchtbl.FindAsync(filter).Result;
cursor.ForEachAsync(batch =>
{
user.Add(BsonSerializer.Deserialize<UserWatchTblCls>(batch));
});
UsersComboBox.ItemsSource = user.Select(x => x.Id);
SymbolesListBox.DisplayMember = "Name";
SymbolesListBox.ItemsSource =
user.SelectMany(x => x.WatchTbls).SelectMany(y => y.Symbols);
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var document = new BsonDocument();
BsonArray arrSym = new BsonArray();
//BsonArray arrWatc = new BsonArray();
document.Add("wid", WIDTextBox.Text.ToString());
document.Add("name", NameComboBox.SelectedItem.ToString());
foreach (SymboleCls item in SymbolesListBox.SelectedItems)
{
arrSym.Add(new BsonDocument("Name", item.Name));
}
document.Add("Symboles", arrSym);
// needed only when replacing the Watchtbl
//arrWatc.Add(document);
// Do You really need to use async?
collectionWatchtbl.UpdateOne(Builders<BsonDocument>.Filter.Eq("_id", UsersComboBox.SelectedItem), Builders<BsonDocument>.Update.AddToSet("Watchtbl", document));
}
And the POCO classes for deserialize>
public class UserWatchTblCls
{
[BsonId]
[BsonElement("_id")]
public ObjectId Id { get; set; }
public string fbId { get; set; }
public string Name { get; set; }
[BsonElement("pass")]
public string Pass { get; set; }
[BsonElement("Watchtbl")]
public List<WatchTblCls> WatchTbls { get; set; }
}
public class WatchTblCls
{
[BsonElement("wid")]
public string WID { get; set; }
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("Symboles")]
public List<SymboleCls> Symbols { get; set; }
}
public class SymboleCls
{
public string Name { get; set; }
}

Call function using Mongodb-CSharp Driver

Currently, I work on Csharp Driver LinQ for MongoDb, and I have issues to implement a method that like calling a stored function on MongoDb. Actually,I know that MongoDB doesn't have stored procedure mechanism. And I need someone give suggestion or solution to work around for this. The important thing that is somehow data will be done in mongodb without done in memory. For example, I want to return a list with filter condition which implemented by custom method. This method calculates base on field dependencies .
An example is done in memory.
var list = collection.AsQueryable<Rule>().ToList();
var result = list.Where(x => x.Active && CalcMethod(x.Rule)> 5);
And custom method here.
public static int CalcMethod(Rule rule)
{
if(rule.Active)
// bypass check null
return rule.Weight.Unit * rule.Weight.Value;
else
// return something here
}
The CalcMethod method like a function in SQL Server.
Whether we can do this with MongoDb or other, expect that we can inject a method to calculate data and filter without done in memory.
Every help will be appreciated.
Soner,
I think you can use aggregation for that (or MapReduce for complex things). ie:
async void Main()
{
var client = new MongoClient("mongodb://localhost");
var db = client.GetDatabase("TestSPLike");
var col = db.GetCollection<Rule>("rules");
await client.DropDatabaseAsync("TestSPLike"); // recreate if exists
await InsertSampleData(col); // save some sample data
var data = await col.Find( new BsonDocument() ).ToListAsync();
//data.Dump("All - initial");
/*
db.rules.aggregate(
[
{ $match:
{
"Active":true
}
},
{ $project:
{
Name: 1,
Active: 1,
Weight:1,
Produce: { $multiply: [ "$Weight.Unit", "$Weight.Value" ] }
}
},
{ $match:
{
Produce: {"$gt":5}
}
}
]
)
*/
var aggregate = col.Aggregate()
.Match(new BsonDocument{ {"Active", true} })
.Project( new BsonDocument {
{"Name", 1},
{"Active", 1},
{"Weight",1},
{"Produce",
new BsonDocument{
{ "$multiply", new BsonArray{"$Weight.Unit", "$Weight.Value"} }
}}
} )
.Match( new BsonDocument {
{ "Produce",
new BsonDocument{ {"$gt",5} }
}
})
.Project( new BsonDocument {
{"Name", 1},
{"Active", 1},
{"Weight",1}
} );
var result = await aggregate.ToListAsync();
//result.Dump();
}
private async Task InsertSampleData(IMongoCollection<Rule> col)
{
var data = new List<Rule>() {
new Rule { Name="Rule1", Active = true, Weight = new Weight { Unit=1, Value=10} },
new Rule { Name="Rule2", Active = false, Weight = new Weight { Unit=2, Value=3} },
new Rule { Name="Rule3", Active = true, Weight = new Weight { Unit=1, Value=4} },
new Rule { Name="Rule4", Active = true, Weight = new Weight { Unit=2, Value=2} },
new Rule { Name="Rule5", Active = false, Weight = new Weight { Unit=1, Value=5} },
new Rule { Name="Rule6", Active = true, Weight = new Weight { Unit=2, Value=4} },
};
await col.InsertManyAsync( data,new InsertManyOptions{ IsOrdered=true});
}
public class Weight
{
public int Unit { get; set; }
public int Value { get; set; }
}
public class Rule
{
public ObjectId _id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public Weight Weight { get; set; }
}

Categories