Can I customize a Select in Linq to select into an object? - c#

I am using LINQ to select like this:
double views = Counts.btnCountViewsList
.Select(x => x.Views)
.DefaultIfEmpty(0).Average();
double btnCount = Counts.btnCountViewsList
.Select(x => x.BtnCount)
.DefaultIfEmpty(0).Average();
However I would like to know is this possible to do with one query and select into this object?
public class BtnCountViews
{
public BtnCountViews()
{
}
public int DayOfYear { get; set; }
public int Month { get; set; }
public int Year { get; set; }. // <<<<<<
public double BtnCount { get; set; } // <<<<<<
public double Views { get; set; }
}

It is as simple as this. This is just a guideline. I hope you could adapt it to your own needs.
Counts.btnCountViewsList.Select(x => new BtnCountViews()
{
BtnCount = 0,
DayOfYear = 0,
Month = 0,
Views = 0,
Year = 0
});

Related

Conversion of this SQL Query to LINQ

SELECT
CreationUtcTime, Speed,
CONVERT(varchar, (CreationUtcTime - LAG(CreationUtcTime) OVER (ORDER BY CreationUtcTime)), 108) AS diff
FROM
assetstatusrecords
WHERE
Speed <> 0.00
ORDER BY
CreationUtcTime
I want this SQL query to be converted to LINQ query without using LINQTODB functions and I want exact difference including hours, days, seconds, minutes such that I want to sum the time at later stage.
What I have tried is below:
var records = _context.AssetStatusRecords
.OrderByDescending(s => s.CreationUtcTime)
.Where(s => s.AssetId.Equals(asset.Id)
&& s.CreationUtcTime >= from
&& s.CreationUtcTime <= to
&& s.Speed != 0)
.ToList();
var query = from rec1 in records
from rec2 in records.Where(r => rec1.SequentialId > r.SequentialId).DefaultIfEmpty()
group new { rec1, rec2 } by new { rec1.SequentialId, rec1.CreationUtcTime, rec1.Speed } into g
orderby g.Key.SequentialId
select new
{
g.Key.CreationUtcTime,
g.Key.Speed,
Diff = EntityFunctions.DiffDays(g.Max(p => p.rec2.CreationUtcTime), g.Key.CreationUtcTime)
};
Model class for LINQ
class AssetStatusRecord : Entity
{
protected AssetStatusRecord()
{
}
public AssetStatusRecord(CoordinatesValue coordinates, double speed,
LengthValue distanceTravelled, Guid sensorId, Guid? assetId,
int? heading, Guid readingId, DateTime? sensorDateTime)
{
Coordinates = coordinates;
Speed = speed;
DistanceTravelled = distanceTravelled;
SensorId = sensorId;
AssetId = assetId;
Heading = heading;
ReadingId = readingId;
SensorDateTime = sensorDateTime;
}
public CoordinatesValue Coordinates { get; private set; }
public double Speed { get; private set; }
public LengthValue DistanceTravelled { get; private set; }
public Guid SensorId { get; private set; }
public Guid? AssetId { get; private set; }
public int? Heading { get; private set; }
public Guid ReadingId { get; private set; }
public DateTime? SensorDateTime { get; private set; }
}
And the Entity class is as follows:
public class Entity : IEntity
{
public Entity();
public Guid Id { get; protected set; }
public long SequentialId { get; protected set; }
public DateTime CreationUtcTime { get; protected set; }
public DateTime CreationLocalTime { get; protected set; }
}
And this is the interface IEntity:
public interface IEntity
{
Guid Id { get; }
long SequentialId { get; }
DateTime CreationUtcTime { get; }
}
Try the following query:
var records = _context.AssetStatusRecords
.Where(s => s.AssetId == asset.Id
&& s.CreationUtcTime >= from
&& s.CreationUtcTime <= to
&& s.Speed != 0);
var query =
from current in records
from prev in records
.Where(prev => current.CreationUtcTime <= prev.CreationUtcTime && prev.SequentialId < current.SequentialId)
.OrderByDescending(prev => prev.CreationUtcTime)
.Take(1)
.DefaultIfEmpty()
orderby current.CreationUtcTime
select new
{
current.CreationUtcTime,
current.Speed,
Diff = EntityFunctions.DiffDays(current.CreationUtcTime, prev.CreationUtcTime)
};

