How to update all keys in MongoDB from a dictionary? - c#

I have some object:
public class ObjA
{
[BsonElement("_id")]
public int ID { get; set; }
[BsonElement("languages")]
public Dictionary<string, ObjB> languages { get; set; }
}
public class ObjB
{
[BsonElement("translation_1")]
public string Translation_1 { get; set; }
[BsonElement("translation_2")]
public string Translation_2 { get; set; }
[BsonElement("translation_3")]
public string Translation_3 { get; set; }
}
There are situations where I need to update Translation_1 property of ObjB for every key in languages property of ObjA object.
Value of key of languages dictionary is not the same for all ObjA.
So, query should update all Translation_1 properties regardless of the key
Update:
So far I have not made any significant progress:
UpdateDefinition<ObjA> update = Builders<ObjA>.Update.Set("languages.-key-.translation_1", newValue);
var result = await Collection.UpdateManyAsync(x => x.Id == someID, update);

There are situations where I need to update Translation_1 property of
ObjB for every key in languages property of ObjA object.
Here is the Update with Aggregation Pipeline code, the first one is the mongo shell version and the second the C# version. The update modifies the value of the property translation_1 of ObjB, for all keys of the languages dictionary property of ObjA.
var NEW_VALUE = "some new value"
var someId = "some id value"
db.test.updateOne(
{ _id: someId },
[
{
$set: {
languages: {
$map: {
input: { $objectToArray: "$languages" },
as: "ele",
in: {
$mergeObjects: [
"$$ele",
{ "v": {
"translation_1": NEW_VALUE,
"translation_2": "$$ele.v.translation_2",
"translation_3": "$$ele.v.translation_3"
} } ]
}
}
}
}},
{
$set: {
languages: {
$arrayToObject: "$languages"
}
}},
]
)
var pipeline = new BsonDocumentStagePipelineDefinition<ObjA, ObjA>(
new[] {
new BsonDocument("$set",
new BsonDocument("languages",
new BsonDocument("$map",
new BsonDocument {
{ "input", new BsonDocument("$objectToArray", "$languages") },
{ "as", "ele" },
{ "in",
new BsonDocument("$mergeObjects",
new BsonArray {
"$$ele",
new BsonDocument("v",
new BsonDocument {
{ "translation_1", NEW_VALUE },
{ "translation_2", "$$ele.v.translation_2" },
{ "translation_3", "$$ele.v.translation_3" }
})
}) }
}
))),
new BsonDocument("$set",
new BsonDocument("languages",
new BsonDocument("$arrayToObject", "$languages")
))
}
);
System.Linq.Expressions.Expression<Func<ObjA, bool>> filter = x => x.id == someId;
var update = new PipelineUpdateDefinition<ObjA>(pipeline);
var result = collection.UpdateOne<ObjA>(filter, update);

Related

Dapper ORM multiple level nested object

