One to many relationship expressed in JSON - c#

I am having a small problem in some LINQ I am writing
public async Task<BookingEntry[]> AuditInvoiceForAPI(BookingSearchFilter filter)
{
if (filter == null)
throw new ArgumentNullException();
using var speedPayDbContext = this.speedPayDbContextFactory();
var results = await speedPayDbContext.spAuditInvoiceForAPI(filter.BookingID, filter.VendorID, filter.InvoiceID).ToArrayAsync();
var BookingResponse = results
.Select(r =>
{
return new BookingEntry()
{
Result = r.Result,
BookingID = r.BookingID,
Balance = r.Balance,
TotalCost = r.TotalCost,
CostDetails =
results.Where(cd => cd.BookingID == r.BookingID)
.Select(a => new Costdetail(a.Amount, a.ChargeCodeID)).ToArray(),
Message = r.Message,
};
})
.ToArray();
return BookingResponse;
here is the result set
BookingId
Result
Message
TotalCost
BillKey
Balance
ChargeCodeId
amount
8446670
0
Success
498.54
8446670
89.8000
11
75.00
8446670
0
Success
498.54
8446670
89.8000
14
14.80
I am getting dup in the JSON which I can understand why. There are indeed two rows returned one booking entry with two costdetails.
Can someone please help me with get rid of the dup?
Its probably as simple as I don't understand SelectMany.
This is in essence a simple join first 6 columns are the header and the last two are the detail records and you can see that the bookingID is what they are joined on.
Thanks very much
{
"bookingID": "8446670",
"result": "0",
"message": "Success",
"totalCost": 498.54,
"balance": 89.8,
"costDetails": [
{
"chargeCodeID": 11,
"amount": 75
},
{
"chargeCodeID": 14,
"amount": 14.8
}
]
},
{
"bookingID": "8446670",
"result": "0",
"message": "Success",
"totalCost": 498.54,
"balance": 89.8,
"costDetails": [
{
"chargeCodeID": 11,
"amount": 75
},
{
"chargeCodeID": 14,
"amount": 14.8
}
]
}
]

Related

MongoDb conditional UpdateMany statement including expression using .net driver

I have the following document:
{
"_id": {
"$oid": "5fc232d9b9b390623ce498ee"
},
"trigger": {
"type": 0,
"interval": 15
},
"startTime": {
"$numberLong": "1608078905"
},
"state": 0,
"assigned_to": ""
}
And I have a UpdateMany statement that only updates documents that their startTime (timespan) + interval (seconds) is lower than equal to the current timestamp with the value of 'computer a', (UPDATE) and set the 'state' attribute to '1':
(UPDATE)
db.mycollection.updateMany(
{ state: 0 },
[
{ $set: { assigned_to: { $switch: {
branches: [
{ case: { $lte: [ { $add: [ "$startTime", "$trigger.interval" ] }, new Timestamp() ] }, then: "computer a" }
],
default: ""
} } } },
{ $set: { state: 1 } }
]
)
My goals are:
(RESOLVED) To update the state to '1' in addition to the 'assigned_to' field
(RESOLVED) To automatically calculate the timestamp (the current time)
Eventually, to have it working with my C# .NET Core application (if possible - with the driver syntax so it will be strongly-typed)
On the C# side I've tried the following (with no success):
var results = _jobs.UpdateMany(
f => (f.StartTime + f.Trigger.Interval) <= DateTimeOffset.Now.ToUnixTimeSeconds(),
o => o.AssignedTo == "computer a");
Another approach I tried:
var builder = Builders<Job>.Filter;
var filters =
builder.Lte(o => o.StartTime + o.Trigger.Interval, DateTimeOffset.Now.ToUnixTimeSeconds()) &
builder.Eq(o => o.AssignedTo, string.Empty);
var updateDefinition =
Builders<Job>.Update.Set(x => x.AssignedTo, Environment.MachineName);
var result = _jobs.UpdateMany(filters, updateDefinition);
After spending hours, I only achieve the UpdateMany statement above working.
If you have ideas about how it could be done in a better way - I'm free for offers.

How can I filter Mongodb specific document's subdocument