Resampling with Deedle duplicate keys

My code below resamples 5-minute interval to 1-day interval for the daily profit stats. The problem is that BacktestResult consists of duplicate CloseDate values, because I'm testing with multiple pairs (TRXUSDT, ETHUSDT and BTCUSDT). dailyProfit returns Series<DateTime, double>, which explains the exception. How can I make it grouped by Pair or something? It works fine when tested with one pair.
// Create series
var series = _backtestResults.ToOrdinalSeries();
// daily_profit = results.resample('1d', on = 'close_date')['profit_percent'].sum()
var dailyProfit = series.ResampleEquivalence(
index => new DateTime(series[index].CloseDate.Year, series[index].CloseDate.Month, series[index].CloseDate.Day, 0, 0, 0, DateTimeKind.Utc),
group => group.SelectValues(g => g.ProfitPercentage).Sum()).DropMissing();
// classes
public class BacktestResult
{
public string Pair { get; set; }
public decimal ProfitPercentage { get; set; }
public decimal ProfitAbs { get; set; }
public decimal OpenRate { get; set; }
public decimal CloseRate { get; set; }
public DateTime OpenDate { get; set; }
public DateTime CloseDate { get; set; }
public decimal OpenFee { get; set; }
public decimal CloseFee { get; set; }
public decimal Amount { get; set; }
public decimal TradeDuration { get; set; }
public SellType SellReason { get; set; }
}
Edit:
Example which takes the JSON data from pastebin:
using Deedle;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Resample
{
class Program
{
public class BacktestResultTest
{
public string Pair { get; set; }
public decimal ProfitPercentage { get; set; }
public decimal ProfitAbs { get; set; }
public decimal OpenRate { get; set; }
public decimal CloseRate { get; set; }
public DateTime OpenDate { get; set; }
public DateTime CloseDate { get; set; }
public decimal OpenFee { get; set; }
public decimal CloseFee { get; set; }
public decimal Amount { get; set; }
public decimal TradeDuration { get; set; }
public bool OpenAtEnd { get; set; }
public int SellReason { get; set; }
}
static void Main(string[] args)
{
// Take JSON data from pastebin
using var webClient = new WebClient();
var json = webClient.DownloadString("https://pastebin.com/raw/Dhp9202f");
// Deserialize the data
var data = JsonConvert.DeserializeObject<List<BacktestResultTest>>(json);
var ts = data.ToOrdinalSeries();
var byDateAndPair = ts.SelectKeys(kvp => Tuple.Create(kvp.Value.Value.CloseDate, kvp.Value.Value.Pair)).SortByKey();
// daily_profit = results.resample('1d', on = 'close_date')['profit_percent'].sum()
var dailyProfit2 = byDateAndPair.ResampleEquivalence(
k => Tuple.Create(new DateTime(k.Item1.Year, k.Item1.Month, k.Item1.Day), k.Item2),
g => g.Select(kvp => kvp.Value.ProfitPercentage).Sum());
// backtest_worst_day = min(daily_profit)
var worstDay2 = dailyProfit2.Min();
// backtest_best_day = max(daily_profit)
var bestDay2 = dailyProfit2.Max();
// winning_days = sum(daily_profit > 0)
var winningDays2 = dailyProfit2.SelectValues(x => x > 0).Sum();
// draw_days = sum(daily_profit == 0)
var drawDays2 = dailyProfit2.SelectValues(x => x == 0).Sum();
// losing_days = sum(daily_profit < 0)
var losingDays2 = dailyProfit2.SelectValues(x => x < 0).Sum();
Console.ReadLine();
}
}
}
You can use a custom data type as a key in Deedle. If you want to be able to use resampling on the series, then this needs to support IComparable. You can either define your own type or use built-in Tuple.
Assuming we have some very basic data:
var ts =
new[] {
KeyValue.Create(new DateTime(2020,1,1), new { Value = 1.0, Kind = "A" }),
KeyValue.Create(new DateTime(2020,1,2), new { Value = 1.0, Kind = "A" }),
KeyValue.Create(new DateTime(2020,1,3), new { Value = 1.0, Kind = "B" }),
KeyValue.Create(new DateTime(2020,1,4), new { Value = 1.0, Kind = "B" }),
}.ToSeries();
The first thing we need to do is to change the key to be the date together with a kind. (In fact, you can get into trouble earlier in your code if you had duplicate dates!)
var byDateAndKind =
ts.SelectKeys(kvp => Tuple.Create(kvp.Key, kvp.Value.Value.Kind)).SortByKey();
Now the key is Tuple<DateTime, string> consisting of the date and the kind. You can now use ResampleEquivalence on this. Here, we use year and kind as the new key and sum values in group:
var aggByYearAndKind =
byDateAndKind.ResampleEquivalence(
(k) => Tuple.Create(k.Item1.Year, k.Item2),
(g) => g.Select(kvp => kvp.Value.Value).Sum());
aggByYearAndKind.Print();
This will print a series that maps 2020, "A" to 2 and also 2020, "B" to 2.
EDIT You are right - this does not seem to work. I was able to get it to work using GroupBy instead of ResampleEquvialence:
var dailyProfit2 =
ts.GroupBy(kvp =>
new { Date = new DateTime(kvp.Value.CloseDate.Year, kvp.Value.CloseDate.Month, kvp.Value.CloseDate.Day), Kind = kvp.Value.Pair })
.SelectValues(g => g.Select(kvp => kvp.Value.ProfitPercentage).Values.Sum());
// backtest_worst_day = min(daily_profit)
var worstDay2 = dailyProfit2.Min();
// backtest_best_day = max(daily_profit)
var bestDay2 = dailyProfit2.Max();
// winning_days = sum(daily_profit > 0)
var winningDays2 = dailyProfit2.Where(x => x.Value > 0).Values.Sum();
// draw_days = sum(daily_profit == 0)
var drawDays2 = dailyProfit2.Where(x => x.Value == 0).Values.Sum();
// losing_days = sum(daily_profit < 0)
var losingDays2 = dailyProfit2.Where(x => x.Value < 0).Values.Sum();