I'm trying to return SQL Server data using Dapper ORM that had nested object, so far here is what I'm doing
Model class
public class CustomerAll
{
public string KODELANG { get; set; }
public string GRUPLANG { get; set; }
public string NAMALANG { get; set; }
public string ALAMAT { get; set; }
public string STATUS { get; set; }
public List<NoDiscount> DISC_ACTIVE { get; set; }
}
public class NoDiscount
{
public string NO_DISC { get; set; }
public List<ProductDiscount> LIST_PRODUCT{ get; set; }
}
public class ProductDiscount
{
public string KODEPROD{ get; set; }
}
Raw query
SELECT CUSTOMER.KODELANG
,CUSTOMER.GRUPLANG
,CUSTOMER.NAMALANG
,CUSTOMER.ALAMAT
,CUSTOMER.STATUS
,DISCOUNT.NO_DISC
,PRODUCT.KODEPROD
FROM CUST_TABLE CUSTOMER, DCNT_TABLE DISCOUNT, PRODUCT_TABLE PRODUCT
WHERE CUSTOMER.KODELANG = DISCOUNT.KODELANG
AND DISCOUNT.NO_DISC = PRODUCT.NO_DISC
AND CUSTOMER.KODELANG = #CustNum
Controller class
var parameters = new { CustNum = customerNumber };
var lookup = new Dictionary<string, CustomerAll>();
con.Query<CustomerAll, NoDiscount, ProductDiscount, CustomerAll>(
querySql,
(c, n, p) =>
{
CustomerAll customer;
if (!lookup.TryGetValue(c.KODELANG, out customer))
{
lookup.Add(c.KODELANG, customer = c);
}
if (customer.DISC_ACTIVE == null)
{
customer.DISC_ACTIVE = new List<NoDiscount>();
}
if (p != null)
{
if (n.LIST_PRODUCT == null)
{
n.LIST_PRODUCT = new List<ProductDiscount>();
}
n.LIST_PRODUCT.Add(p);
}
customer.DISC_ACTIVE.Add(n);
return customer;
}
,parameters
,splitOn: "KODELANG, NO_DISC, KODEPROD");
return Ok(lookup.Values);
Here is what response returned
[
{
"KODELANG": "101308",
"GRUPLANG": "22",
"NAMALANG": "Name Example",
"ALAMAT": "Street number 4",
"STATUS": "A",
"DISC_ACTIVE": [
{
"NO_DISC": "DISC/1021/0001",
"LIST_PRODUCT": [
{
"KODEPROD": "TLBCA"
}
]
},
{
"NO_DISC": "DISC/1021/0001",
"LIST_PRODUCT": [
{
"KODEPROD": "TLBCB"
}
]
},
{
"NO_DISC": "DISC/3304/0009",
"LIST_PRODUCT": [
{
"KODEPROD": "ZVKAA"
}
]
}
]
}
]
What i'm trying to had is to be returned like these
[
{
"KODELANG": "101308",
"GRUPLANG": "22",
"NAMALANG": "Name Example",
"ALAMAT": "Street number 4",
"STATUS": "A",
"DISC_ACTIVE": [
{
"NO_DISC": "DISC/1021/0001",
"LIST_PRODUCT": [
{
"KODEPROD": "TLBCA"
},
{
"KODEPROD": "TLBCB"
},
{
"KODEPROD": "TLBCC"
}
]
},
{
"NO_DISC": "DISC/3304/0001",
"LIST_PRODUCT": [
{
"KODEPROD": "ZVKAA"
}
]
}
]
}
]
I don't want the LIST_PRODUCT getting added again if the NO_DISC are the same
Try this, beware that same NoDiscount object may have duplicate copys in diffierent customers.
var parameters = new { CustNum = customerNumber };
var customDict = new Dictionary<string, CustomerAll>();
con.Query<CustomerAll, NoDiscount, ProductDiscount, CustomerAll>(
querySql,
(c, n, p) =>
{
CustomerAll customer = null;
if (customDict.ContainsKey(c.KODELANG))
{
customer = customDict[c.KODELANG];
}
else
{
var customerObj = new CustomerAll()
{
KODELANG = c.KODELAN,
DISC_ACTIVE = new List<NoDiscount>()
};
customDict.Add(c.KODELANG, customerObj);
};
NoDiscount noDiscount = customer.DISC_ACTIVE.FirstOrDefault(x => x.NO_DISC == n.NO_DISC);
if (noDiscount ==null)
{
var noDiscountObj = new NoDiscount()
{
NO_DISC = n.NO_DISC,
LIST_PRODUCT = new List<ProductDiscount>()
};
noDiscount = noDiscountObj;
customer.DISC_ACTIVE.Add(noDiscountObj);
}
if (p != null && !noDiscount.LIST_PRODUCT.Contains(x=>x.KODEPROD==p.KODEPROD))
{
noDiscount.LIST_PRODUCT.Add(p);
}
return customer;
}
, parameters
, splitOn: "KODELANG, NO_DISC, KODEPROD");
return Ok(customDict.Values);

MongoDb c# driver consecutive SelectMany

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
}
}])

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