AggregationContainer vs. AggregationDescriptor - c#

I am trying to send an array of ranges to an Aggregation Descriptor, but the Lambda expression expects a comma delimited expresions
.Aggregations(agg =>
{
AggregationDescriptor ag = agg.Terms("objectTypes", ot => ot.Field("doc.objectType"));
if (!parameters.ContainsKey("userID"))
ag = ag.Terms("users", ot => ot.Field("doc.entryUserID"));//.Field("doc.sourceUserID")))
ag.Terms("commentTypes", ot => ot.Field("doc.commentType"));
if (!parameters.ContainsKey("dateRange"))
{
Dictionary<string, SearchDateRange> dateMap = GetDateRangeMap();
ag.DateRange("dates", dr => dr.Field("doc.date").Format("yyyy-MM-dd")
.Ranges(r1 => r1.Key("Today").From(dateMap["Today"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r2 => r2.Key("SinceWednesday").From(dateMap["Today"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r3 => r3.Key("ThisYear").From(dateMap["ThisYear"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r3 => r3.Key("Last2Years").From(dateMap["Last2Years"].startDate.Value.ToString("yyyy-MM-dd")).To("now"),
r4 => r4.Key("Last3Years").From(dateMap["Last3Years"].startDate.Value.ToString("yyyy-MM-dd")).To("now")
));
}
The above code works.
Below I would like to use the Range[] array and pass it to the aggregate discriptor but I can't, but I can create an AggregationContainer with the range array. How do I marry these two pieces together?
if (!parameters.ContainsKey("revenueRange") && docTypes.Contains(CouchbaseDocumentType.Company))
{
Dictionary<string, SearchNumberRange> numMap = GetMoneyRangeMap();
Range<double>[] ranges = numMap.Select(m =>
{
var r = new Range<double>().Key(m.Key);
if (m.Value.low.HasValue) r.From(m.Value.low.Value);
if (m.Value.high.HasValue) r.To(m.Value.high.Value);
return r;
}).ToArray();
AggregationContainer agr = new AggregationContainer
{
Range = new RangeAggregator { Field = "doc.lastFinancial.revenueUSD", Ranges = ranges }
};
}
return ag;
}
)

I created simple example to show you how you can achieve this.
var funcs = new List<Func<Range<double>, Range<double>>>();
funcs.Add(range => new Range<double>().From(1).To(2));
funcs.Add(range => new Range<double>().From(3).To(4));
var searchResponse = client.Search<Document>(
s => s.Aggregations(agg => agg.Range("range", descriptor => descriptor.Field(f => f.Number).Ranges(funcs.ToArray()))));
Document class:
public class Document
{
public int Id { get; set; }
public double Number { get; set; }
}
Hope you won't have problem with putting it into your context.

Related

Returning a LINQ database query from a Method

Hello everyone I have this query I am performing in multiple places. Instead of retyping the query over and over, I would like to be able to call a method that returns the query. I am not sure what to put as the return type for the method or if this is even possible to do. I use the query to write a csv file of the information, and I use the query to add items to my observable collection that is bound to a list view.
using (ProjectTrackingDBEntities context = new ProjectTrackingDBEntities())
{
var result = context.TimeEntries.Where(Entry => Entry.Date >= FilterProjectAfterDate
&& Entry.Date <= FilterProjectBeforerDate
&& (FilterProjectName != null ? Entry.ProjectName.Contains(FilterProjectName) : true))
.GroupBy(m => new { m.ProjectName, m.Phase })
.Join(context.Projects, m => new { m.Key.ProjectName, m.Key.Phase }, w => new { w.ProjectName, w.Phase }, (m, w) => new { te = m, proj = w })
.Select(m => new
{
Name = m.te.Key.ProjectName,
Phase = m.te.Key.Phase,
TimeWorked = m.te.Sum(w => w.TimeWorked),
ProposedCompletionDate = m.proj.ProposedCompletionDate,
ActualCompletionDate = m.proj.ActualCompletionDate,
Active = m.proj.Active,
StartDate = m.proj.StartDate,
Description = m.proj.Description,
EstimatedHours = m.proj.EstimatedHours
});
}
I am able to do both right now by retyping the query and performing the subsequent foreach() loops on the data. I would rather be able to do something like:
var ReturnedQuery = GetProjectsQuery();
foreach(var item in ReturnedQuery)
{
//do stuff
}
Any help would be appreciated.
You want to return IQueryable<T> with a known model that represents what it is you are returning. You should not return an anonymous type. Also you want to pass in the DbContext so it can be disposed of by the caller and not in the method otherwise you will receive an exception that the DbContext has been disposed of.
For example:
public IQueryable<ProjectModel> GetProjectQuery(ProjectTrackingDBEntities context) {
return context.TimeEntries.Where(Entry => Entry.Date >= FilterProjectAfterDate
&& Entry.Date <= FilterProjectBeforerDate
&& (FilterProjectName != null ? Entry.ProjectName.Contains(FilterProjectName) : true))
.GroupBy(m => new { m.ProjectName, m.Phase })
.Join(context.Projects, m => new { m.Key.ProjectName, m.Key.Phase }, w => new { w.ProjectName, w.Phase }, (m, w) => new { te = m, proj = w })
.Select(m => new ProjectModel
{
Name = m.te.Key.ProjectName,
Phase = m.te.Key.Phase,
TimeWorked = m.te.Sum(w => w.TimeWorked),
ProposedCompletionDate = m.proj.ProposedCompletionDate,
ActualCompletionDate = m.proj.ActualCompletionDate,
Active = m.proj.Active,
StartDate = m.proj.StartDate,
Description = m.proj.Description,
EstimatedHours = m.proj.EstimatedHours
});
}
ProjectModel.cs
public class ProjectModel {
public string Name {get;set;}
public string Phase {get;set;}
// rest of properties
}
Calling code
using (ProjectTrackingDBEntities context = new ProjectTrackingDBEntities())
{
var ReturnedQuery = GetProjectsQuery(context);
foreach(var item in ReturnedQuery)
{
//do stuff
}
}
It is easy to return the enumerator, but you can't return an enumerator for an anonymous type, unfortunately. Probably the easiest path forward for you would be to return enumerator over the full row object, like this:
public IEnumerable<TimeEntries> GetTimeEntries()
{
using (ProjectTrackingDBEntities context = new ProjectTrackingDBEntities())
{
return context.TimeEntries
.Where
(
Entry =>
Entry.Date >= FilterProjectAfterDate &&
Entry.Date <= FilterProjectBeforerDate &&
(FilterProjectName != null ? Entry.ProjectName.Contains(FilterProjectName) : true)
)
.GroupBy(m => new { m.ProjectName, m.Phase })
.Join
(
context.Projects,
m => new { m.Key.ProjectName, m.Key.Phase },
w => new { w.ProjectName, w.Phase },
(m, w) => new { te = m, proj = w }
);
}
)
}
And use it like this:
var query = GetTimeEntries();
foreach (var row in query.Select( m => new { Name = row.te.Key.ProjectName })
{
Console.WriteLine(row.Name);
}

Query CosmosDB document using C#

I have documents stored in cosmos db, I have multiple documents for a same "stationkey"(partition key), in this example stationkey "ABC" has more than one documents with "yymm" has "2018-02" & "2018-01" e.t.c,
query that i am trying is get all "avg" & "dd" fields along with "yymm" for the given stationkey and yymm filter combination
I am trying to query using C#, I am trying to get "avg", "dd" & "yymm" fields from "data" array, the query that I have written is giving entire "data" array.
var weatherQuery = this.docClient.CreateDocumentQuery<WeatherStation>(docUri, queryOptions)
.Where(wq => wq.stationName == stationKey && lstYearMonthFilter.Contains(wq.yearMonth))
.Select(s => s.data);
what is the best way to query specific fields in from a document array?
So you got the data in s => s.data. To get only the avg from the array you have to do another projection as following:
.Select (s => s.data.Select ( a => a.avg ))
Modifying my answer as you say you don't find 'Select' on 'data'.
Define a class MyDocument as such:
public class Datum
{
[JsonProperty("dd")]
public string dd;
[JsonProperty("max")]
public int max;
[JsonProperty("min")]
public int min;
[JsonProperty("avg")]
public int avg;
}
public class MyDocument : Document
{
[JsonProperty("id")]
public string id;
[JsonProperty("data")]
public Datum[] data;
}
modify your code accordingly
IDocumentQuery<MyDocument> query = client.CreateDocumentQuery<MyDocument>(UriFactory.CreateDocumentCollectionUri(_database, _collection),
new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true, MaxDegreeOfParallelism = 199, MaxBufferedItemCount = 100000})
.Where(predicate)
.AsDocumentQuery();
while (query.HasMoreResults)
{
FeedResponse<MyDocument> feedResponse = await query.ExecuteNextAsync<MyDocument>();
Console.WriteLine (feedResponse.Select(x => x.data.Select(y => y.avg)));
}
HTH
You can select only specific fields from the array items using a double-nested anonymous class - see the altered SelectMany below. This will return yymm with every Datum, so may not be as efficient as just selecting the entire array - definitely measure the RU/s in both cases.
var weatherQuery = this.docClient.CreateDocumentQuery<WeatherStation>(docUri, queryOptions)
.Where(wq => wq.stationName == stationKey && lstYearMonthFilter.Contains(wq.yearMonth))
.SelectMany(x => x.data.Select(y => new { x.yymm, data = new[] { new { y.dd, y.avg } } }))
.AsDocumentQuery();
var results = new List<WeatherStation>();
while (weatherQuery.HasMoreResults)
{
results.AddRange(await weatherQuery.ExecuteNextAsync<WeatherStation>());
}
var groupedResults = results
.GroupBy(x => x.yymm)
.Select(x => new { x.First().yymm, data = x.SelectMany(y => y.data).ToArray() })
.Select(x => new WeatherStation() { yymm = x.yymm, data = x.data });

Is there a way to simplify this with a loop or linq statement?

I'm trying to find out if there is a way to create a loop for my example code below
// the objects below create a list of decimals
var ema12 = calc.ListCalculationData.Select(i => (double)i.Ema12);
var ema26 = calc.ListCalculationData.Select(i => (double)i.Ema26);
var ema = calc.ListCalculationData.Select(i => (double)i.Ema);
var adl = calc.ListCalculationData.Select(i => (double)i.ADL);
var r1 = GoodnessOfFit.RSquared(ema12);
var r2 = GoodnessOfFit.RSquared(ema26);
var r3 = GoodnessOfFit.RSquared(ema);
var r4 = GoodnessOfFit.RSquared(adl);
I'm trying to get something similar to the below pseudo code. Please keep in mind that each var item is a list of decimals
foreach (var item in calc.ListCalculationData.AsEnumerable())
{
var item2 = calc.ListCalculationData.Select(i => (double)item);
var r1 = GoodnessOfFit.RSquared(item2);
}
More information:
ListCalculationData is a list of my custom class that I have added below. What I'm trying to do is cycle through each variable in that class and perform a select query to perform the goodness of fit rsquared calculation on the list of decimals that the select query returns so it simplifies my code and makes it similar to my pseudo code
public class CalculationData
{
public decimal Ema { get; set; }
public decimal Ema12 { get; set; }
public decimal Ema26 { get; set; }
public decimal ADL { get; set; }
}
Update: I tried this for a local function and it fails with ; expected and invalid {
double r(Func<CalculationData, double> f) =>
{ GoodnessOfFit.RSquared(calc.ListCalculationData.Select(f), vectorArray) };
Update 2: This is what I have my current code set to because of the recommendations but obviously this doesn't work because it says that the name i doesn't exist in this context at this section: nameof(i.Ema12) and also because I'm using mostly pseudo code
MultipleRegressionInfo rn(Func<CalculationData, double> f, string name, int days)
{
MultipleRegressionInfo mrInfo = new MultipleRegressionInfo
{
RSquaredValue = GoodnessOfFit.RSquared(calc.ListCalculationData.Select(f), vectorArray),
ListValues = (List<double>)calc.ListCalculationData.Select(f).ToList(),
ValueName = name,
Days = days
};
listMRInfo.Add(mrInfo);
return mrInfo;
};
MultipleRegressionInfo rnList(Func<CalculationData, List<decimal>> f, string name, int days)
{
MultipleRegressionInfo mrInfo = new MultipleRegressionInfo
{
RSquaredValue = GoodnessOfFit.RSquared(calc.ListCalculationData.Select(f), vectorArray),
ListValues = (List<double>)calc.ListCalculationData.Select(f).ToList(),
ValueName = name,
Days = days
};
listMRInfo.Add(mrInfo);
return mrInfo;
};
foreach (CalculationData calc in ListCalculationData)
{
foreach (object value in calc)
{
if (value == typeof(decimal))
{
MultipleRegressionInfo r1 = rn(i => (double)i.value, nameof(i.value), 100)
}
else if (value == typeof(List<decimal>)
{
MultipleRegressionInfo r1 = rnList(i => i.value, nameof(i.value), 100)
}
}
}
You can either express each individual field as a lambda that retrieves a particular field value (I think this is better) or as a string or PropertyType value that uses reflection to achieve the same thing.
var getters = new Func<CalculationData, double>[] {
(i) => (double)i.Ema12,
(i) => (double)i.Ema26,
(i) => (double)i.Ema,
(i) => (double)i.ADL,
};
Then it's just a matter of getting each individual IEnumerable<double> sequence and calculating its RSquared value.
var dataseries = getters.Select((func) => calc.ListCalculationData.Select(func));
double[] results = dataseries.Select((data) => GoodnessOfFit.RSquared(data)).ToArray();
From comments:
This is similar to what I'm looking for but I have over 40 variables in my class and I added more information to try to explain what I'm trying to do but I'm trying to prevent the extra 40 lines of code to do something similar to your code
The following should do what you're asking, using reflection.
IEnumerable<Func<CalculationData, double>> getters =
typeof(CalculationData).GetProperties()
.Select<PropertyInfo, Func<CalculationData, double>>(
(PropertyInfo p) => (CalculationData x) => (double)(decimal)p.GetValue(x)
);
Edit: The question was edited again, and I'm no longer certain you need the indirection of the getters. see https://dotnetfiddle.net/Sb65DZ for a barebones example of how I'd write this code.
In Visual Studio 2015+ you can use local functions (not tested):
double r(Func<CalculationData, double> f) =>
GoodnessOfFit.RSquared(calc.ListCalculationData.Select(f));
double r1 = r(i => (double)i.Ema12), r2 = r(i => (double)i.Ema26),
r3 = r(i => (double)i.Ema) , r4 = r(i => (double)i.ADL);
or a bit less efficient lambda:
Func<Func<CalculationData, double>, double> r = f =>
GoodnessOfFit.RSquared(calc.ListCalculationData.Select(f));
double r1 = r(i => (double)i.Ema12), r2 = r(i => (double)i.Ema26),
r3 = r(i => (double)i.Ema) , r4 = r(i => (double)i.ADL);
Another alternative could be converting them to array:
Func<CalculationData, double>[] lambdas = { i => (double)i.Ema12, i => (double)i.Ema26,
i => (double)i.Ema, i => (double)i.ADL };
double[] r = Array.ConvertAll(lambdas, f =>
GoodnessOfFit.RSquared(calc.ListCalculationData.Select(f)));
To find the property with the max rsquared value using reflection, you can try this:
Tuple<double, string> maxR = typeof(CalculationData).GetProperties().Max(p => Tuple.Create(
GoodnessOfFit.RSquared(calc.ListCalculationData.Select(i => Convert.ToDouble(p.GetValue(i)))), p.Name));
double maxRvalue = maxR.Item1;
string maxRname = maxR.Item2;
You could use an extension method to collect a common sequence of operations together.
public static class CalculationDataExtensions
{
public static IEnumerable<double> CalcRSquared(
this IEnumerable<CalculationData> source,
Func<CalculationData, decimal> propertySelector)
{
IEnumerable<double> values = source
.Select(propertySelector)
.Select(x => (double)x);
return GoodnessOfFit.RSquared(values);
}
}
called by
var r1 = calc.ListCalculationData.CalcRSquared(x => x.Ema12);
var r2 = calc.ListCalculationData.CalcRSquared(x => x.Ema26);
var r3 = calc.ListCalculationData.CalcRSquared(x => x.Ema);
var r4 = calc.ListCalculationData.CalcRSquared(x => x.ADL);

How can I select the last digit of an integer in a LINQ .select?

I have this LINQ select:
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created // an int?
});
If I define:
public int? CreatedLast { get; set; }
Then how can I populate that with the last digit of x.Created?
If you are looking for the last digit of the Created property, the use the % operator like this:
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created,
CreatedLast = x.Created % 10
});
The first way to come to mind is to call .ToString().Last():
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created,
CreatedLast = x.Created?.ToString().Last()
});
If you aren't using the latest shiny C#, then null protection can be done with:
var extendedPhrases = phrases
.Select(x => new ExtendedPhrase()
{
Ajlpt = x.Ajlpt,
Bjlpt = x.Bjlpt,
Created = x.Created,
CreatedLast = x.Created.HasValue ? x.Created.ToString().Last() : null
});
And some conversion back to an int? left as an exercise to the reader.

Lambda Expression for Unpivoting DataTable

I am reading data from an Excel sheet in the following format:
I need to store the data in the following way:
I am trying to do it with the help of Linq lambda expression but I think I'm not getting anywhere with this.
DataTable dataTable= ReadExcel();
var dt = dataTable.AsEnumerable();
var resultSet = dt.Where(x => !String.IsNullOrEmpty(x.Field<String>("Project_Code")))
.GroupBy(x =>
new
{
Month = x.Field<String>("Month"),
ProjectCode = x.Field<String>("Project_Code"),
//change designation columns into row data and then group on it
//Designation =
}
);
//.Select(p =>
// new
// {
// Month= p.d
// }
// );`
I would use ToDictionary with a pre-defined set of designation names:
private static readonly string[] designationNames = {"PA","A","SA","M","SM","CON"};
void Function()
{
/* ... */
var resultSet = dt.AsEnumerable().Where(x => !String.IsNullOrEmpty(x.Field<String>("Project_Code")))
.Select(x =>
new
{
Month = x.Field<String>("Month"),
ProjectCode = x.Field<String>("Project_Code"),
Designations = designationNames.ToDictionary(d => d, d => x.Field<int>(d))
}
);
}
This is the normalized version. If you want it flat instead, use:
private static readonly string[] designationNames = {"PA","A","SA","M","SM","CON"};
void Function()
{
/* ... */
var resultSet = dt.AsEnumerable().Where(x => !String.IsNullOrEmpty(x.Field<String>("Project_Code")))
.Select(x =>
designationNames.Select(
d =>
new
{
Month = x.Field<String>("Month"),
ProjectCode = x.Field<String>("Project_Code"),
Designation = d,
Count = x.Field<int>(d)
}
)
).SelectMany(x => x).ToList();
}
If the type is not always int then you might want to use x.Field<String>(d) instead and check for validity.

Categories