How to prevent items in a list returning "0" when not filled in and they supposed to be "null"?

This was kind of a hard question to ask but this is my problem:
I'm populating a grid with data I obtain from a different class, this class uses a (generic) Model that can represent multiple models:
Model(can represent Vessel or Container):
public class DataGridInstallationRow
{
[Key]
public string Name { get; set; }
//Vessel
public int IMO { get; set; }
public int MMSI { get; set; }
public int EEOI { get; set; }
public int FOC { get; set; }
[Display(Name = "Total Fuel Mass")]
public int TotalFuelMass { get; set; }
[Display(Name = "Average Speed")]
public int AverageSpeed { get; set; }
[Display(Name = "Total Distance Sailed")]
public int TotalDistanceSailed { get; set; }
//Container
[Display(Name = "Generated by Sun")]
public int EnergyGeneratedBySun { get; set; }
[Display(Name = "Generated by Wind")]
public int EnergyGeneratedByWind { get; set; }
[Display(Name = "Generated by Generator")]
public int EnergyGeneratedByGenerator { get; set; }
[Display(Name = "Consumed by EV's")]
public int EnergyConsumedByEV { get; set; }
[Display(Name = "Consumed by Construction Site")]
public int EnergyConsumedByConstructionSite { get; set; }
}
This model is used in my provider:
if (fleet.Type.Equals("Container"))
{
return Enumerable.Range(0, 10).Select(i => new DataGridInstallationRow()
{
Name = $"Container {i}",
EnergyGeneratedBySun = 13,
EnergyGeneratedByWind = 19,
EnergyGeneratedByGenerator = 3,
EnergyConsumedByEV = 15,
EnergyConsumedByConstructionSite = 24
}).ToList();
}
else
{
return Enumerable.Range(0, 10).Select(i => new DataGridInstallationRow()
{
Name = $"Vessel {i}",
IMO = 231,
MMSI = 1344,
EEOI = 8121,
FOC = 123,
TotalFuelMass = 6817,
AverageSpeed = 14,
TotalDistanceSailed = 1560
}).ToList();
}
As u can see, depending on the Fleet.Type, one of the other is filled in. If Fleet.Type is container the object will look like this:
As u can see the properties of "Vessel" is filled in aswell with all "0", I want these to be null instead of "0" because my datagrid is filled with both models now:
Whats best practice to avoid and fix this?
UPDATE
Applied solution of Dogac:
if (fleet.Type.Equals("Container"))
{
return Enumerable.Range(0, 10).Select(i => new DataGridInstallationRow()
{
Name = $"Container {i}",
EnergyGeneratedBySun = 13,
EnergyGeneratedByWind = 19,
EnergyGeneratedByGenerator = 3,
EnergyConsumedByEV = 15,
EnergyConsumedByConstructionSite = 24
}).Where(row =>
{
return row.EnergyGeneratedBySun.HasValue &&
row.EnergyGeneratedByWind.HasValue &&
row.EnergyGeneratedByGenerator.HasValue &&
row.EnergyConsumedByEV.HasValue &&
row.EnergyConsumedByConstructionSite.HasValue;
}).ToList();
}
else
{
return Enumerable.Range(0, 10).Select(i => new DataGridInstallationRow()
{
Name = $"Vessel {i}",
IMO = 231,
MMSI = 1344,
EEOI = 8121,
FOC = 123,
TotalFuelMass = 6817,
AverageSpeed = 14,
TotalDistanceSailed = 1560
}).Where(row =>
{
return row.IMO.HasValue &&
row.MMSI.HasValue &&
row.EEOI.HasValue &&
row.FOC.HasValue &&
row.TotalFuelMass.HasValue &&
row.AverageSpeed.HasValue &&
row.TotalDistanceSailed.HasValue;
}).ToList();
}
Is still not working, im again receiving a list with nullable items.
Thanks in advance
Try making all properties nullable.
Like this:
// Vessel
public int? IMO { get; set; }
public int? MMSI { get; set; }
public int? EEOI { get; set; }
public int? FOC { get; set; }
Edit regarding comment:
Enumerable.Range(0, 10).Select(i => new DataGridInstallationRow()
{
Name = $"Vessel {i}",
IMO = 231,
MMSI = 1344,
EEOI = 8121,
FOC = 123,
TotalFuelMass = 6817,
AverageSpeed = 14,
TotalDistanceSailed = 1560
}).Where(row =>
{
return row.IMO.HasValue &&
row.MMSI.HasValue &&
row.EEOI.HasValue &&
row.FOC.HasValue &&
row.TotalFuelMass.HasValue &&
row.AverageSpeed.HasValue &&
row.TotalDistanceSailed.HasValue;
}).ToList();

