I want to group an aggregate by day (not dayOfMonth or Year, just absolute day). Therefore I want to use the $dateToString operator as shown here: $dateToString: { format: "%Y-%m-%d", date: "$date" }. Is there a way to use an expression for this like:
var groups = await collection.Aggregate()
.Match(job => /*...*/)
.Group(job => job.Created.ToString(),
group => /*...*/)
.ToListAsync();
I get this error:
ToString of type System.DateTime is not supported in the expression tree {document}{created}.ToString...
With the help of #Blakes Sevens comment, I solved the original problem with another grouping key.
var groups = await collection.Aggregate()
.Match(job => /*...*/)
.Group(job => new
{
Year = job.Created.Year,
Month = job.Created.Month,
Day = job.Created.Day
},
group => new { Key = group.Key, Value = group.Count() })
.ToListAsync();
Edit
To support other periods than day, I had to use a BsonDocument.
var groupBy = new BsonDocument
{
{
"_id", new BsonDocument
{
{ "$add", new BsonArray
{
new BsonDocument
{
{ "$subtract", new BsonArray
{
new BsonDocument { { "$subtract", new BsonArray
{
"$created",
new DateTime(0) }
}
new BsonDocument { { "$mod", new BsonArray
{
new BsonDocument
{
{ "$subtract", new BsonArray
{
"$created",
new DateTime(0)
}
}
},
msPerPeriod
}
} }
}
}
},
new DateTime(0)
}
}
}
},
{ "count", new BsonDocument("$sum", 1) } };
var groups = await collection.Aggregate()
.Match(job => job.Created >= since && regex.IsMatch(job.Definition))
.Group(groupBy)
.Sort(Builders<BsonDocument>.Sort.Ascending(doc => doc["_id"]))
.ToListAsync();
See this answer: https://stackoverflow.com/a/32137234/498298
Related
FilterDefinition<DM.Content> filterDefinition = Builders<DM.Content>.Filter.Empty;
filterDefinition &= Builders<DM.Content>.Filter.Eq(x => x.IsDeleted, false);
if (contentTypeId > 0)
{
if (contentTypeId == 4)// Photo Video recipes
{
filterDefinition &= Builders<DM.Content>.Filter.In(x => x.ContentTypeId, new List<int>() { 1, 2 });// Video Photo recipes Recipes
}
else
{
filterDefinition &= Builders<DM.Content>.Filter.Eq(x => x.ContentTypeId, contentTypeId);
}
}
// rating
if (!string.IsNullOrEmpty(rating))
{
//filterDefinition &= Builders<DM.Content>.Filter.Gte(x => x.ContentAverageRating, rating);
filterDefinition &= new BsonDocument("$expr", new BsonDocument("$gte",
new BsonArray {
new BsonDocument("$toDouble", "$ContentAverageRating"),
Convert.ToDouble(rating)
}));
}
// cookTime
if (!string.IsNullOrEmpty(cookTime))
{
filterDefinition &= new BsonDocument("$expr", new BsonDocument("$lte",
new BsonArray {
new BsonDocument("$toDouble", "$ContentTime"),
Convert.ToDouble(cookTime)
}));
}
SortDefinition<DM.Content> sortDefinition = Builders<DM.Content>.Sort.Descending(x => x.UpdatedDate);
var results = await contentDocument.AggregateByPage(filterDefinition, sortDefinition, pageIndex, pageSize);
return new DM.CustomModels.ContentSearchResult() { ContentItems = results.data.ToList(), TotalPages = results.totalPages };
public static async Task<(int totalPages, IReadOnlyList<TDocument> data)> AggregateByPage<TDocument>(
this IMongoCollection<TDocument> collection,
FilterDefinition<TDocument> filterDefinition,
SortDefinition<TDocument> sortDefinition,
int page,
int pageSize)
{
var countFacet = AggregateFacet.Create("count",
PipelineDefinition<TDocument, AggregateCountResult>.Create(new[]
{
PipelineStageDefinitionBuilder.Count<TDocument>()
}));
var dataFacet = AggregateFacet.Create("data",
PipelineDefinition<TDocument, TDocument>.Create(new[]
{
PipelineStageDefinitionBuilder.Sort(sortDefinition),
PipelineStageDefinitionBuilder.Skip<TDocument>((page - 1) * pageSize),
PipelineStageDefinitionBuilder.Limit<TDocument>(pageSize),
}));
var aggregation = await collection.Aggregate()
.Match(filterDefinition)
.Facet(countFacet, dataFacet)
.ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()?
.FirstOrDefault()?
.Count ?? 0;
var totalPages = (int)Math.Ceiling((double)count / pageSize);
var data = aggregation.First()
.Facets.First(x => x.Name == "data")
.Output<TDocument>();
return (totalPages, data);
}
I am trying to execute the above query to check ContentAverageRating, ContentTime with certain conditions. But throwing an exception:
Command aggregate failed: An object representing an expression must have exactly one field: { $gte: [ { $toDouble: "$ContentAverageRating" }, 3.0 ], $lte: [ { $toDouble: "$ContentTime" }, 15.0 ] }.
Can anyone let me know what is wrong with the above query?
Issue & Concern
From what I suspect, MongoDB .Net driver had placed both $gte and $lte under the same $expr value, which make the whole BsonDocument failed.
Expected generated Bson Document
{
$and: [
{ $expr: { $gte: [/* Match Condition */] } },
{ $expr: { $lte: [/* Match Condition */] } }
]
}
Actual generated Bson Doccument
{
$and: [
{ $expr: { $gte: [/* Match Condition */], $lte: [/* Match Condition */] } }
]
}
Solution
After some trial and error, I suggest that remove the BsonDocument with $expr from both $lte and $gte. Then you create another FilterDefinition, rootFilterDefinitionto place $expr at top level, then append the filterDefinition as below:
filterDefinition &=
new BsonDocument("$gte",
new BsonArray {
new BsonDocument("$toDouble", "$ContentAverageRating"),
Convert.ToDouble(rating)
}
);
filterDefinition &=
new BsonDocument("$lte",
new BsonArray {
new BsonDocument("$toDecimal", "$ContentTime"),
Decimal.Parse(cookTime)
}
);
FilterDefinition<BsonDocument> rootFilterDefinition = new BsonDocument("$expr",
filterDefinition.ToBsonDocument());
var results = await contentDocument.AggregateByPage(rootFilterDefinition , sortDefinition, pageIndex, pageSize);
Equivalent to this MongoDB query
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{
$gte: [
{
$toDouble: "$ContentAverageRating"
},
3.0
]
},
{
$lte: [
{
$toDouble: "$ContentTime"
},
15.0
]
}
]
}
}
}
])
Sample Mongo Playground
****************************EDITED**********************************************
I have the following code that does the following:
Group alerts with same alert type ID together
sort entire groups in a custom order specified by AlertSorterUtil
within a group sort by severity in a specific order.
I am having trouble with 3. Within a group, the elements(alerts) have to be sorted by severity which is an integer. In the order of severity's 1,2,3,4,5,0.
var severityOrder = new[] { 1 ,2,3,4,5,0 };
// All Alerts
var alerts = new BPAlerts
{
AllAlerts = intakeAlerts.Select(
alert => new BPAlert
{
AlertTypeId = alert.AlertTypeId ?? 8100,
IsOverview = alert.IsOverviewAlert.GetValueOrDefault(),
Text = alert.AlertText,
Title = alert.AlertTitle,
Type = alert.AlertTypeId == 8106 ? "warning" : "report",
Severity = alert.AlertSeverity.GetValueOrDefault(),
Position = alert.Position.GetValueOrDefault()
})
.GroupBy(a => a.AlertTypeId)
.OrderBy(g => AlertSorterUtil.SortByAlertTypeId(g.Key))
.Select(g => g.OrderBy(a => a.Severity))
//list of lists (groups) need to be converted to a single list
.SelectMany(g => g)
.ToList()
};
if (!intakeTexts.IsNullOrEmpty())
{
foreach (BusinessReportCustomText text in intakeTexts)
{
var alert = new BPAlert
{
AlertTypeId = text.CustomTextSectionId,
IsOverview = false,
Text = text.CustomText,
Title = Utils.CustomTextTitleGenerator.getTitle(text.CustomTextSectionId)
};
alerts.AllAlerts.Add(alert);
}
}
alerts.TotalAlertCount = intakeAlerts.Count;
return alerts;
}
When you are sorting the groups with this lambda:
g => g.OrderBy(a => a.Severity)
you need to modify the return value of the lambda to produce the order needed. Using your provided
var severityOrder = new[] { 1 ,2,3,4,5,0 };
you could use
g => g.OrderBy(a => Array.IndexOf(severityOrder, a.Severity))
Alternatively, you can mathematically transform Severity into severityOrder:
g => g.OrderBy(a => (a.Severity+5) % 6)
Finally, you could preserve the flexibility of the first approach but avoid the search overhead of Array.IndexOf by creating a mapping:
var severitySortMap = new[] { 6, 1,2,3,4,5 };
g => g.OrderBy(a => severitySortMap[a.Severity])
so using Thenby twide does not disrupt the order in any way. This solved my problem:
var alerts = new BPAlerts
{
AllAlerts = intakeAlerts.Select(
alert => new BPAlert
{
AlertTypeId = alert.AlertTypeId ?? 8100,
IsOverview = alert.IsOverviewAlert.GetValueOrDefault(),
Text = alert.AlertText,
Title = alert.AlertTitle,
Type = alert.AlertTypeId == 8106 ? "warning" : "report",
Severity = alert.AlertSeverity.GetValueOrDefault(),
Position = alert.Position.GetValueOrDefault()
})
.OrderBy(x => AlertSorterUtil.SortByAlertTypeId(x.AlertTypeId))
.ThenBy(y => AlertSorterUtil.SortAlertBySeverity(y.Severity))
.ThenBy(z => z.Text)
.ToList()
};
I'm trying to get my code to match what the following code does:
var data = new Dictionary<string, object>() {
{ "state", new string[] { "Ohio", "Ohio", "Ohio", "Nevada", "Nevada" } },
{ "year", new int[] { 2000, 2001, 2002, 2001, 2002 } },
{ "pop", new double[] { 1.5, 1.7, 3.6, 2.4, 2.9 } }
};
My current code:
var data = new Dictionary<string, object>() {
{ "targetValue", calc.ListCalculationData.Select(i => i.MRTargetValue).ToArray<decimal>() },
{ "ema12", calc.ListCalculationData.Select(i => i.Ema12).ToArray<decimal>() },
{ "ema26", calc.ListCalculationData.Select(i => i.Ema26).ToArray<decimal>() },
{ "ema", calc.ListCalculationData.Select(i => i.Ema).ToArray<decimal>() }
};
var df1 = DataFrame.FromColumns(data);
var model2 = new LinearRegressionModel(df1, "targetValue ~ ema12 + ema26 + ema"); <= This line gives me the error
I'm getting the following error from the api that I'm using which is the extreme optimization C# api and here is the reference url: http://www.extremeoptimization.com/QuickStart/CSharp/DataFrames.aspx
The variable is not of a kind that is allowed in the group.
I do not see any documentation or examples using the decimal type, but I cannot verify since I do not have access to the library.
There were some examples using the double type. If precision of 15-16 digits is acceptable, that could be an option. Try defining your data variable like this, casting to a double in the Select, then converting to an array:
var data = new Dictionary<string, object>() {
{ "targetValue", calc.ListCalculationData.Select(i => (double)i.MRTargetValue).ToArray() },
{ "ema12", calc.ListCalculationData.Select(i => (double)i.Ema12).ToArray() },
{ "ema26", calc.ListCalculationData.Select(i => (double)i.Ema26).ToArray() },
{ "ema", calc.ListCalculationData.Select(i => (double)i.Ema).ToArray() }
};
I have this structure of class
Flight
..OriginDestinations
....FlightSegments
My question is prett easy, what can be equivalent in lambda expression:
var result1 = (
from sf in selectedFlights
from odo in sf.OriginDestinationOptions
from fs in odo.FlightSegments
select new FlightNumberAndClass {
FlightNumber = fs.FlightNumber,
FlightClass = fs.FlightClass
});
where result1 is type of IEnumerable<FlightNumberAndClass>
I tried this but:
var result2 =
selectedFlights.Select(
x => x.OriginDestinationOptions.Select(
y =>
y.FlightSegments.Select(
z => new FlightNumberAndClass {
FlightNumber = z.FlightNumber,
FlightClass = z.FlightClass
}
)
)
);
it gives me result2 is type of "something like" System.Linq.Enumerable.WhereSelectListIterator<IEnumerable<IEnumerable<FlightNumberAndClass>>>
SelectMany projects each element of a sequence to an IEnumerable<T> and flattens the resulting sequences into one sequence.
IEnumerable<FlightNumberAndClass> result2 = selectedFlights
.SelectMany(sf => sf.OriginDestinationOptions
.SelectMany(odo => odo.FlightSegments
.Select(fs => new FlightNumberAndClass
{
FlightNumber = fs.FlightNumber,
FlightClass = fs.FlightClass
}
)
)
);
In situation like this you can use SelectMany like this example:
List<List<List<string>>> list1 = new List<List<List<string>>>
{
new List<List<string>> {
new List<string>{"a","b","c","d","e"},
new List<string>{"a","b","c","d","e"},
new List<string>{"a","b","c","d","e"}
},
new List<List<string>> {
new List<string>{"a","b","c","d","e"},
new List<string>{"a","b","c","d","e"},
new List<string>{"a","b","c","d","e"}
},
new List<List<string>> {
new List<string>{"a","b","c","d","e"},
new List<string>{"a","b","c","d","e"},
new List<string>{"a","b","c","d","e"}
}
};
var result = list1.SelectMany(a => a.SelectMany(b => b.Select(c => c))).ToList();
I am trying to show a statistics count, per month, per manufacturer from a MongoDB collection using Linq.I would like to see my data as:
[{
Manufacturer: '',
Statistics: [{
Month: '',
Counts: 0
}]
}]
So I am trying the following linq query:
var result = _statisticsRepository
.All()
.GroupBy(g => g.ManufacturerId)
.Select(s => new
{
Manufacturer = s.Key,
Statistics = s
.GroupBy(a => a.Values["DATA_MONTH"])
.Select(a => new
{
Month = a.Key,
Counts = a.Sum(d => d.Count)
})
});
When I try this code I get this error:
NotSupportedException: The method GroupBy is not supported in the expression tree: {document}.GroupBy(a => a.Values.get_Item("DATA_MONTH")).
Am I doing this the wrong way or is this a limitation I have in the C# driver? Or is there another way to do this?
You could use the aggregation framework instead LINQ to get the data that you need:
var group1 = new BsonDocument
{
{ "_id", new BsonDocument{{"ManufacturerId", "$ManufacturerId"},{"DATA_MONTH", "$DATA_MONTH"} }},
{ "Count", new BsonDocument
{
{
"$sum", $Count }
} }
};
var group2 = new BsonDocument
{
{ "_id", "$_id.ManufacturerId" },
{ "Statistics ", new BsonDocument { { "$addToSet",new BsonDocument{{"Month", "$_id.DATA_MONTH"},{"Count","$Count"} } } }},
};
var result = database.GetCollection<BsonDocument>("Manufacturers").Aggregate()
.Group(group1)
.Group(group2)
.ToList();