Looking way to refactor these two methods into single method - c#

Hi All i am trying to generate the word document with two different tables included in it, for this purpose i have two similar methods where i am passing word document reference and data object and table to the similar methods..
Now i am looking to make single method in generic way so that in different places i can use single method by passing parameters to it
Method 1 :
private static List<OpenXmlElement> RenderExhaustEquipmentTableDataAndNotes(MainDocumentPart mainDocumentPart, List<ProjectObject<ExhaustEquipment>> exhaustEquipment,Table table)
{
HtmlConverter noteConverter = new HtmlConverter(mainDocumentPart);
var equipmentExhaustTypes = new Dictionary<string, List<ProjectObject<ExhaustEquipment>>>();
foreach (var item in exhaustEquipment)
{
string exhaustEquipmentName = item.TargetObject.Name;
if (!equipmentExhaustTypes.ContainsKey(exhaustEquipmentName))
{
equipmentExhaustTypes.Add(exhaustEquipmentName, new List<ProjectObject<ExhaustEquipment>>());
}
equipmentExhaustTypes[exhaustEquipmentName].Add(item);
}
List<OpenXmlElement> notes = new List<OpenXmlElement>();
int noteIndex = 1;
foreach (var exhaustEquipmentItem in equipmentExhaustTypes)
{
List<string> noteIndices = new List<string>();
for (int exhaustEquipmentConditionIndex = 0; exhaustEquipmentConditionIndex < exhaustEquipmentItem.Value.Count; exhaustEquipmentConditionIndex++)
{
var condition = exhaustEquipmentItem.Value[exhaustEquipmentConditionIndex];
var row = new TableRow();
Run superscriptRun = new Run(new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }));
if (exhaustEquipmentConditionIndex == 0)
{
row.Append(RenderOpenXmlElementContentCell(new Paragraph(
new List<Run> {
new Run(new RunProperties(), new Text(exhaustEquipmentItem.Key) { Space = SpaceProcessingModeValues.Preserve }),
superscriptRun
}), 1,
new OpenXmlElement[] {new VerticalMerge { Val = MergedCellValues.Restart },new TableCellMargin {
LeftMargin = new LeftMargin { Width = "120" },
TopMargin = new TopMargin { Width = "80" } }
}));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new OpenXmlElement[] { new VerticalMerge { Val = MergedCellValues.Continue } }));
}
row.Append(RenderTextContentCell(condition.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{condition.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{condition.TargetObject.MaxAirflow:R2}"));
if (condition.TargetObject.NotesHTML?.Count > 0)
{
foreach (var note in condition.TargetObject.NotesHTML)
{
var compositeElements = noteConverter.Parse(note);
var htmlRuns = compositeElements.First().ChildElements.Where(c => c is Run).Cast<Run>().Select(n => n.CloneNode(true));
notes.Add(new Run(htmlRuns));
noteIndices.Add(noteIndex++.ToString(CultureInfo.InvariantCulture));
}
}
if (exhaustEquipmentConditionIndex == exhaustEquipmentItem.Value.Count - 1 && condition.TargetObject.NotesHTML?.Count > 0)
{
superscriptRun.Append(new Text($"({String.Join(',', noteIndices)})") { Space = SpaceProcessingModeValues.Preserve });
}
table.Append(row);
}
}
List<OpenXmlElement> notesSection = new List<OpenXmlElement>();
List<OpenXmlElement> result = RenderNotesArray(table, notes, notesSection);
return result;
}
and I am calling this method like this in below
var table = new Table(RenderTableProperties());
table.Append(new TableRow(
RenderTableHeaderCell("Name"),
RenderTableHeaderCell("Constant Volume"),
RenderTableHeaderCell("Minimum Airflow", units: "(cfm)"),
RenderTableHeaderCell("Wet Bulb Temperature", units: "(cfm)")
));
body.Append(RenderExhaustEquipmentTableDataAndNotes(mainDocumentPart, designHubProject.ExhaustEquipment, table));
Method 2:
private static List<OpenXmlElement> RenderInfiltrationTableData(MainDocumentPart mainDocumentPart, List<ProjectObject<Infiltration>> infiltration,Table table)
{
HtmlConverter noteConverter = new HtmlConverter(mainDocumentPart);
var nameByInflitrationObject = new Dictionary<string, List<ProjectObject<Infiltration>>>();
foreach (var infiltrationData in infiltration)
{
string infiltrationName = infiltrationData.TargetObject.Name;
if (!nameByInflitrationObject.ContainsKey(infiltrationName))
{
nameByInflitrationObject.Add(infiltrationName, new List<ProjectObject<Infiltration>>());
}
nameByInflitrationObject[infiltrationName].Add(infiltrationData);
}
List<OpenXmlElement> notes = new List<OpenXmlElement>();
int noteIndex = 1;
foreach (var inflitrationDataItem in nameByInflitrationObject)
{
List<string> noteIndices = new List<string>();
for (int inflitrationNameIndex = 0; inflitrationNameIndex < inflitrationDataItem.Value.Count; inflitrationNameIndex++)
{
var dataItem = inflitrationDataItem.Value[inflitrationNameIndex];
var row = new TableRow();
Run superscriptRun = new Run(new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }));
if (inflitrationNameIndex == 0)
{
row.Append(RenderOpenXmlElementContentCell(new Paragraph(
new List<Run> {
new Run(new RunProperties(), new Text(inflitrationDataItem.Key) { Space = SpaceProcessingModeValues.Preserve }),superscriptRun
}), 1,
new OpenXmlElement[] {new VerticalMerge { Val = MergedCellValues.Restart },new TableCellMargin {
LeftMargin = new LeftMargin { Width = "120" },
TopMargin = new TopMargin { Width = "80" }}
}));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new OpenXmlElement[] { new VerticalMerge { Val = MergedCellValues.Continue } }));
}
row.Append(RenderTextContentCell($"{dataItem.TargetObject.AirflowScalar.ToString("R2", CultureInfo.CurrentCulture)} cfm {EnumUtils.StringValueOfEnum(dataItem.TargetObject.InfiltrationCalculationType).ToLower(CultureInfo.CurrentCulture)}"));
if (dataItem.TargetObject.NotesHTML?.Count > 0)
{
foreach (var note in dataItem.TargetObject.NotesHTML)
{
var compositeElements = noteConverter.Parse(note);
var htmlRuns = compositeElements.First().ChildElements.Where(c => c is Run).Cast<Run>().Select(n => n.CloneNode(true));
notes.Add(new Run(htmlRuns));
noteIndices.Add(noteIndex++.ToString(CultureInfo.InvariantCulture));
}
}
if (inflitrationNameIndex == inflitrationDataItem.Value.Count - 1 && dataItem.TargetObject.NotesHTML?.Count > 0)
{
superscriptRun.Append(new Text($"({String.Join(',', noteIndices)})") { Space = SpaceProcessingModeValues.Preserve });
}
table.Append(row);
}
}
List<OpenXmlElement> notesSection = new List<OpenXmlElement>();
List<OpenXmlElement> result = RenderNotesArray(table, notes, notesSection);
return result;
}
and then i am calling this method here like as below
var table = new Table(RenderTableProperties());
table.Append(new TableRow(
RenderTableHeaderCell("Type"),
RenderTableHeaderCell("Air Flow")
));
body.Append(RenderInfiltrationTableData(mainDocumentPart, designHubProject.Infiltration, table));
i know these are lots of lines but is there any generic way to use single method out of these two similar methods and i am using .net core
Could any one please suggest any idea or suggestion how can i refactor these two methods into single method that would be very grateful.
many thanks in advance

