I have Logging model that related to db table
Here is code:
public partial class Logging
{
public string Imei { get; set; }
public DateTime CurDateTime { get; set; }
public Nullable<System.DateTime> GPSDateTime2 { get; set; }
public Nullable<decimal> Latitude2 { get; set; }
public Nullable<decimal> Longitude2 { get; set; }
public int Speed { get; set; }
public Nullable<int> Datatype { get; set; }
public int Id { get; set; }
}
I want to calculate difference and decided to create ViewModel
Here is code
public class HeatmapViewModel:Logging
{
public TimeSpan? FirstStartDifference
{
get
{
if (CurDateTime != null)
{
var midnight = new DateTime(CurDateTime.Year, CurDateTime.Month, CurDateTime.Day, 00, 00, 00);
var difference = CurDateTime - midnight;
return difference;
}
return null;
}
}
public TimeSpan? LastStartDifference
{
get
{
if (CurDateTime != null)
{
var midnight = new DateTime(CurDateTime.Year, CurDateTime.Month, CurDateTime.Day, 23, 59, 00);
var difference = midnight - CurDateTime;
return difference;
}
return null;
}
}
public int coeff = 2;
public int Difference;
}
And on back-end I have this method
List<HeatmapViewModel> items = new List<HeatmapViewModel>();
var firstitem = ctx.Loggings.Where(x => x.Datatype == 2).AsEnumerable().Select(
x => new HeatmapViewModel
{
Longitude2 = x.Longitude2,
Latitude2 = x.Latitude2,
Difference = (int)(x.FirstStartDifference?.TotalMinutes ?? -1) * x.coeff
}).FirstOrDefault();
var lastItem = ctx.Loggings.Where(x => x.Datatype == 2).AsEnumerable().Select(
x => new HeatmapViewModel
{
Longitude2 = x.Longitude2,
Latitude2 = x.Latitude2,
Difference = (int)(x.LastStartDifference?.TotalMinutes ?? -1) * x.coeff
}).LastOrDefault();
But on this row Difference = (int)(x.FirstStartDifference?.TotalMinutes ?? -1) * x.coeff, I have error
Severity Code Description Project File Line Suppression State
Error CS1061 'Logging' does not contain a definition for 'FirstStartDifference' and no extension method 'FirstStartDifference' accepting a first argument of type 'Logging' could be found (are you missing a using directive or an assembly reference?) Heatmap C:\Users\nemes\source\repos\Heatmap\Heatmap\Controllers\HomeController.cs 28 Active
How I can use property from VIewModel
Use multi-line lambda instead:
var firstitem = Loggings.Where(x => x.Datatype == 2).AsEnumerable().Select(
x =>
{
var vm = new HeatmapViewModel
{
Longitude2 = x.Longitude2,
Latitude2 = x.Latitude2
};
vm.Difference = (int)(vm.FirstStartDifference?.TotalMinutes ?? -1) * vm.coeff;
return vm;
}).FirstOrDefault();
Note: Please read comments below your post, because you are mixing types. Variable x is of type Logging
Related
Is there any way i can get number of child with max parent id and list the result with linq?
I'm trying to bring the total of values by Status, but i can only get the last one from the child
what I have done so far:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var lstRfq = new List<RfqEvents>()
{
new RfqEvents(1,1,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(2,2,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(3,2,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(4,3,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(5,4,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(6,5,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(7,5,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(8,5,DateTime.Parse("2021-05-06 00:00:00+00"),3),
new RfqEvents(9,6,DateTime.Parse("2021-05-06 00:00:00+00"),3),
new RfqEvents(10,6,DateTime.Parse("2021-05-06 00:00:00+00"),3),
};
var subquery = from c in lstRfq
group c by c.RfqId into g
select new InternalStatusInformations
{
RfqId = g.Key,
RfqEventId = g.Max(a => a.Id),
StatusId = g.Select(p => p.Status).FirstOrDefault()
};
var sss = from d in lstRfq.Where(p=> subquery.Select(p=>p.RfqEventId).Contains(p.Id))
group d by d.Status into z
select new InternalStatusInformations
{
StatusId = z.Key,
Total = z.Count(),
Past = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date < DateTime.Now.Date).Count(),
Future = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date > DateTime.Now.Date).Count(),
Today = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date == DateTime.Now.Date).Count(),
FiveDays = z.Where(p => (p.DueDate.HasValue && p.DueDate.Value.Date > DateTime.Now.Date) && p.DueDate.HasValue && p.DueDate.Value.Date < DateTime.Now.Date.AddDays(5)).Count(),
};
//expected: Status 1: 2 values
// Status 2: 3 values
// Status 3: 2 value
//output: Status 1: 2 values
// Status 2: 2 values
// Status 3: 2 values
sss.Dump();
}
public class InternalStatusInformations
{
public int RfqEventId { get; set; }
public int RfqId { get; set; }
public int StatusId { get; set; }
public int Future { get; set; }
public int Past { get; set; }
public int Today { get; set; }
public int FiveDays { get; set; }
public int Total { get; set; }
public DateTime? DueDate { get; set; }
}
public class RfqEvents
{
public RfqEvents(int id, int rfqId, DateTime? dueDate, int status)
{
Id = id;
RfqId = rfqId;
DueDate = dueDate;
Status = status;
}
public int Id { get; set; }
public DateTime? DueDate { get; set; }
public int RfqId { get; set; }
public int Status { get; set; }
}
}
https://dotnetfiddle.net/YoRsIG
but something is not right with the results, could you guys help me?
If you just want to count the number of distinct RfqId values in each status, this should do it:
var pairs = lstRfq
.GroupBy(evt => evt.Status)
.Select(grouping =>
{
var status = grouping.Key;
var count = grouping
.Select(evt => evt.RfqId)
.Distinct()
.Count();
return (status, count);
});
foreach ((var status, var count) in pairs)
{
Console.WriteLine($"Status {status}: {count} values");
}
Output is:
Status 1: 2 values
Status 2: 3 values
Status 3: 2 values
I'm writing a simple console weather app (OpenWeatherMap) and I would like to validate whether response from OWM contains any null properties. This is my class containing properties:
public class WeatherMain
{
public Coord coord { get; set; }
public List<Weather> weather { get; set; }
public Main main { get; set; }
public Wind wind { get; set; }
public Rain rain { get; set; }
public Snow snow { get; set; }
public Clouds clouds { get; set; }
public Sys sys { get; set; }
[JsonProperty("base")]
public string _base { get; set; }
public int? visibility { get; set; }
public int? timezone { get; set; }
public int? id { get; set; }
public string name { get; set; }
public int? cod { get; set; }
}
As you can see there are also some classed containing their own properties as well.
I would like to check, if any of these is basically a null, so I can inform about missing value in Console. Right now I did some basic check using IFs for WeatherMain properties:
public static string PrepareResponse(WeatherMain input)
{
string cityName, main, visibility, wind, clouds, rain, snow, coord;
if (input.name == null)
cityName = "City name section not found\n";
else
cityName = $"\nCity name: {input.name}\n";
if (input.main == null)
main = "Main section not found\n";
else
{
main = $"Main parameters:\n\tTemperature: {input.main.temp}C\n\t" +
$"Temperature max.: {input.main.temp_max}C\n\tTemperature min.: {input.main.temp_min}C" +
$"\n\tFeels like: {input.main.feels_like}C\n\tPressure: {input.main.pressure}hPA\n\t" +
$"Humidity: {input.main.humidity}%\n";
}
if (input.visibility == null)
visibility = "Visibility section not found\n";
else
visibility = $"Visibility: {input.visibility}m\n";
if (input.wind == null)
wind = "Wind section not found\n";
else
wind = $"Wind:\n\tSpeed: {input.wind.speed}m/s\n\tDirection: {input.wind.deg}deg\n";
if (input.clouds == null)
clouds = "Clouds section not found\n";
else
clouds = $"Clouds: {input.clouds.all}%\n";
if (input.rain == null)
rain = "Rain section not found\n";
else
rain = $"Rain: {input.rain._1h}mm\n";
if (input.snow == null)
snow = "Snow section not found\n";
else
snow = $"Snow: {input.snow._1h}mm\n";
if (input.coord == null)
coord = "Coordinates section not found\n";
else
coord = $"Coordinates:\n\tLatitude: {input.coord.lat}\n\tLongitude: {input.coord.lon}\n";
string outputString = cityName + main + visibility + wind + clouds + rain + snow + coord;
return outputString;
}
I was thinking of putting those properties to a collection and checking them for null one by one and maybe changing the value to string.
I guess there are better ways to do that.
Any ideas?
you can do this by using Reflection namespace:
var weather = new WeatherMain
{
visibility = 2,
timezone = 5
};
Type t = weather.GetType();
Console.WriteLine("Type is: {0}", t.Name);
PropertyInfo[] props = t.GetProperties();
Console.WriteLine("Properties (N = {0}):",
props.Length);
foreach (var prop in props)
if (prop.GetValue(weather) == null)
{
Console.WriteLine(" {0} ({1}) is null", prop.Name,
prop.PropertyType.Name);
}
here is a sample fiddle: https://dotnetfiddle.net/MfV7KD
Using a descriptor class, you can consolidate the value access, the null messsage and the formatted output into one place, and then loop through them.
First, the Parameter class will describe each parameter:
public class Parameter {
public Func<WeatherMain, object> Value;
public string NullName;
public Func<WeatherMain, string> FormatSection;
}
Then, a static List will describe all the parameters you want to test and output:
public static List<Parameter> Parameters = new[] {
new Parameter { Value = w => w.name, NullName = "City name", FormatSection = w => $"City name: {w.name}" },
new Parameter { Value = w => w.main, NullName = "Main",
FormatSection = w => $"Main parameters:\n\tTemperature: {w.main.temp}C\n\t" +
$"Temperature max.: {w.main.temp_max}C\n\tTemperature min.: {w.main.temp_min}C" +
$"\n\tFeels like: {w.main.feels_like}C\n\tPressure: {w.main.pressure}hPA\n\t" +
$"Humidity: {w.main.humidity}%" },
new Parameter { Value = w => w.visibility, NullName = "Visibility", FormatSection = w => $"Visibility: {w.visibility}" },
new Parameter { Value = w => w.wind, NullName = "Wind", FormatSection = w => $"Wind:\n\tSpeed: {w.wind.speed}m/s\n\tDirection: {w.wind.deg}deg" },
new Parameter { Value = w => w.clouds, NullName = "Clouds", FormatSection = w => $"Clouds: {w.clouds.all}%" },
new Parameter { Value = w => w.rain, NullName = "Rain", FormatSection = w => $"Rain: {w.rain._1h}mm" },
new Parameter { Value = w => w.snow, NullName = "Snow", FormatSection = w => $"Snow: {w.snow._1h}mm" },
new Parameter { Value = w => w.coord, NullName = "Coordinates", FormatSection = w => $"Coordinates:\n\tLatitude: {w.coord.lat}\n\tLongitude: {w.coord.lon}" },
}.ToList();
Finally, you can produce the formatted response by looping through the list:
public static string PrepareResponse(WeatherMain input) {
var ans = new List<string>();
foreach (var p in Parameters) {
if (p.Value(input) == null)
ans.Add($"{p.NullName} section not found");
else
ans.Add(p.FormatSection(input));
}
return String.Join("\n", ans);
}
I have about 300K rows in a DataTable. The first column is "utcDT" which contains a DateTime with minutes.
I want to group the data by Date into a list of "ReportDailyData". My method is below but takes around 8 seconds to run. I need to make this significantly faster.
Is there a better way to do this?
public class ReportDailyData
{
public DateTime UtcDT;
public double Day_Pnl;
public int TradeCount;
public int Volume;
public ReportDailyData(DateTime utcDT, double day_Pnl, int tradeCount, int volume)
{
UtcDT = utcDT;
Day_Pnl = day_Pnl;
TradeCount = tradeCount;
Volume = volume;
}
public string AsString()
{
return UtcDT.ToString("yyyyMMdd") + "," + Day_Pnl.ToString("F2") + "," + TradeCount + "," + Volume;
}
}
public static DataTable Data;
public static DataSpecification DataSpec;
public void Go()
{
//Fill Data and DataSpec elsewhere
var dailylist = GetDailyData();
}
public List<ReportDailyData> GetDailyData()
{
List<ReportDailyData> dailyDatas = new List<ReportDailyData>();
DateTime currentDT = DataSpec.FromDT.Date;
while (currentDT <= DataSpec.ToDT.Date)
{
var rowsForCurrentDT = Data.AsEnumerable().Where(x => x.Field<DateTime>("utcDT").Date == currentDT).ToList();
if (rowsForCurrentDT.Any())
{
double day_Pnl = rowsForCurrentDT.Sum(x => x.Field<double>("Bar_Pnl"));
var positions = rowsForCurrentDT.Select(x => x.Field<double>("Position")).ToList();
var deltas = positions.Zip(positions.Skip(1), (current, next) => next - current);
int tradeCount = deltas.Where(x => x != 0).Count();
int volume = (int)deltas.Where(x => x != 0).Sum(x => Math.Abs(x));
dailyDatas.Add(new ReportDailyData(currentDT, day_Pnl, tradeCount, volume));
}
else
{
dailyDatas.Add(new ReportDailyData(currentDT, 0, 0, 0));
}
currentDT = currentDT.AddDays(1);
}
return dailyDatas;
}
If I understood correctly - you want to perform grouping on some data collection, is that right?
If so - why not to use linq: GroupBy method?
A simple example is below:
void Main()
{
var data = new List<MyData>();
data.Add(new MyData() { UtcDT = DateTime.UtcNow, Volume = 1 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), Volume = 1 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), Volume = 4 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-2), Volume = 5 });
var result = GroupReportDataAndFormat(data);
}
public Dictionary<DateTime, int> GroupReportDataAndFormat(List<MyData> data)
{
return data.GroupBy(t => t.UtcDT.Date).ToDictionary(k => k.Key, v => v.Sum(s => s.Volume));
}
public class MyData
{
public DateTime UtcDT { get; set; }
public int Volume { get; set; }
}
Of course - for performance reasons, you probably should do grouping on database level (compose query to return your data, that is already grouped)
=== UPDATE =====
MainInMoon : I've updated solution to fit your case:
void Main()
{
var data = new List<MyData>();
data.Add(new MyData() { UtcDT = DateTime.UtcNow, DayPnl = 1, Positions = 3 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), DayPnl = 1, Positions = 4 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), DayPnl = 4, Positions = 5 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-2), DayPnl = 5, Positions = 6 });
var result = GroupReportDataAndFormat(data);
}
public Dictionary<DateTime, GroupResult> GroupReportDataAndFormat(List<MyData> data)
{
return data.GroupBy(t => t.UtcDT.Date).ToDictionary(
k => k.Key, v => new GroupResult
{
DayPnlSum = v.Sum(s => s.DayPnl),
Deltas = v.Select(t => t.Positions).Zip(v.Select(s => s.Positions).Skip(1), (current, next) => next - current)
});
}
public class GroupResult
{
public double DayPnlSum { get; set; }
public IEnumerable<double> Deltas { get; set; }
public int TradeCount
{
get
{
return Deltas.Where(x => x != 0).Count();
}
}
public int Volume
{
get
{
return (int)Deltas.Where(x => x != 0).Sum(x => Math.Abs(x));
}
}
}
public class MyData
{
public DateTime UtcDT { get; set; }
public int DayPnl { get; set; }
public double Positions { get; set; }
}
Of course, you can change TradeCount and Volume properties to be calculated during grouping (not lazy loaded)
I would advise: sort on the utcDT, then enumerate the result linearly and do the grouping and aggregation manually into a new data structure. For every new utcDT value you encounter, create a new ReportDailyData instance, then start aggregating the values into it until utcDT has the same value.
Hi I'm a new to elastic nest API and I'm using nest 5.x. I'm currently developing some kind of advanced search page so when user doesn't check a criteria i don't have to include that filter on my query. I'm trying to combine 2 queries under must operator with object initializer approach using nest. How to achieve it? I'm following the example on [https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/bool-queries.html]
var secondSearchResponse = client.Search(new
SearchRequest {
Query = new TermQuery { Field = Field(p => p.Name), Value = "x" } &&
new TermQuery { Field = Field(p => p.Name), Value = "y" } });
But it doesnt work cause Field class doesnt accept type arguments.
I also tried to followed this approach from this topic
[Nest Elastic - Building Dynamic Nested Query
here is my code
public HttpResponseMessage GetSearchResult([FromUri] SearchModels queries)
{
try
{
///
string result = string.Empty;
result += "queryfields + " + queries.queryfields == null ? string.Empty : queries.queryfields;
result += "datefrom + " + queries.datefrom == null ? string.Empty : queries.datefrom;
result += "dateto + " + queries.dateto == null ? string.Empty : queries.dateto;
result += "emitentype + " + queries.emitentype == null ? string.Empty : queries.emitentype;
QueryContainer andQuery = null;
//List<QueryContainer> QueryContainers = new List<QueryContainer>();
IDXNetAnnouncement record = new IDXNetAnnouncement
{
kode_emiten = queries.kodeemiten
};
#region keyword
if (!string.IsNullOrEmpty(queries.queryfields))
{
var val = queries.queryfields;
TermQuery tq = new TermQuery
{
Field = queries.queryfields,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
#endregion keyword
#region kodeemiten
if (!string.IsNullOrEmpty(queries.kodeemiten))
{
var val = queries.kodeemiten;
TermQuery tq = new TermQuery
{
Name = "kode_emiten",
Field = record.kode_emiten,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
#endregion
#region date
if (!string.IsNullOrEmpty(queries.datefrom) && !string.IsNullOrEmpty(queries.dateto))
{
DateRangeQuery dq = new DateRangeQuery();
dq.Name = "tglpengumuman";
dq.LessThanOrEqualTo = DateMath.Anchored(queries.dateto);
dq.GreaterThanOrEqualTo = DateMath.Anchored(queries.datefrom);
dq.Format = "dd/mm/yyyy";
if (andQuery == null)
andQuery = dq;
else
andQuery &= dq;
//QueryContainers.Add(dq);
}
#endregion keyword
var reqs = (ISearchResponse<IDXNetAnnouncement>)null;
if (andQuery != null)
{
reqs = conn.client.Search<IDXNetAnnouncement>(s => s
.AllIndices()
.AllTypes()
.From(queries.indexfrom)
.Size(queries.pagesize)
.Query(q => q.Bool(qb => qb.Must(m => m.MatchAll() && andQuery))));
//var json = conn.client.Serializer.SerializeToString(reqs.ApiCall.ResponseBodyInBytes);
}
else
{
reqs = conn.client.Search<IDXNetAnnouncement>(s => s
.AllIndices()
.AllTypes()
.From(queries.indexfrom)
.Size(queries.pagesize)
.Query(m => m.MatchAll()));
}
//var reqstring = Encoding.UTF8.GetString(conn.client.);
var reslts = this.conn.client.Serializer.SerializeToString(reqs,SerializationFormatting.Indented);
var resp = new HttpResponseMessage()
{
Content = new StringContent(reslts)
};
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return resp;
}
catch (Exception e)
{
var resp = new HttpResponseMessage()
{
Content = new StringContent(e.ToString())
};
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return resp;
}
}
But that returns zero result. How to achieve this? Thx anyway.
EDIT :
This is the params variabel definition. Its apoco model of search keywords
public class SearchModels
{
public string queryfields { get; set; }
public string datefrom { get; set; }
public string dateto { get; set; }
public string emitentype { get; set; }
public string kodeemiten { get; set; }
public string issuercode { get; set; }
public int indexfrom { get; set; }
public int pagesize { get; set; }
}
IDXNetAnnouncement is a poco model of search result. Its actualy a document type which is stored on the elastic server
public class IDXNetAnnouncement
{
public string perihalpengumuman { get; set; }
public string attachments { get; set; }
public string createddate { get; set; }
public bool efekemiten_spei { get; set; }
public string jmsxgroupid { get; set; }
public string tglpengumuman { get; set; }
public object errordescription { get; set; }
public string ESversion { get; set; }
public int oldfinalid { get; set; }
public bool efekemiten_etf { get; set; }
public object errorcode { get; set; }
public string jenisemiten { get; set; }
public int pkid { get; set; }
public string judulpengumuman { get; set; }
public string form_id { get; set; }
public bool efekemiten_eba { get; set; }
public string jenispengumuman { get; set; }
public string nopengumuman { get; set; }
public string kode_emiten { get; set; }
public string divisi { get; set; }
public string EStimestamp { get; set; }
public bool efekemiten_obligasi { get; set; }
public long finalid { get; set; }
public bool efekemiten_saham { get; set; }
public string kodedivisi { get; set; }
public string SearchTerms
{
get
{
return string.Format("{0} {1} {2}", judulpengumuman, kode_emiten, nopengumuman);
}
}
}
But it doesnt work cause Field class doesnt accept type arguments.
You need to ensure that you include a using static directive for Nest.Infer i.e.
using static Nest.Infer;
with the rest of the using directives.
.Query(q => q.Bool(qb => qb.Must(m => m.MatchAll() && andQuery))));
No need to wrap in a Must(), just do
.Query(q => q.MatchAll() && andQuery)
which will wrap both queries in a bool query must clause. You also don't need to null check andQuery because NEST is smart enough to not combine the two queries if either or both are null.
if (!string.IsNullOrEmpty(queries.queryfields))
{
var val = queries.queryfields;
TermQuery tq = new TermQuery
{
Field = queries.queryfields,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
NEST has the concept of conditionless queries so you don't need to check it queries.queryfields is null or empty, simply build the query and add it to andQuery. So it would become
var val = queries.queryfields;
andQuery &= new TermQuery
{
Field = queries.queryfields,
Value = val
};
Aside
All of the NEST documentation is generated from source code; you can trace back to the original source file by clicking on any edit link within the documentation. This will take you to a github page, such as this one for bool queries. From here, the document contains an important note that links back to the original source.
I have a Collection:
Collection<DateOfValues> collectionDateOfValues;
...
I get a DateOfValues Instance - lets say dateOfValuesNew and want to iterate over the collection and overwrite only values that are different.
public class DateOfValues
{
{
this.Values = new Collection<SomeValue>();
}
public int id { get; set;}
public DateTime Start { get; set; }
public Collection<SomeValue> Values;
}
public class SomeValue
{
public int Id { get; set; }
public DateOfValues Date { get; set; }
public string Status { get; set; }
publi decimal StatusNumber { get; set; }
}
What I have done:
if (dateOfValuesNew != null)
{
foreach (var dateOfValues in collectionDateOfValues)
{
if (dateOfValues.Id == dateOfValuesNew.Id)
{
// Here Im sure to find the dateOfValues Instance I will work with.
}
}
}
But If I want to compare dateOfValues with dateOfValuesNew with foreach it is ugly and unreadable.
Is there any better way to do it?
The Start of DateOfValues can be changed. It is the easiest part - cause I can simply overwrite it.
The hard part is to compare SomeValue Collection. Every SomeValue can have changed Date and Status - this can be solved with overwriting too.
But SomeValue Collection can become new SomeValue or it can be deleted.
For example dateOfValues has 3 SomeValue in SomeValue Collection and dateOfValuesNew will have 4 or 2.
A bit tricky but this works:
class Program
{
static void Main(string[] args)
{
// Test
DateOfValues dov1 = new DateOfValues { Id = 1, Start = new DateTime(2011, 12, 01) };
dov1.AddSomeValue(1,"OK",2);
dov1.AddSomeValue(2,"Not OK",3);
dov1.AddSomeValue(3,"Not OK",4);
dov1.AddSomeValue(4,"Additional dov1",5);
DateOfValues dov2 = new DateOfValues { Id = 1, Start = new DateTime(2011, 12, 02) };
dov2.AddSomeValue(1, "OK", 2);
dov2.AddSomeValue(2, "Not OK", 4);
dov2.AddSomeValue(3, "OK", 1);
dov2.AddSomeValue(6, "Additional dov2", 15);
foreach (Tuple<SomeValue,SomeValue> difference in dov1.GetDifference(dov2))
{
if (difference.Item1 != null)
{
Console.WriteLine("Item1: Id:{0}; Status:{1}; Status Number:{2}",
difference.Item1.Id, difference.Item1.Status, difference.Item1.StatusNumber);
}
if (difference.Item2 != null)
{
Console.WriteLine("Item2: Id:{0}; Status:{1}; Status Number:{2}",
difference.Item2.Id, difference.Item2.Status, difference.Item2.StatusNumber);
}
Console.WriteLine("-------------------------------------------");
}
}
}
public class DateOfValues
{
public DateOfValues()
{
Values = new Collection<SomeValue>();
}
public int Id { get; set; }
public DateTime Start { get; set; }
public Collection<SomeValue> Values;
public void AddSomeValue(int id, string status, decimal statusNumber)
{
Values.Add(new SomeValue{Date = this,Id = id,Status = status,StatusNumber = statusNumber});
}
public IEnumerable<Tuple<SomeValue, SomeValue>> GetDifference(DateOfValues other)
{
IEnumerable<SomeValue> notMatching = Values.Where(v => !other.Values.Any(o => v.Equals(o)))
.Union(other.Values.Where(v=> !Values.Any(o=> v.Equals(o)))).Distinct();
return notMatching
.GroupBy(x => x.Id)
.Select(x =>
new Tuple<SomeValue, SomeValue>(
x.FirstOrDefault(y => y.Date == this), x.FirstOrDefault(y => y.Date == other)));
}
}
public class SomeValue : IEquatable<SomeValue>
{
public int Id { get; set; }
public DateOfValues Date { get; set; }
public string Status { get; set; }
public decimal StatusNumber { get; set; }
public bool Equals(SomeValue other)
{
return other.Id == Id && other.Status == Status && other.StatusNumber == StatusNumber;
}
}
Output:
Item1: Id:2; Status:Not OK; Status Number:3
Item2: Id:2; Status:Not OK; Status Number:4
-------------------------------------------
Item1: Id:3; Status:Not OK; Status Number:4
Item2: Id:3; Status:OK; Status Number:1
-------------------------------------------
Item1: Id:4; Status:Additional dov1; Status Number:5
-------------------------------------------
Item2: Id:6; Status:Additional dov2; Status Number:15
-------------------------------------------
Edit
Alternative you could use an EqualityComparer:
public class DateOfValues
{
public DateOfValues()
{
Values = new Collection<SomeValue>();
}
public int Id { get; set; }
public DateTime Start { get; set; }
public Collection<SomeValue> Values;
public void AddSomeValue(int id, string status, decimal statusNumber)
{
Values.Add(new SomeValue { Date = this, Id = id, Status = status, StatusNumber = statusNumber });
}
public IEnumerable<Tuple<SomeValue, SomeValue>> GetDifference(DateOfValues other)
{
var notMatching = Values.Except(other.Values, new SomeValueComparer())
.Union(other.Values.Except(Values,new SomeValueComparer()));
return notMatching
.GroupBy(x => x.Id)
.Select(x =>
new Tuple<SomeValue, SomeValue>(
x.FirstOrDefault(y => y.Date == this), x.FirstOrDefault(y => y.Date == other)));
}
}
public class SomeValueComparer : IEqualityComparer<SomeValue>
{
public bool Equals(SomeValue x, SomeValue y)
{
return
x.Id == y.Id &&
x.Status == y.Status &&
x.StatusNumber == y.StatusNumber;
}
public int GetHashCode(SomeValue obj)
{
return obj.GetHashCode();
}
}
public class SomeValue
{
public int Id { get; set; }
public DateOfValues Date { get; set; }
public string Status { get; set; }
public decimal StatusNumber { get; set; }
public override int GetHashCode()
{
return string.Format("{0}{1}{2}",Id,Status,StatusNumber).GetHashCode();
// or a better method to get a hashcode
}
}