I have a List of Members:
List<Members> MyMembers= new List<Members>();
Members class:
public class Members
{
public int IdKey;
public string name;
public string relationBegin;
public string relationEnd;
public bool isOriginal;
}
I need to merge duplicate IdKey into one.
This can be done using something like:
MyMembers=MyMembers.GroupBy(x => x.IdKey )
.Select(g => new Members{ IdKey = g.Key })
.ToList();
Here is where the fun begins.
The condiation is that if we detect duplicate IdKey It need to preserve the one that has isOriginal=true (if both isOriginal=false, we leave isOriginal to false but update dates begin and end as explained in next statment)
Furthermore,
We need preserve the lowest relationBegin and highest relationEnd from the two duplicates, Sometimes relationBegin or relationEnd could be Null or empty.
Example:
Row1:
IsOriginal=true
relationBegin = 1/1/2017
relationEnd = 10/10/2018
Example:
Row2:
IsOriginal=false
relationBegin = 1/1/2015
relationEnd = NULL
Result would be:
IsOriginal= true
relationBegin=1/1/2015
relationEnd 10/10/2018
It would be better if your Member class would have DateTime instead of string Date time.
if you need them in string, you can have property like below.
public class Members
{
public int IdKey;
public string name;
public string relationBegin;
public string relationEnd;
public bool isOriginal;
public DateTime RelationBeginDate
{
get { return DateTime.ParseExact(relationBegin, "dd-MM-yyyy hh:mm:ss", CultureInfo.InvariantCulture); }
}
public DateTime RelationEndDate
{
get { return DateTime.ParseExact(relationEnd, "dd-MM-yyyy hh:mm:ss", CultureInfo.InvariantCulture); }
}
public Members(int IdKey, string name, string relationBegin, string relationEnd, bool isOriginal)
{
//assign paramters to proper properties
}
}
and your desired linq will be something like below.
MyMembers = MyMembers.GroupBy(x => x.IdKey)
.Select(g => new Members(
g.Key, //Id will be same as you shown in question
g.FirstOrDefault().name, //assuming name will be same in all
g.Select(x => x.RelationBeginDate).Min().ToString("dd-MM-yyyy hh:mm:ss"), //Min begin date
g.Select(x => x.RelationEndDate).Max().ToString("dd-MM-yyyy hh:mm:ss"), //Max end date
g.Any( x => x.isOriginal))).ToList(); //if isOriginal = true found
Related
I'm trying to build a query that will execute against the database as an IQueryable, and not in memory (IEnumerable).
The query will be used for several different purposes and each purpose has a slightly different way in which the Total property is calculated.
Because I'm using a Func for calculating the total, i get an error advising me that sql doesn't know how to deal with the Invoke method of my Func, which is understandable.
To get past the problem, i have had to list the groupings into memor by calling ToList() which is not good for performance.
Is there a way that i can execute this query as an IQueryable? Otherwise im going to have to write this query 20+ times with a calculation variance
Func<IGrouping<object, MyType>, double?> calculateTotal= (group) => #group.Sum(x => x.PassengerTotal);
Dictionary<object, double?> weekValues = queryable.GroupBy(o => new
{
Year = SqlFunctions.DatePart("yyyy", o.DateCreated),
Week = SqlFunctions.DatePart("ww", o.DateCreated),
Source = o.SourceId,
})
.ToList() //NEED TO REMOVE THIS CALL
.Select(ac => new WeeklyGraphGroup()
{
Year = ac.Key.Year,
Week = ac.Key.Week,
SourceId = ac.Key.Source,
Total = calculateTotal(ac)
})
.ToDictionary(dict =>
new
{
Year = dict.Year,
Week = dict.Week,
Source = dict.SourceId
}, grp => grp.Total);
Create a partial class as follows:
public partial class WeeklyGraphGroup
{
public int ? Year { get; set; }
public int ? Week { get; set; }
public int Source { get; set; }
}
public partial class WeeklyGraphGroup
{
private int ? _Total;
public int ? Total
{
get
{
this._Total = CalculateTotal(this.Year, this.Week, this.Source);
return this._Total;
}
}
public int ? CalculateTotal(int ? Year, int ? Week, int Source)
{
// do your calculation and return the value of total
// use whatever formula you want here. I guess you are calculating
// total based on any of the parameters(year, week or source);
return value;
}
}
Then do your query as below:
var list = db.Stores.GroupBy(o => new WeeklyGraphGroup
{
Year = SqlFunctions.DatePart("yyyy", o.DateCreated),
Week = SqlFunctions.DatePart("ww", o.DateCreated),
Source = o.SourceId,
})
.Select ( u => new WeeklyGraphGroup
{
Year = u.Key.Year,
Week = u.Key.Week,
Source = u.Key.Source
}
).ToList();
Total will be updated automatically
The dataset I have is :
Name Order ID
Summary 1 147
Software Functionality -9 211
I have this LINQ query:
string value = projectA.OrderByDescending(a => a.Order)
.ThenBy(a => a.Name)
.ToList()
.First()
.ID
.ToString();
The answer I get is 211, but I think the answer should be 147. Why does this happen?
Note: the Order field is of type string.
You get the output you have because your Order is a string. As proof, this sample will show that you get the correct output when it's an integer:
void Main()
{
var list = new List<Project>() { new Project() { Order = 1, Id = 147, Name = "Summary" }, new Project() { Order = -9, Id = 211, Name = "Software Functionality" } };
int value= list.OrderByDescending(a => a.Order).ThenBy(a => a.Name).ToList().First().Id;
Console.WriteLine (value);
}
public class Project
{
public int Order {get;set;}
public int Id {get;set;}
public string Name {get;set;}
}
However, in case you do need it as a string: why doesn't it work like this?
Take a look at the CompareOptions enum. More specificically: CompareOptions.IgnoreSymbols.
Indicates that the string comparison must ignore symbols, such as white-space characters, punctuation, currency symbols, the percent sign, mathematical symbols, the ampersand, and so on. This is also explained here.
This essentially makes your -9 a 9.
You can bypass this easily by creating your own comparer and passing it what you need:
public class CustomComparer : IComparer<string>
{
public int Compare(string x,string y)
{
return CultureInfo.CurrentCulture.CompareInfo.Compare(x, y, CompareOptions.Ordinal);
}
}
which can be used as
new[] {"1", "-9"}.OrderByDescending(x => x, new CustomComparer())
I tried to reconstruct your issue. Here is my code (LinqPad), which works properly:
void Main()
{
List<ProgramA> progs = new List<ProgramA>{
new ProgramA("Summary", 1, 147),
new ProgramA("Software Functionality", -9, 211)
};
int value= progs.OrderByDescending(a => a.Order).ThenBy(a => a.Name).ToList().First().ID;
value.Dump();
}
// Define other methods and classes here
class ProgramA
{
private string sName = string.Empty;
private int iOrder = 0;
private int iID = 0;
public ProgramA(string _Name, int _Order, int _ID)
{
sName = _Name;
iOrder = _Order;
iID = _ID;
}
public string Name
{
get {return sName;}
set {sName = value;}
}
public int Order
{
get {return iOrder;}
set {iOrder = value;}
}
public int ID
{
get {return iID;}
set {iID = value;}
}
}
returns: 147
[EDIT]
#DStanley It is stored as "1" and "-9" in the list – user1989 6 mins ago
If Order is stored as string value, you can try to convert it into integer:
int value= progs.OrderByDescending(a => Convert.ToInt32(a.Order)).ThenBy(a => a.Name).ToList().First().ID;
string value= projectA.OrderByDescending(a => a.Order).ThenBy(a => a.Name).ToList().First().ID.ToString();
OrderByDescending => Orders
Output - -9, 1
Select First element
- 9 = 211. Logic is correct.
I'm quite new with C#. I have a function which gives me the results in an array like this
[0] => value
[1] => value
[2] => value
[3] => value
[4] => value
But in my case, [0] [1] [2] need to be together, and I show them in a table in PHP.
This is my function:
public List<string> ReadOpenCalls(int relation)
{
IQAssemblyResolver.Initialize(#"C:\Program Files\.....");
IQSDK IQSDK = new IQSDK();
string loginTekst = IQSDK.Login("Administrator", "..", "..").GetResult();
SDKRecordset inboundSet = IQSDK.CreateRecordset("R_ACTIONSCOPE", "CODE, DESCRIPTION, DATECREATED", "FK_RELATION = " + relation, "");
var messages = new List<string>();
if (inboundSet != null && inboundSet.RecordCount > 0)
{
inboundSet.MoveFirst();
do
{
string code = inboundSet.Fields["CODE"].Value.ToString();
string desc = inboundSet.Fields["DESCRIPTION"].Value.ToString();
string date = inboundSet.Fields["DATECREATED"].Value.ToString();
messages.Add( code);
messages.Add( desc);
messages.Add( date);
inboundSet.MoveNext();
}
while (!inboundSet.EOF);
return messages;
}
messages.Add("Error niet gelukt");
return messages;// null;
}
I want the output to be something like this:
[0] => [0] => "desc"
[1] => "code"
[2] => "date"
So that I can output this in a nice way in my table in PHP.
A very quick fix:
var messages = new List<string[]>();
...
messages.Add( new string[] { code, desc, date});
But it also depends on what is easy to work with in PHP.
A better solution, probably, is to write a small class:
class MyMessage
{
public string Code { get; set; }
public string Desc { get; set; }
public string Date { get; set; }
}
And of course you'd have to change the method into one of:
public List<string[]> ReadOpenCalls(int relation) ...
public List<MyClass> ReadOpenCalls(int relation) ...
and that change also needs to be made in the ServiceContract etc.
PHP induces bad practices and a horrible understanding of data structures. Please do not try to pattern anything after whatever you saw or did in PHP. In particular, using array indices as a substitute for proper members.
Have you considered writing a class that has three fields to describe a message?
Ideally you want to change the existing function, however using System.Linq you could alter your results after you've generated them.
var messages = ReadOpenCalls(relation);
var new messages = messages.Select((a,i) => new {index = i / 3, value = a})
.GroupBy (y => y.index, y => y.value)
.Select(g => g.ToList())
.ToList();
However you would be far better altering your function
either add the list directly
public List<List<string>> ReadOpenCalls(int relation)
{
IQAssemblyResolver.Initialize(#"C:\Program Files\.....");
IQSDK IQSDK = new IQSDK();
string loginTekst = IQSDK.Login("Administrator", "..", "..").GetResult();
SDKRecordset inboundSet = IQSDK.CreateRecordset("R_ACTIONSCOPE", "CODE, DESCRIPTION, DATECREATED", "FK_RELATION = " + relation, "");
var messages = new List<List<string>>();
if (inboundSet != null && inboundSet.RecordCount > 0)
{
inboundSet.MoveFirst();
do
{
string code = inboundSet.Fields["CODE"].Value.ToString();
string desc = inboundSet.Fields["DESCRIPTION"].Value.ToString();
string date = inboundSet.Fields["DATECREATED"].Value.ToString();
messages.Add(new List<string> { code, desc, date});
inboundSet.MoveNext();
}
while (!inboundSet.EOF);
return messages;
}
messages.Add("Error niet gelukt");
return messages;// null;
}
or make a class to hold the values and return a list of the class
UPDATE - OP has stated this is in a webservice so I've added the DataContract and DataMember attributes. You will need to make sure the project references System.Runtime.Serialization
using System.Runtime.Serialization;
[DataContract]
public class Class {
public Class(string code, string desc, string date) {
this.code = code;
this.desc = desc;
this.date = date;
}
[DataMember]
public string Code { get;set; }
[DataMember]
public string Desc { get;set; }
[DataMember]
public string Date { get;set; }
}
And then your altered function
public List<Call> ReadOpenCalls(int relation)
{
IQAssemblyResolver.Initialize(#"C:\Program Files\.....");
IQSDK IQSDK = new IQSDK();
string loginTekst = IQSDK.Login("Administrator", "..", "..").GetResult();
SDKRecordset inboundSet = IQSDK.CreateRecordset("R_ACTIONSCOPE", "CODE, DESCRIPTION, DATECREATED", "FK_RELATION = " + relation, "");
var messages = new List<Call>();
if (inboundSet != null && inboundSet.RecordCount > 0)
{
inboundSet.MoveFirst();
do
{
string code = inboundSet.Fields["CODE"].Value.ToString();
string desc = inboundSet.Fields["DESCRIPTION"].Value.ToString();
string date = inboundSet.Fields["DATECREATED"].Value.ToString();
messages.Add( new Call(code,desc, date));
inboundSet.MoveNext();
}
while (!inboundSet.EOF);
return messages;
}
messages.Add("Error niet gelukt");
return messages;// null;
}
Try this,
Change
public List<string> ReadOpenCalls(int relation)
to
public List<List<string>> ReadOpenCalls(int relation)
Change
var messages = new List<string>();
to
var messages = new List<List<string>>();
And change
messages.Add( code);
messages.Add( desc);
messages.Add( date);
to
List<string> list = new List<string>();
list.Add( code);
list.Add(desc);
list.Add(date);
messages.Add(list);
Change function return value to string[][] and return the jagged array however you like.
When I use the system.datetime as my datamember it returns a strange format in my datagridview.
My Datamembers looks like this:
[DataMember(Name = "TimeAdded")]
public DateTime TimeAdded;
And my AddStudent looks like this:
public void AddStudent(Student student)
{
student.StudentID = (++eCount).ToString();
student.TimeAdded = DateTime.Now; // not important more so the method to get the correct format
students.Add(student);
}
The output I get is 2012-04-12T03:10:42.8138255+01:00 is there a way to fix this?
I think you want:
student.TimeAdded = DateTime.Now;
Right now you're just converting a null time to local time.
Assuming you are referring to the fact that your dates are set to default(DateTime), then you want yamen's answer, if you don't like the format the DateTime is showing, then add another property onto Student that uses a standard DateTime format string like so.
public class Student
{
// existing date, don't expose this
public DateTime TimeAdded;
// expose this instead
[DataMember(Name = "TimeAddedString")]
public string TimeAddedString
{
//show as "Monday, January 01, 0001 12:00 AM"
get { return this.TimeAdded.ToString("f"); }
}
}
public class Student
{
public string TimeAddedString
{
get
{
return this.TimeAdded.ToString("dd/MM/yyyy hh:mm:ss");
// your desired format can goes here
}
}
}
public void AddStudent(Student student)
{
student.StudentID = (++eCount).ToString();
student.TimeAdded = DateTime.Now; // or your desired datetime
students.Add(student);
}
If you are converting incoming student time added you need to re-assign it:
public void AddStudent(Student student)
{
student.StudentID = (++eCount).ToString();
student.TimeAdded = student.TimeAdded.ToLocalTime();
students.Add(student);
}
01.02.2010 0:00:00 -> 01.02.2010 anytime
01.02.2010 0:00:00 -> 01.02.2010 0:any minutes and seconds
so here is my date :
DateTime x;
it's
01.02.2010 0:00:00
as a string
x.Date.ToString()
here I compare date
DatarowsForOneDay = dt.Select("DailyRecTime= '" + x.ToString() + "'");
So how can I group by date + Hours without care about minutes and seconds.
You could write your own IEqualityComparer<DateTime> to only compare the parts of the DateTime you care about. LINQ's GroupBy has an overload that takes an IEqualityComparer. I had the same problem recently and did just that.
But you would have to call GroupBy before converting to strings. If you can't then you might want to create an IEqualityComparer<string> and parse the strings back to DateTime before comparing.
I don't have the original code with me right now. I re-typed this from memory and did not test it.
public class DateAndHourComparer : IEqualityComparer
{
public bool Equals(DateTime x, DateTime y)
{
var xAsDateAndHours = AsDateHoursAndMinutes(x);
var yAsDateAndHours = AsDateHoursAndMinutes(y);
return xAsDateAndHours.Equals(yAsDateAndHours);
}
private DateTime AsDateHoursAndMinutes(DateTime dateTime)
{
return new DateTime(dateTime.Year, dateTime.Month,
dateTime.Day, dateTime.Hour,
dateTime.Minute, 0);
}
public int GetHashCode(DateTime obj)
{
return AsDateHoursAndMinutes(obj).GetHashCode();
}
}
I never did the string based version, but it could use the above DateTime based code and look something like...
public class DateAndHourStringComparer : IEqualityComparer
{
private readonly DateAndHourComparer dateComparer = new DateAndHourComparer();
public bool Equals(string x, string y)
{
var xDate = DateTime.Parse(x);
var yDate = DateTime.Parse(y);
return dateComparer.Equals(xDate, yDate);
}
public int GetHashCode(string obj)
{
var date = DateTime.Parse(obj);
return dateComparer.GetHashCode(date);
}
}
I have not tested it, I did not add null checks or format checks. The code is meant to demonstrate the general idea.
You can pass a parameter with DateTime.ToString(string pattern).
More information # http://www.geekzilla.co.uk/View00FF7904-B510-468C-A2C8-F859AA20581F.htm.