Combine two different types into one linq query and sort it

I have two database tables and I'm attempting to create a union query from them. They have different structures:
public partial class Notes
{
public int ID { get; set; }
public int VisitID { get; set; }
public string Note { get; set; }
public DateTime PostDate { get; set; }
public decimal AcctBalance {get; set; }
}
public partial class SystemNotes
{
public int ID {get; set;}
public int VisitID {get; set;}
public int FacilityID {get; set;}
public string Note {get; set;
public DateTime NoteDate {get ;set; }
}
What I want to do is end up with a list of all the data in Notes format sorted by PostDate. What I've tried so far is this:
List<Notes> requests = new List<Notes>();
requests = _context.Notes.Where(i => i.VisitID == VisitID && i.isActive == true).ToList();
List<SystemNotes> requests_s = new List<SystemNotes>();
requests_s = _context.SystemNotes.Where(i => i.VisitID == VisitID).ToList();
var unionA = from a in requests
select new
{
a.ID,
a.VisitID,
a.Note,
a.PostDate,
a.AcctBalance
};
var unionB = from b in requests_s
select new Notes()
{
ID = b.ID,
VisitID = (int)b.VisitID,
Note = b.Note,
PostDate = b.NoteDate,
AcctBalance = (decimal)0.00
};
List<Object> allS = (from x in unionA select (Object)x).ToList();
allS.AddRange((from x in unionB select (Object)x).ToList());
However, PostDate is no longer recognized as an element inside the Object so I can't sort on it. Also, it's in Object format not in Notes format which is what I want for where I'm sending my data. I'm stuck on this one point. Can you assist? Or am I doing this the wrong way in general?
If I correctly understand what you want:
List<Notes> myNotes = new List<Notes> {
new Notes () {
ID = 1,
VisitID = 2
}
};
List<SystemNotes> mySystemNotes = new List<SystemNotes> {
new SystemNotes () {
ID = 3,
VisitID = 4
}
};
var result = myNotes.Select (mn => new { mn.ID, mn.VisitID })
.Union(mySystemNotes.Select (msn => new { msn.ID, msn.VisitID }))
.OrderByDescending(a=>a.ID);
foreach (var currentItem in result)
{
Console.WriteLine ("ID={0}; VisitID={1}", currentItem.ID, currentItem.VisitID);
}

Efficient way to call .Sum() on multiple properties

I have a function that uses LINQ to get data from the database and then I call that function in another function to sum all the individual properties using .Sum() on each individual property. I was wondering if there is an efficient way to sum all the properties at once rather than calling .Sum() on each individual property. I think the way I am doing as of right now, is very slow (although untested).
public OminitureStats GetAvgOmnitureData(int? fnsId, int dateRange)
{
IQueryable<OminitureStats> query = GetOmnitureDataAsQueryable(fnsId, dateRange);
int pageViews = query.Sum(q => q.PageViews);
int monthlyUniqueVisitors = query.Sum(q => q.MonthlyUniqueVisitors);
int visits = query.Sum(q => q.Visits);
double pagesPerVisit = (double)query.Sum(q => q.PagesPerVisit);
double bounceRate = (double)query.Sum(q => q.BounceRate);
return new OminitureStats(pageViews, monthlyUniqueVisitors, visits, bounceRate, pagesPerVisit);
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats (
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
);
return query;
}
return null;
}
public class OminitureStats
{
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = Math.Round((double)(PageViews / Visits), 1);
}
public OminitureStats(int PageViews, int MonthlyUniqueVisitors, int Visits, double BounceRate, double PagesPerVisit)
{
this.PageViews = PageViews;
this.MonthlyUniqueVisitors = MonthlyUniqueVisitors;
this.Visits = Visits;
this.BounceRate = BounceRate;
this.PagesPerVisit = PagesPerVisit;
}
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
IIRC you can do all the sums in one go (as long as the query is translated to SQL) with
var sums = query.GroupBy(q => 1)
.Select(g => new
{
PageViews = g.Sum(q => q.PageViews),
Visits = g.Sum(q => q.Visits),
// etc etc
})
.Single();
This will give you one object which contains all the sums as separate properties.
I found out why it was throwing the NotSupportedException. I learned that Linq to Entity does not support constructors with parameters, So deleted the constructors and made changes in my query. I am a novice C# programmer, so let me know if my solution could be improved, but as of right now it is working fine.
public class OminitureStats
{
public int PageViews { get; set; }
public int MonthlyUniqueVisitors { get; set; }
public int Visits { get; set; }
public double PagesPerVisit { get; set; }
public double BounceRate { get; set; }
}
private IQueryable<OminitureStats> GetOmnitureDataAsQueryable(int? fnsId, int dateRange)
{
var yesterday = DateTime.Today.AddDays(-1);
var nDays = yesterday.AddDays(-dateRange);
if (fnsId.HasValue)
{
IQueryable<OminitureStats> query = from o in lhDB.omniture_stats
where o.fns_id == fnsId
&& o.date <= yesterday
&& o.date > nDays
select new OminitureStats() {
o.page_views.GetValueOrDefault(),
o.monthly_unique.GetValueOrDefault(),
o.visits.GetValueOrDefault(),
(double)o.bounce_rate.GetValueOrDefault()
};
return query;
}
return null;
}

Categories

Resources