Call function using Mongodb-CSharp Driver - c#

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; }
}

Related

API Returns OK in API but no Data

I'm trying to return an object in a model through another API I've made but when I fetch my API with GET on PostMan it only returns 200 OK but an empty array.
This is what I'm trying to get:
[
{
"productId": 0,
"quantity": 0
}
]
And this is what I'm getting in PostMan
[]
by calling with this API URL:
http://localhost:5700/api/Orders/basket/firstId
Here is my controller and the respective GET method on which I'm calling upon in Postman:
[HttpGet("basket/{identifier}")]
public async Task<IEnumerable<BasketEntryDto>> FetchBasketEntries(string identifier)
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
$"https://localhost:5500/api/Basket/{identifier}")
{
Headers = { { HeaderNames.Accept, "application/json" }, }
};
var httpClient = httpClientFactory.CreateClient();
using var httpResponseMessage =
await httpClient.SendAsync(httpRequestMessage);
var basketEntires = Enumerable.Empty<BasketEntryDto>();
if (!httpResponseMessage.IsSuccessStatusCode)
return basketEntires;
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var basketDTO = await JsonSerializer.DeserializeAsync
<BasketDto>(contentStream, options);
//basketDTO = new NewBasketDTO.ItemDTO
//{
// ProductId = basketDTO.ProductId,
// Quantity = basketDTO.Quantity
//};
basketEntires = basketDTO.Entries.Select(x =>
new BasketEntryDto
{
ProductId = x.ProductId,
Quantity = x.Quantity
}
);
return basketEntires; // 200 OK
}
Here is my BasketDTO:
public class BasketDto
{
public string Identifier { get; set; }
public IEnumerable<BasketEntryDto> Entries { get; set; } = new List<BasketEntryDto>();
}
and my BasketEntryDto:
public class BasketEntryDto
{
public int ProductId { get; set; }
public int Quantity { get; set; }
}
and this is the original API in JSON:
{
"identifier": "mrPostMan",
"items": [
{
"productId": 1,
"quantity": 1
}
]
}
in which I want to get the items array and its object.
Is there something I'm doing wrong? Why is it returning an empty array? Thanks in advance for any help..
As I mentioned in the comments, you need to change the Entries property in BasketDTO to Items to match with the JSON property name.
public class BasketDto
{
public string Identifier { get; set; }
public IEnumerable<BasketEntryDto> Items { get; set; } = new List<BasketEntryDto>();
}
Alternatively, you could also explicitly mention the JSON Property name by using JsonPropertyNameAttribute
public class BasketDto
{
public string Identifier { get; set; }
[JsonPropertyName("items")]
public IEnumerable<BasketEntryDto> Entries { get; set; } = new List<BasketEntryDto>();
}
Well this will work when there are more than 0 items (basket is not empty) but not when the basket is empty as:
basketEntires = basketDTO.Entries.Select(x =>
new BasketEntryDto
{
ProductId = x.ProductId,
Quantity = x.Quantity
}
);
there are no items, select will not work. so you can do like this:
if(basketEntires.Count == 0)
{
basketEntires = new BasketEntryDto
{
ProductId = 0,
Quantity = 0
}
}
return basketEntires; // 200 OK
And don't forget to add .ToList():
basketEntires = basketDTO.Entries.Select(x =>
new BasketEntryDto
{
ProductId = x.ProductId,
Quantity = x.Quantity
}
).ToList();
You should not return IEnumerable, instead you should return list (or array).

How to update all keys in MongoDB from a dictionary?

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);

TelerikCombobox for blazor search in itemtemplate

I have a telerik comobox with filter option enabled. I have added itemtemplate for displaying id and value in the textfield. I want to filter with contains when user type in the dropdown.
Below is the code I tried
<TelerikComboBox Data="#CostCenters"
Filterable="true" FilterOperator="#filterOperator"
Placeholder="Find cost center by typing"
#bind-Value="#SelectedCostCenter1" TextField="CostCenterName" ValueField="CostCenterId">
<ItemTemplate Context="employeeItem">
#( (employeeItem as CostCenter).CostCenterId) #((employeeItem as CostCenter).CostCenterName)
</ItemTemplate>
</TelerikComboBox>
But the above code is only filtering by name
The built-in filtering works on the text (reference) so if you want to filter by multiple fields, you should use the OnRead event and handle the filtering as you need. It's important to note that the contains filter only makes sense for strings, so it is not very likely that you could filter IDs with that since they are usually numbers or guids.
Anyway, here's an example how you can filter the data as required - in this sample - by both ID and string text:
#SelectedValue
<br />
<TelerikComboBox Data="#CurrentOptions"
OnRead=#ReadItems
Filterable="true"
Placeholder="Find a car by typing part of its make"
#bind-Value="#SelectedValue" ValueField="Id" TextField="Make">
</TelerikComboBox>
#code {
public int? SelectedValue { get; set; }
List<Car> AllOptions { get; set; }
List<Car> CurrentOptions { get; set; }
protected async Task ReadItems(ComboBoxReadEventArgs args)
{
if (args.Request.Filters.Count > 0)
{
Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor;
string userInput = filter.Value.ToString();
string method = filter.Operator.ToString(); // you can also pass that along if you need to
CurrentOptions = await GetFilteredData(userInput);
}
else
{
CurrentOptions = await GetFilteredData(string.Empty);
}
}
// in a real case that would be a service method
public async Task<List<Car>> GetFilteredData(string filterText)
{
//generate the big data source that we want to narrow down for the user
//in a real case you would probably have fetched it from a database
if (AllOptions == null)
{
AllOptions = new List<Car>
{
new Car { Id = 1, Make = "Honda" },
new Car { Id = 2, Make = "Opel" },
new Car { Id = 3, Make = "Audi" },
new Car { Id = 4, Make = "Lancia" },
new Car { Id = 5, Make = "BMW" },
new Car { Id = 6, Make = "Mercedes" },
new Car { Id = 7, Make = "Tesla" },
new Car { Id = 8, Make = "Vw" },
new Car { Id = 9, Make = "Alpha Romeo" },
new Car { Id = 10, Make = "Chevrolet" },
new Car { Id = 11, Make = "Ford" },
new Car { Id = 12, Make = "Cadillac" },
new Car { Id = 13, Make = "Dodge" },
new Car { Id = 14, Make = "Jeep" },
new Car { Id = 15, Make = "Chrysler" },
new Car { Id = 16, Make = "Lincoln" }
};
}
if (string.IsNullOrEmpty(filterText))
{
return await Task.FromResult(AllOptions);
}
var filteredData = AllOptions.Where(c => c.Make.ToLowerInvariant().Contains(filterText.ToLowerInvariant()));
// here's an attempt to also filter by ID based on a string, implement this as needed
int id;
if (int.TryParse(filterText, out id))
{
var filteredById = AllOptions.Where(c => c.Id.ToString().Contains(filterText));
filteredData = filteredData.Union(filteredById);
}
return await Task.FromResult(filteredData.ToList());
}
public class Car
{
public int Id { get; set; }
public string Make { get; set; }
}
}

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());

Categories