Before we can create a single function that handles both types, achieving the highly laudable goal of removing gratuitous duplication, we should clean the code up to make it easier to see which parts, if any, are different between the two nearly identical methods. And there is a lot to clean up, even if we only had one function.
In short, your functions are too long, having too much much code in one place, and in fact too much code altogether.
In the following, the original code has been broken down into multiple functions with specific purposes and refactored to remove DIY nonsense in favor of the standard library functions and the removal of pointless code.
static IEnumerable<OpenXmlElement> RenderExhaustEquipmentTableDataAndNotes(MainDocumentPart mainDocumentPart, List<ProjectObject<ExhaustEquipment>> exhaustEquipment, Table table)
{
var equipmentByType = exhaustEquipment.ToLookup(item => item.TargetObject.Name);
List<OpenXmlElement> notes = new List<OpenXmlElement>();
foreach (var items in equipmentByType)
{
Run superscriptRun = CreateSuperScriptRun();
foreach (var item in items)
{
var row = new TableRow();
if (item == items.First())
{
row.Append(CreateFirstRowStartingCell(items.Key, superscriptRun));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new[] {
new VerticalMerge { Val = MergedCellValues.Continue }
}));
}
row.Append(RenderTextContentCell(item.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{item.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{item.TargetObject.MaxAirflow:R2}"));
table.Append(row);
var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);
if (item == items.Last() && itemNotes.Any())
{
UpdateSuperScript(superscriptRun, itemNotes);
}
notes.AddRange(itemNotes);
}
}
List<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
return result;
}
private static Run CreateSuperScriptRun()
{
return new Run(new RunProperties(new VerticalTextAlignment
{
Val = VerticalPositionValues.Superscript
}));
}
private static void UpdateSuperScript(Run superscriptRun, IEnumerable<OpenXmlElement> notes)
{
superscriptRun.Append(new Text($"({string.Join(",", Enumerable.Range(0, notes.Count()))})")
{
Space = SpaceProcessingModeValues.Preserve
});
}
private static IEnumerable<OpenXmlElement> ParseNotes(MainDocumentPart mainDocumentPart, IEnumerable<OpenXmlElement> notes)
{
return notes == null
? Enumerable.Empty<OpenXmlElement>()
: notes.Select(note => new HtmlConverter(mainDocumentPart).Parse(note))
.Select(note => note.First().ChildElements
.OfType<Run>()
.Select(n => n.CloneNode(true))).Select(htmlRuns => new Run(htmlRuns))
.ToList();
}
private OpenXmlElement CreateFirstRowStartingCell(string key, Run superscriptRun)
{
return RenderOpenXmlElementContentCell(
new Paragraph(new List<Run> {
new Run(new RunProperties(), new Text(key) { Space = SpaceProcessingModeValues.Preserve }),
superscriptRun
}),
1,
new OpenXmlElement[] {
new VerticalMerge { Val = MergedCellValues.Restart },
new TableCellMargin { LeftMargin = new LeftMargin { Width = "120" }, TopMargin = new TopMargin { Width = "80" } }
});
}
Now, let's tackle the second function:
static IEnunumerable<OpenXmlElement> RenderInfiltrationTableData(MainDocumentPart mainDocumentPart, IEnunumerable<ProjectObject<Infiltration>> infiltration, Table table)
{
var infiltrationsByType = infiltration.ToLookup(item => item.TargetObject.Name);
List<OpenXmlElement> notes = new List<OpenXmlElement>();
foreach (var inflitrations in infiltrationsByType)
{
Run superscriptRun = CreateSuperScriptRun();
foreach (var item in inflitrations)
{
var row = new TableRow();
if (item == inflitrations.First())
{
row.Append(CreateFirstRowStartingCell(inflitrations.Key, superscriptRun));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new[] {
new VerticalMerge { Val = MergedCellValues.Continue }
}));
}
row.Append(RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}").ToLower());
table.Append(row);
var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);
if (item == inflitrations.Last() && itemNotes.Any())
{
UpdateSuperScript(superscriptRun, itemNotes);
}
notes.AddRange(itemNotes);
}
}
IEnumerable<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
return result;
}
As we have seen, duplication can be massively reduced simply by extracting code into simple helper functions.
This also makes it far easier to see just where the differences are between the two functions.
It is simply a matter of
row.Append(RenderTextContentCell(item.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{item.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{item.TargetObject.MaxAirflow:R2}"));
vs.
row.Append(RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}").ToLower());
To achieve your desired goal of a single function, we can make a generic function, and require that the caller pass in a function that will take care of these differences.
static IEnumerable<OpenXmlElement> RenderTableDataAndNotes<T>(
MainDocumentPart mainDocumentPart,
IEnumerable<ProjectObject<T>> projects,
Table table,
Func<ProjectObject<T>, IEnumerable<OpenXmlElement>> createCells
) where T : ITargetObject
{
var projectsByType = projects.ToLookup(item => item.TargetObject.Name);
List<OpenXmlElement> notes = new List<OpenXmlElement>();
foreach (var items in projectsByType)
{
Run superscriptRun = CreateSuperScriptRun();
foreach (var item in items)
{
var row = new TableRow();
if (item == items.First())
{
row.Append(CreateFirstRowStartingCell(items.Key, superscriptRun));
}
else
{
row.Append(RenderTextContentCell(null, 1, null, null, new[] {
new VerticalMerge { Val = MergedCellValues.Continue }
}));
}
var itemCells = createCells(item);
foreach (var cell in itemCells)
{
row.Append(cell);
}
table.Append(row);
var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);
if (item == items.Last() && itemNotes.Any())
{
UpdateSuperScript(superscriptRun, itemNotes);
}
notes.AddRange(itemNotes);
}
}
IEnumerable<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
return result;
}
Now, when we call it for say some Exhaust Equipment, we do so as follows:
var rendered = RenderTableDataAndNotes(mainDocumentPart, exhaustProjects, table,
exhaust => new[] {
RenderTextContentCell(exhaust.TargetObject.IsConstantVolume ? "Yes" : "No"),
RenderTextContentCell($"{exhaust.TargetObject.MinAirflow:R2}"),
RenderTextContentCell($"{exhaust.TargetObject.MaxAirflow:R2}"),
});
And for infiltration projects, we would do as follows:
var rendered = RenderTableDataAndNotes(
mainDocumentPart,
infiltrationProjects,
table,
infiltration => new[] {
RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}")
.ToLower()
});
The code could still be substantially improved even now. Currently it requires that the various project types implement a common ITargetObject interface declaring the Name property used to group projects by type. If you refactored your code to reduce nesting by hoisting Name to the ProjectObject<T> type, then we could remove the constraint and the otherwise useless requirement that Infiltration and ExhaustEquipment implement the ITargetObject interface.
Note, if you can't change the types, you can adjust the code in a few ways.
For example, you can remove the type constraint on T and build the lookup outside and pass it to the function:
static IEnumerable<OpenXmlElement> RenderTableDataAndNotes<T>(
MainDocumentPart mainDocumentPart,
ILookup<string, ProjectObject<T>> projectsByType,
Table table,
Func<ProjectObject<T>, IEnumerable<OpenXmlElement>> createCells
)
Then you would call it as
var infiltrationProjectsByType = infiltrationProjects.ToLookup(project => project.Name);
var rendered = RenderTableDataAndNotes(
mainDocumentPart,
infiltrationProjectsByType,
table,
infiltration => new[] {
RenderTextContentCell($"{infiltration.TargetObject.AirflowScalar:R2} cfm {infiltration.TargetObject.InfiltrationCalculationType}").ToLower()
}
);

Related

How to add multilevel list in word document using openxml sdk and c#?

I am trying to implement multilevel list in word document programmatically using openxml sdk v2.5 and C#. I have used the following code in order to display headings in multilevel list. I am calling the AddHeading method and passing the respective parameters as shown below. My expected result is to be as below.
1. Parent
1.1. childItem
1.1.1. subchildItem
1.2. childItem
2. Parent
2.1. childItem
3. Parent
But the output i am getting is
1. Parent
1. childItem
1. subchildItem
2. childItem
2. Parent
1. childItem
3. Parent
public static void AddHeading(WordprocessingDocument document, string colorVal, int fontSizeVal, string styleId, string styleName, string headingText, int numLvlRef, int numIdVal)
{
StyleRunProperties styleRunProperties = new StyleRunProperties();
Color color = new Color() { Val = colorVal };
DocumentFormat.OpenXml.Wordprocessing.FontSize fontSize1 = new DocumentFormat.OpenXml.Wordprocessing.FontSize();
fontSize1.Val = new StringValue(fontSizeVal.ToString());
styleRunProperties.Append(color);
styleRunProperties.Append(fontSize1);
AddStyleToDoc(document.MainDocumentPart.Document.MainDocumentPart, styleId, styleName, styleRunProperties, document);
Paragraph p = new Paragraph();
ParagraphProperties pp = new ParagraphProperties();
pp.ParagraphStyleId = new ParagraphStyleId() { Val = styleId };
pp.SpacingBetweenLines = new SpacingBetweenLines() { After = "0" };
ParagraphStyleId paragraphStyleId1 = new ParagraphStyleId() { Val = "ListParagraph" };
NumberingProperties numberingProperties1 = new NumberingProperties();
NumberingLevelReference numberingLevelReference1 = new NumberingLevelReference() { Val = numLvlRef };
NumberingId numberingId1 = new NumberingId() { Val = numIdVal }; //Val is 1, 2, 3 etc based on your numberingid in your numbering element
numberingProperties1.Append(numberingLevelReference1); Indentation indentation1 = new Indentation() { FirstLineChars = 0 };
numberingProperties1.Append(numberingId1);
pp.Append(paragraphStyleId1);
pp.Append(numberingProperties1);
pp.Append(indentation1);
p.Append(pp);
Run r = new Run();
Text t = new Text(headingText) { Space = SpaceProcessingModeValues.Preserve };
r.Append(t);
p.Append(r);
document.MainDocumentPart.Document.Body.Append(p);
}
public static void AddStyleToDoc(MainDocumentPart mainPart, string styleid, string stylename, StyleRunProperties styleRunProperties, WordprocessingDocument document)
{
StyleDefinitionsPart part = mainPart.StyleDefinitionsPart;
if (part == null)
{
part = AddStylesPartToPackage(mainPart);
AddNewStyle(part, styleid, stylename, styleRunProperties);
}
else
{
if (IsStyleIdInDocument(mainPart, styleid) != true)
{
string styleidFromName = GetStyleIdFromStyleName(document, stylename);
if (styleidFromName == null)
{
AddNewStyle(part, styleid, stylename, styleRunProperties);
}
else
styleid = styleidFromName;
}
}
}
public static string GetStyleIdFromStyleName(WordprocessingDocument doc, string styleName)
{
StyleDefinitionsPart stylePart = doc.MainDocumentPart.StyleDefinitionsPart;
string styleId = stylePart.Styles.Descendants<StyleName>()
.Where(s => s.Val.Value.Equals(styleName) &&
(((Style)s.Parent).Type == StyleValues.Paragraph))
.Select(n => ((Style)n.Parent).StyleId).FirstOrDefault();
return styleId;
}
public static StyleDefinitionsPart AddStylesPartToPackage(MainDocumentPart mainPart)
{
StyleDefinitionsPart part;
part = mainPart.AddNewPart<StyleDefinitionsPart>();
DocumentFormat.OpenXml.Wordprocessing.Styles root = new DocumentFormat.OpenXml.Wordprocessing.Styles();
root.Save(part);
return part;
}
public static bool IsStyleIdInDocument(MainDocumentPart mainPart, string styleid)
{
DocumentFormat.OpenXml.Wordprocessing.Styles s = mainPart.StyleDefinitionsPart.Styles;
int n = s.Elements<DocumentFormat.OpenXml.Wordprocessing.Style>().Count();
if (n == 0)
return false;
DocumentFormat.OpenXml.Wordprocessing.Style style = s.Elements<DocumentFormat.OpenXml.Wordprocessing.Style>()
.Where(st => (st.StyleId == styleid) && (st.Type == StyleValues.Paragraph))
.FirstOrDefault();
if (style == null)
return false;
return true;
}
private static void AddNewStyle(StyleDefinitionsPart styleDefinitionsPart, string styleid, string stylename, StyleRunProperties styleRunProperties)
{
DocumentFormat.OpenXml.Wordprocessing.Styles styles = styleDefinitionsPart.Styles;
DocumentFormat.OpenXml.Wordprocessing.Style style = new DocumentFormat.OpenXml.Wordprocessing.Style()
{
Type = StyleValues.Paragraph,
StyleId = styleid,
CustomStyle = false
};
style.Append(new StyleName() { Val = stylename });
style.Append(new BasedOn() { Val = "Normal" });
style.Append(new NextParagraphStyle() { Val = "Normal" });
style.Append(new UIPriority() { Val = 900 });
styles.Append(style);
}
To use multi-level lists in Microsoft Word, you need to ensure that:
you have the desired multi-level list set up correctly in your numbering definitions part (meaning your w:numbering element contains a w:num and corresponding w:abstractNum that specifies the different list levels);
you have one style for each list level that references both the numbering ID specified by the w:num element and the desired list level; and
your paragraphs reference the correct style for the list level.
Please have a look at my answers on how to create multi-level ordered lists with Open XML and display multi-level lists in Word documents using C# and the Open XML SDK for further details and explanations.

Google Reporting API V4 Missing Values

I've been having a problem with Google's analytic reporting api v4. When I make a request, i can get data back, but some dimension and metric values are missing and/or inconsistent.
For example if i wanted the fullRefferer, it would return (not set). Or when i do get values my page views value could be 1312 and my sessions could be 26.
My code for making the request is below:
public GetReportsResponse Get(string viewId, DateTime startDate, DateTime endDate, string nextPageToken = null)
{
try
{
var credential = GetCredential();
using (var svc = new AnalyticsReportingService(
new BaseClientService.Initializer
{
HttpClientInitializer = credential
}))
{
var mets = new List<Metric>
{
new Metric
{
Alias = "Users",
Expression = "ga:users"
},
new Metric
{
Alias = "Bounce Rate",
Expression = "ga:bounceRate"
},
new Metric
{
Alias = "Page Views",
Expression = "ga:pageViews"
},
new Metric()
{
Alias = "Sessions",
Expression = "ga:sessions"
}
};
var dims = new List<Dimension>
{
new Dimension { Name = "ga:date" },
new Dimension { Name = "ga:hour" },
new Dimension { Name = "ga:browser" },
new Dimension { Name = "ga:pagePath" },
new Dimension { Name = "ga:fullReferrer"}
};
var dateRange = new DateRange
{
StartDate = startDate.ToFormattedString(),
EndDate = endDate.ToFormattedString()
};
var reportRequest = new ReportRequest
{
DateRanges = new List<DateRange> { dateRange },
Dimensions = dims,
Metrics = mets,
ViewId = viewId,
PageToken = nextPageToken
};
var getReportsRequest = new GetReportsRequest
{
ReportRequests = new List<ReportRequest> { reportRequest },
};
var batchRequest = svc.Reports.BatchGet(getReportsRequest);
var response = batchRequest.Execute();
return response;
}
}
catch (Exception e)
{
return null;
}
}
And my code for filtering the results is here:
public static List<AnalyticEntry> Filter(Google.Apis.AnalyticsReporting.v4.Data.GetReportsResponse response)
{
if (response == null) return new List<AnalyticEntry>();
List<GoogleDataDto> gData = new List<GoogleDataDto>();
foreach (var report in response.Reports)
{
foreach (var row in report.Data.Rows)
{
GoogleDataDto dto = new GoogleDataDto();
foreach (var metric in row.Metrics)
{
foreach (var value in metric.Values)
{
int index = metric.Values.IndexOf(value);
var metricHeader = report.ColumnHeader.MetricHeader.MetricHeaderEntries[index];
switch (metricHeader.Name)
{
case "Sessions":
dto.Sessions = Convert.ToInt32(value);
break;
case "Bounce Rate":
dto.BounceRate = Convert.ToDecimal(value);
break;
case "Page Views":
dto.PageViews = Convert.ToInt32(value);
break;
case "Users":
dto.Users = Convert.ToInt32(value);
break;
}
}
}
foreach (var dimension in row.Dimensions)
{
int index = row.Dimensions.IndexOf(dimension);
var dimensionName = report.ColumnHeader.Dimensions[index];
switch (dimensionName)
{
case "ga:date":
dto.Date = dimension;
break;
case "ga:hour":
dto.Hour = dimension;
break;
case "ga:browser":
dto.Browser = dimension;
break;
case "ga:pagePath":
dto.PagePath = dimension;
break;
case "ga:source":
dto.Source = dimension;
break;
case "ga:fullRefferer":
dto.Referrer = dimension;
break;
}
}
gData.Add(dto);
}
}
return Combine(gData);
}
private static List<AnalyticEntry> Combine(IReadOnlyCollection<GoogleDataDto> gData)
{
List<AnalyticEntry> outputDtos = new List<AnalyticEntry>();
var dates = gData.GroupBy(d => d.Date.Substring(0,6)).Select(d => d.First()).Select(d => d.Date.Substring(0,6)).ToList();
foreach (var date in dates)
{
var entities = gData.Where(d => d.Date.Contains(date)).ToList();
AnalyticEntry dto = new AnalyticEntry
{
Date = date.ToDate(),
PageViews = 0,
Sessions = 0,
Users = 0,
BounceRate = 0
};
foreach (var entity in entities)
{
dto.BounceRate += entity.BounceRate;
dto.PageViews += entity.PageViews;
dto.Users += entity.Users;
dto.Sessions += entity.Sessions;
}
dto.BounceRate = dto.BounceRate / entities.Count();
var dictionaries = entities.GetDictionaries();
var commonBrowsers = dictionaries[0].GetMostCommon();
var commonTimes = dictionaries[1].GetMostCommon();
var commonPages = dictionaries[2].GetMostCommon();
var commonSources = dictionaries[3].GetMostCommon();
var commonReferrers = dictionaries[4].GetMostCommon();
dto.CommonBrowser = commonBrowsers.Key;
dto.CommonBrowserViews = commonBrowsers.Value;
dto.CommonTimeOfDay = commonTimes.Key.ToInt();
dto.CommonTimeOfDayViews = commonTimes.Value;
dto.CommonPage = commonPages.Key;
dto.CommonPageViews = commonPages.Value;
dto.CommonSource = commonSources.Key;
dto.CommonSourceViews = commonSources.Value;
dto.CommonReferrer = commonReferrers.Key;
dto.CommonReferrerViews = commonReferrers.Value;
outputDtos.Add(dto);
}
return outputDtos;
}
I'm not sure what else to put, please comment for more info :)
Solved!
Originally I was trying to find a 'metric name' based on the location of a value in an array. So using the location I would get the name and set the value.
The problem was the array could have multiple values which were the same.
For example:
var arr = [1,0,3,1,1];
If a value was 1, I was trying to use the location of 1 in the array to get a name.
So if the index of 1 in the array was 0, I would find its name by using that index and finding the name in another array.
For example:
var names = ['a','b','c'];
var values = [1,2,1];
var value = 1;
var index = values.indexOf(value); // which would be 0
SetProperty(
propertyName:names[index], // being a
value: value);
Although its hard to explain I was setting the same value multiple times due to the fact that there were more than one value equal to the same thing in the array.
Here is the answer. Tested and works
public List<GoogleDataDto> Filter(GetReportsResponse response)
{
if (response == null) return null;
List<GoogleDataDto> gData = new List<GoogleDataDto>();
foreach (var report in response.Reports)
{
foreach (var row in report.Data.Rows)
{
GoogleDataDto dto = new GoogleDataDto();
foreach (var metric in row.Metrics)
{
int index = 0; // Index counter, used to get the metric name
foreach (var value in metric.Values)
{
var metricHeader = report.ColumnHeader.MetricHeader.MetricHeaderEntries[index];
//Sets property value based on the metric name
dto.SetMetricValue(metricHeader.Name, value);
index++;
}
}
int dIndex = 0; // Used to get dimension name
foreach (var dimension in row.Dimensions)
{
var dimensionName = report.ColumnHeader.Dimensions[dIndex];
//Sets property value based on dimension name
dto.SetDimensionValue(dimensionName, dimension);
dIndex++;
}
// Will only add the dto to the list if its not a duplicate
if (!gData.IsDuplicate(dto))
gData.Add(dto);
}
}
return gData;
}

Filter products with ElasticSearch concat a lot of filter

I've been trying to filter products with Elasticsearch for a few hours, unfortunately to no avail.
I need to find products that belong to certain categories and at the same time have selected several brands and one size.
Help :(
json screen
querycontainer build method
private QueryContainer CreateOrQueryFromFilter(QueryContainer queryContainer, SortedSet<string> filter, string fieldName)
{
if (filter != null && filter.Count > 0)
{
foreach (var item in filter)
{
queryContainer |= new TermQuery()
{
Name = fieldName + "named_query",
Boost = 1.1,
Field = fieldName,
Value = item
};
}
}
return queryContainer;
}
and search method
public ResultModel SearchRequest(RequestModel r)
{
string key = string.Format("search-{0}-{1}", r.CacheKey + "-" + ProductCatalog.Model.Extension.StringHelper.UrlFriendly(r.SearchText), r.Prefix);
node = new Uri("http://xxxx:9200/");
settings = new ConnectionSettings(node);
settings.DisableDirectStreaming();
settings.DefaultIndex("products");
client = new ElasticClient(settings);
// return AppCache.Get(key, () =>
// {
DateTime start = DateTime.Now;
ResultModel result = new ResultModel(r.Canonical, r.RouteObject);
if (!string.IsNullOrEmpty(r.Prefix))
{
result.Prefix = r.Prefix;
}
QueryContainer c = new QueryContainer();
if (r.CategoryFilterChilds != null && r.CategoryFilterChilds.Count > 0)
{
var a1 = new SortedSet<string>(r.CategoryFilterChilds.Select(a => (string)a.ToString()));
c = CreateOrQueryFromFilter(c, a1, "categories");
}
else
{
if (r.CategoryFilterRoots != null && r.CategoryFilterRoots.Count > 0)
{
var a1 = new SortedSet<string>(r.CategoryFilterRoots.Select(a => (string)a.ToString()));
c = CreateOrQueryFromFilter(c, a1, "categories");
}
else
{
// null
}
}
var filters = new AggregationDictionary();
if (r.IsBrandFilter)
{
c = CreateOrQueryFromFilter(c, r.SelectedBrands, "brands");
}
if (r.IsColorFilter)
{
c = CreateOrQueryFromFilter(c, r.SelectedBrands, "colors");
}
int skip = (r.Page * r.PageSize) - r.PageSize;
ISearchRequest r2 = new SearchRequest("products");
r2.From = 1;
r2.Size = r.PageSize;
r2.Query = c;
string[] Fields = new[] { "brands", "shopId" };
AggregationBase aggregations = null;
foreach (string sField in Fields)
{
var termsAggregation = new TermsAggregation("agg_" + sField)
{
Field = sField,
Size = 120,
Order = new List<TermsOrder> { TermsOrder.TermDescending }
};
if (aggregations == null)
{
aggregations = termsAggregation;
}
else
{
aggregations &= termsAggregation;
}
}
r2.Aggregations = aggregations;
var c2 = client.Search<ShopProductElastic>(r2);
var ShopsBuf = (Nest.BucketAggregate)(c2.Aggregations["agg_brands"]);
var ShopsCount = ShopsBuf.Items.Count();
var results = c2;
result.BrandsRequest = new SortedDictionary<string, int>();
foreach (Nest.KeyedBucket<object> item in ShopsBuf.Items)
{
result.BrandsRequest.Add((string)item.Key, (int)(item.DocCount ?? 0));
}
result.CategorySelected = r.CategoryCurrent;
result.TotalCount = 10;
var costam = results.Documents.ToList();
var targetInstance = Mapper.Map<List<Products>>(costam);
result.Products = targetInstance;
result.Page = r.Page;
result.PageSize = r.PageSize;
result.IsBrandFilter = r.IsBrandFilter;
result.IsColorFilter = r.IsColorFilter;
result.IsPatternFilter = r.IsPatternFilter;
result.IsSeasonFilter = r.IsSeasonFilter;
result.IsShopFilter = r.IsShopFilter;
result.IsSizeFilter = r.IsSizeFilter;
result.IsStyleFilter = r.IsStyleFilter;
result.IsTextileFilter = r.IsTextileFilter;
DateTime stop = DateTime.Now;
result.SearchTime = stop - start;
result.SearchText = r.SearchText;
return result;
// }, TimeSpan.FromHours(8));
}
I have all products that have a given brand or categories. However, i need all products of a selected brand in selected categories

Adding subsequent months on x-axis using Live Charts

private int SumOfNodes()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> SumOfHardware = t.GetHardware().FindAll(x => x.Nodes != 0);
int SumOfNodes = 0;
foreach (Hardware i in SumOfHardware )
{
SumOfNodes += i.Nodes;
}
return SumOfNodes;
}
private int SumOfRepeaters()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> SumOfHardware = t.GetHardware().FindAll(x => x.Repeaters != 0);
int SumOfRepeaters = 0;
foreach (Hardware i in SumOfHardware)
{
SumOfRepeaters += i.Repeaters;
}
return SumOfRepeaters;
}
private int SumOfHubs()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> SumOfHardware = t.GetHardware().FindAll(x => x.Hubs != 0);
int SumOfHubs= 0;
foreach (Hardware i in SumOfHardware)
{
SumOfHubs += i.Hubs;
}
return SumOfHubs;
}
private string Month()
{
DateTime now = DateTime.Now;
string month = DateTime.Now.ToString("MMMM");
return month;
}
private void DisplayData()
{
SeriesCollection = new SeriesCollection
{
new ColumnSeries
{
Title = "Nodes",
Values = new ChartValues<int> { SumOfNodes() }
},
};
SeriesCollection.Add
(
new ColumnSeries
{
Title = "Repeaters",
Values = new ChartValues<int> { SumOfRepeaters() }
}
);
SeriesCollection.Add
(
new ColumnSeries
{
Title = "Hubs",
Values = new ChartValues<int> { SumOfHubs() }
}
);
Labels = new[] { Month() };
Formatter = value => value.ToString("N");
DataContext = this;
}
enter image description here
At this points I've managed to create an app that adds/removes and updates my items on my database. I'm also planning to add some stats (Started off with graph visualisation) but I'm facing an issue.
I want to seperate columns based on months. So for example as seen by the image attached no matter how many items i add , remove or update the total amount for each item is added to Decemeber. But when January comes any newly added modification to the quantity of my items I would like to see adjacent to the Decemeber one.
P.S.: There is alot of code repitition which will accounted for later on.
I've managed to put something together regarding my answer above which may be usefull for someone in the future
What I've done was to
1) Get my data from my db.
2) Find the Month of interest using LINQ queries **
instead of selecting the items (i.e repeaters, nodes)
**
3) By doing so it also accounts for the items I'm interested in during the month of interest.
4) Do an incremental foreach loop as shown in my code above.
5) Change the methodd i want to display (i.e DisplayData to DisplayDecemberData) and use in the same way as above.
6) Create further methods for the subsequent months and just add the additional information in the ChartValues object.
See Below
private int DecemberNodes()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> decembernodes = t.GetHardware().FindAll(x => x.Date.Month==12);
int DecemberSumOfNodes = 0;
foreach (Hardware i in decembernodes)
{
DecemberSumOfNodes += i.Nodes;
}
return DecemberSumOfNodes;
}
private int JanuaryNodes()
{
ManufacturingDataModel MDM = new ManufacturingDataModel();
Test t = new Test(MDM);
List<Hardware> januarynodes = t.GetHardware().FindAll(x => x.Date.Month==01);
int JanuarySumOfNodes = 0;
foreach (Hardware i in januarynodes)
{
JanuarySumOfNodes += i.Nodes;
}
private void DisplayDecemberData()
{
SeriesCollection = new SeriesCollection
{
new ColumnSeries
{
Title = "Nodes",
Values = new ChartValues<int> { DecemberNodes() }
},
};
private void DisplayJanuaryData()
{
SeriesCollection = new SeriesCollection
{
new ColumnSeries
{
Title = "Nodes",
**Values = new ChartValues<int> { DecemberNodes(), JanuaryNodes() }**
},
};
There is some code repetition and I'm pretty sure there is a more code concise way of actually doing it but for now this seems to do the trick. When I simplify the code i will post it.

Refactoring similar methods using generics parameter in c# and linq

I have two methods they are exactly the same except the first parameter.I don't want to repeat the duplicate code. I was wondering how can we refactor the following code using generic parameters.
First method
private Dictionary<List<string>, List<string>> GetFinancialLtmDataSet(List<sp_get_company_balance_sheet_amount_ltm_Result> itemResult, int neededyear)
{
var requestedData =
itemResult.OrderByDescending(x => x.date.Year).Take(neededyear).Select(x => new { date = x.date.Date });
var addFields = new List<string>();
var dataSet = new Dictionary<List<string>, List<string>>();
int counter = 0;
foreach (var itemy in requestedData)
{
var skipvalue = itemResult.Skip(counter);
var columns = skipvalue.OrderBy(x => itemy.date).ToList();
var cc = columns.First();
counter++;
var properties =
cc.GetType()
.GetProperties()
.Select(x => new { Name = x.Name, Value = x.SetMethod, a = x.GetValue(cc, null) })
.ToList();
foreach (var property in properties)
{
addFields.Add(property.Name);
if (property.a != null)
{
dataSet.Add(new List<string> { property.Name }, new List<string> { property.a.ToString() });
}
}
}
return dataSet;
}
Second method
private Dictionary<List<string>, List<string>> GetFinancialQuartelyDataSet(List<sp_get_company_balance_sheet_amount_quaterly_Result> itemResult, int neededyear)
{
var requestedData =
itemResult.OrderByDescending(x => x.date.Year).Take(neededyear).Select(x => new { date = x.date.Date });
var addFields = new List<string>();
var dataSet = new Dictionary<List<string>, List<string>>();
int counter = 0;
foreach (var itemy in requestedData)
{
var skipvalue = itemResult.Skip(counter);
var columns = skipvalue.OrderBy(x => itemy.date).ToList();
var cc = columns.First();
counter++;
var properties =
cc.GetType()
.GetProperties()
.Select(x => new { Name = x.Name, Value = x.SetMethod, a = x.GetValue(cc, null) })
.ToList();
foreach (var property in properties)
{
addFields.Add(property.Name);
if (property.a != null)
{
dataSet.Add(new List<string> { property.Name }, new List<string> { property.a.ToString() });
}
}
}
return dataSet;
}
I have created a following method to make it generic but not been able to get the final implementation any suggestion appreciated.
private List<T> GetFinancialReport<T>(List<T> data, int neededyear)
{
//what should I return from here
return data;
}
and would like to use the above method like this
var balancesheetResult=balancesheet.ToList();
var testData = GetFinancialReport<BalanceSheet_sp>(balancesheetResult, 5);
var cashflowresult=cashflow.ToList();
var testData1 = GetFinancialReport<CahsFlow_sp>(cashflowresult, 10);
From what is shown above the objects (at least the properties involved) match. So you could code against an interface here:
private Dictionary<List<string>, List<string>> GetFinancialReport(List<IBalance>, int neededyear)
{
...
}

Categories