Consider you have Order and OrderDetails. I'm trying to filter order details for the specific order.
For instance if you have document something like this:
{
"orders": [
{
"id": 1,
"date": "02.04.2020 ...",
"user": {
"name": "xx",
"surname": "yy"
},
"orderDetails": [
{
"id": 1,
"productId": 5,
"quantity": 1,
"state": 3
},
{
"id": 2,
"productId": 3,
"quantity": 4,
"state": 3
},
{
"id": 3,
"productId": 4,
"quantity": 12,
"state": 2
},
{
"id": 4,
"productId": 7,
"quantity": 8,
"state": 2
},
{
"id": 5,
"productId": 12,
"quantity": 9,
"state": 3
}
]
},
{
"id": 2,
"date": "01.04.2020 ...",
"user": {
"name": "xx",
"surname": "yy"
},
"orderDetails": [
{
"id": 6,
"productId": 5,
"quantity": 1,
"state": 3
},
{
"id": 7,
"productId": 3,
"quantity": 4,
"state": 3
},
{
"id": 8,
"productId": 4,
"quantity": 12,
"state": 2
}
]
}
}
What I'm trying to do is first filtering by order and then state of an order detail. I have a code like this but it always brings correct order with all orderDetails. Seems like it doesn't care about equal filter for orderDetails.
Actually it's working but not filtering. Because I only have 3 types of state(enum) and int values are 1,2,3. Query brings nothing if I give 4.
var builder = Builders<Order>.Filter;
var filter = builderk.And(builder.Eq("_id", ObjectId.Parse(elementid)), builder.Eq("orderDetails.state", 3));
var result = _mongoRepository.FindByFilter(filter).ToList();
I also tried AnyEq and something like that filters but didn't work.
I will be very happy if anyone can help me.
Thanks.
You can use aggregation operation for operations such as filter sorting in detail records.
If we continue through the sample, you must first create a filter for your master data.
var builderMaster = Builders<Order>.Filter;
var filterMaster = builderMaster.Eq("_id", ObjectId.Parse(elementid));
Then you need to create a different filter for the details.
Important: You must use the BsonDocument type when creating detail filters. Because you cannot give a specific type when you filter the details.
var builderDetail = Builders<BsonDocument>.Filter;
var filterDetail = builderDetail.Eq("orderDetails.state", 3);
Then you can start typing the query.
var list = _mongoRepository.Aggregate()
.Match(filterMaster)
.Unwind("orderDetails")// Name the details.
.Match(filterDetail)
.Sort(sort)// optionally
.Skip(skip) // optionally
.Limit(limit) // optionally
.ToList();
It will give you a list of BsonDocument with the given parameters. After that, you have to map with your own detail class.
var resultList = new List<OrderDetails>();
foreach (var master in list)
{
var masterObj = BsonSerializer.Deserialize<dynamic>(master);
foreach (var item in masterObj)
{
if (item.Key == "orderDetails")
{
var mapper = new MapperConfiguration(cfg => { }).CreateMapper();
var retItem = mapper.Map<OrderDetails>(item.Value);
resultList.Add(retItem);
}
}
}

JSON.net Array diff in C#

I have two arrays
"commanditaires_data": [
{
"id": "254",
"level": 78
},
{
"id": "255",
"level": 22
}
]
"commanditaires_data": [
{
"id": "254",
"level": 78
}, {
"id": "255",
"level": 22
},
{
"id": "255",
"level": 22
}
]
I don't manage to get the missing items in the first array.
I need to do a differential between these two arrays.
I tried to use Except() but I send me every items of the first arrays
I'm using JSON.NET so my vars for arrays are :
var srcObjs = source.Children().ToList().OfType<JObject>();
var tarObjs = target.Children().ToList().OfType<JObject>();
var diff = tarObjs.Except(src.objs);
=> It sends me all items in tarObjs.
I need to handle many cases :
If srcObjs.Count() < tarObjs.Count() //Added item(s)
If srcObjs.Count() > tarObjs.Count() // Deleted item(s)
Else //Edited Item(s)
I also tried to use linq but without success
public class CommanditairesEqualityComparer: IEqualityComparer<Commanditaires>
{
public bool Equals(Commanditaires first, Commanditaires second)
{
if (first== null && first== null)
return true;
return first.Id == second.Id
&& first.Level == second.Level;
}
public int GetHashCode(Commanditaires model)
{
return model.Id.GetHashCode() + model.Level.GetHashCode();
}
}
and then
var comparer = new CommanditairesEqualityComparer();
var distinctItems = firstList.Except(secondList, comparer );

c# linq-to-sql EF query to match a particular JSON structure

I've JSON with the following structure:
[
{
"ID": 1,
"Label": "Reg Scheme",
"Colours": [
{
"ID": 1,
"Value": "0x3333cc",
"Result": 1,
"Label": null
},
{
"ID": 2,
"Value": "0x666699",
"Result": 2,
"Label": null
},
{
"ID": 3,
"Value": "0x009966",
"Result": 3,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Spesh Scheme",
"Colours": [
{
"ID": 11,
"Value": "0x59699c",
"Result": 1,
"Label": null
},
{
"ID": 12,
"Value": "0x0070ff",
"Result": 2,
"Label": null
},
{
"ID": 13,
"Value": "0x90865e",
"Result": 3,
"Label": null
}
]
},
and I have an entity dataset whereby I've joined all the relevant information, and am attempting to produce JSON with that structure via a single linq-to-sql EF query to be returned to the webapi method.
My query so far is:
return
DbContext.Schemes
.Join(
DbContext.SchemeColours,
s => s.SchemeID,
sc => sc.SchemeID,
(s, sc) => new
{
s.SchemeID,
s.Label,
sc.Colour,
sc.Result,
sc.ColourID
})
.Select(a =>
new Overlay.ReportColourScheme
{
ID = a.SchemeID,
Label = a.Label,
Colours = new List<Overlay.ReportColour>
{
new Overlay.ReportColour
{
ID = a.ColourID,
Value = a.Colour,
Result = a.Result
}
}
})
.ToArray();
Which is almost there but not quite:
[
{
"ID": 1,
"Label": "Regular Scheme",
"Colours": [
{
"ID": 1,
"Value": "0x3333cc",
"Result": 1,
"Label": null
}
]
},
{
"ID": 1,
"Label": "Regular Scheme",
"Colours": [
{
"ID": 2,
"Value": "0x666699",
"Result": 2,
"Label": null
}
]
},
{
"ID": 1,
"Label": "Regular Scheme",
"Colours": [
{
"ID": 3,
"Value": "0x009966",
"Result": 3,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Protanopia adjusted Scheme",
"Colours": [
{
"ID": 11,
"Value": "0x59699c",
"Result": 1,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Protanopia adjusted Scheme",
"Colours": [
{
"ID": 12,
"Value": "0x0070ff",
"Result": 2,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Protanopia adjusted Scheme",
"Colours": [
{
"ID": 13,
"Value": "0x90865e",
"Result": 3,
"Label": null
}
]
},
As of course it creates a new list for every resultID. The top-level ID is a SchemeID- what I'm looking for is logic along the lines of: "take the first 3 Results with a particular schemeID, add them to a list in Colours, then move on to the next schemeID"
I believe this will produce identical JSON that I started the post with.
Any assistance at all would be greatly appreciated, thank you.
Try the following code:
return
DbContext.Schemes
.Join(
DbContext.SchemeColours,
s => s.SchemeID,
sc => sc.SchemeID,
(s, sc) => new
{
s.SchemeID,
s.Label,
sc.Colour,
sc.Result,
sc.ColourID
})
// After joining you group by SchemeID, in this way you have
// for each SchemeID the group of related items
.GroupBy(a => a.SchemeID)
// You then create your result, starting from the main object
.Select(g =>
new Overlay.ReportColourScheme
{
ID = g.Key,
// I suppose you have at least a child for each SchemeID,
// otherwise you can check if the list is empty
Label = g.FirstOrDefault().Label,
// For each group you create a list of child object
Colours = g.Select(v => new Overlay.ReportColour
{
ID = v.ColourID,
Value = v.Colour,
Result = v.Result
}).ToList()
})
.ToArray();
The main issue is that you are using a Join where actually you need a Group Join:
return DbContext.Schemes
.GroupJoin(DbContext.SchemeColours,
s => s.SchemeID,
sc => sc.SchemeID,
(s, colours) => new Overlay.ReportColourScheme
{
ID = s.SchemeID,
Label = s.Label,
Colours = colours
.Select(sc => new Overlay.ReportColour
{
ID = sc.ColourID,
Value = sc.Colour,
Result = sc.Result,
})
.ToList()
})
.ToArray();
But since you are using Entity Framework, it would be much better and eaiser if you define (if you already haven't) and use a navigation property:
class Scheme
{
// ...
public ICollection<SchemeColour> Colours { get; set; }
}
and then simply
return DbContext.Schemes
.Select(s => new Overlay.ReportColourScheme
{
ID = s.SchemeID,
Label = s.Label,
Colours = s.Colours
.Select(sc => new Overlay.ReportColour
{
ID = sc.ColourID,
Value = sc.Colour,
Result = sc.Result,
})
.ToList()
})
.ToArray();

C# + EntityFramework: Convert multiple group by query to nested JSON

I made the following linq statement
C#
var list = from row in repository.GetAllEntities()
group row by new { row.RegionString, row.SubRegionString, row.CountryString } into g
select new { g.Key.RegionString, g.Key.SubRegionString, g.Key.CountryString, Count = g.Count() };
return Json(list, JsonRequestBehavior.AllowGet);
That returns
[
{
"RegionString":"Americas",
"SubRegionString":"",
"CountryString":"",
"Count":2
},
{
"RegionString":"Americas",
"SubRegionString":"NorthAmerica",
"CountryString":"Canada",
"Count":5
},
{
"RegionString":"Americas",
"SubRegionString":"NorthAmerica",
"CountryString":"US",
"Count":3
},
{
"RegionString":"Americas",
"SubRegionString":"SouthAmerica",
"CountryString":"Chile",
"Count":3
},
{
"RegionString":"EMEA",
"SubRegionString":"AsiaPacific",
"CountryString":"Australia",
"Count":2
},
{
"RegionString":"EMEA",
"SubRegionString":"AsiaPacific",
"CountryString":"Japan",
"Count":1
},
{
"RegionString":"EMEA",
"SubRegionString":"SouthernEurope",
"CountryString":"Turkey",
"Count":1
},
{
"RegionString":"EMEA",
"SubRegionString":"WesternEurope",
"CountryString":"",
"Count":1
}
]
But I am trying to make it into this format
[{
name: "Americas",
children: [
{
name: "NorthAmerica",
children: [{ "name": "Canada", "count": 5 },
{ "name": "US", "count": 3 }]
},
{
name: "SouthAmerica",
children: [{ "name": "Chile", "count": 1 }]
},
],
},
{
name: "EMA",
children: [
{
name: "AsiaPacific",
children: [{ "name": "Australia", "count": 2 },
{ "name": "Japan", "count": 1 }]
},
{
name: "SouthernEurope",
children: [{ "name": "Turkey", "count": 1 }]
},
],
}]
How could I modify the statement to get the result I'm looking for? Non linq answers are acceptable too.
EDIT: Region is the parent of SubRegion, SubRegion is the parent of Country and Count is the unique number of items that belongs to Country
Here is the linq query you want (I've removed the -String postfix for readability):
var list =
from entity in repository.GetAllEntities()
group entity by entity.Region into regions
let childrenOfRegions =
from region in regions
group region by region.SubRegion into subregions
let countriesOfSubRegions =
from subregion in subregions
group subregion by subregion.Country into countries
select new { Name = countries.Key }
select new { Name = subregions.Key, Children = countriesOfSubRegions }
select new { Name = regions.Key, Children = childrenOfRegions };
There is no Count in this query, since I don't really understand what you are counting.
What I'm doing here is grouping the rows into Regions and in the last line you can see the
select new { Name = regions.Key, ... } part where I'm returning the Regions.
To get the children, you need to group the Regions into SubRegions (the same way with Regions). You repeat this all the way down to the Countries and you're done.

Categories

Resources