I am attempting to get unique values in a list of similar value distinguished only by a one element in a pipe delimited string... I keep getting at least one object must implement Icomparable. I don't understand why I keep getting that. I am able to groupBy that value... Why can't I find the max... I guess it is looking for something to compare it with. If I get the integer version will it stop yelling at me? This is the last time I am going to try using LINQ...
var queryResults = PatientList.GroupBy(x => x.Value.Split('|')[1]).Select(x => x.Max());
I know I can get the unique values some other way. I am just having a hard time figuring it out. In that List I know that the string with the highest value amongst its similar brethren is the one that I want to add to the list. How can I do that? I am totally drawing a blank because I have been trying to get this to work in linq for the last few days with no luck...
foreach (XmlNode node in nodeList)
{
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(node.OuterXml);
string popPatInfo = xDoc.SelectSingleNode("./template/elements/element[#name=\"FirstName\"]").Attributes["value"].Value + ", " + xDoc.SelectSingleNode("./template/elements/element[#name=\"LastName\"]").Attributes["value"].Value + " | " + DateTime.Parse(xDoc.SelectSingleNode("./template/elements/element[#name=\"DateOfBirth\"]").Attributes["value"].Value.Split('T')[0]).ToString("dd-MMM-yyyy");
string patientInfo = xDoc.SelectSingleNode("./template/elements/element[#name=\"PatientId\"]").Attributes["value"].Value + "|" + xDoc.SelectSingleNode("./template/elements/element[#name=\"PopulationPatientID\"]").Attributes["enc"].Value;// +"|" + xDoc.SelectSingleNode("./template/elements/element[#name=\"AdminDate\"]").Attributes["value"].Value;
int enc = Int32.Parse(patientInfo.Split('|')[1]);
if (enc > temp)
{
lastEncounter.Add(enc, patientInfo);
temp = enc;
}
//lastEncounter.Add(Int32.Parse(patientInfo.Split('|')[1]));
PatientList.Add( new SelectListItem { Text = popPatInfo, Value = patientInfo });
}
I was thinking about using some kind of temp variable to find out what is the highest value and then add that string to the List. I am totally drawing a blank however...
Here I get the IDs in an anonymous type to make it readable.
var patientEncounters= from patient in PatientList
let PatientID=Int32.Parse(patient.Value.Split('|')[0])
let EncounterID=Int32.Parse(patient.Value.Split('|')[1])
select new { PatientID, EncounterID };
Then we group by UserID and get the last encounter
var lastEncounterForEachUser=from pe in patientEncounters
group pe by pe.PatientID into grouped
select new
{
PatientID=grouped.Key,
LastEncounterID=grouped.Max(g=>g.EncounterID)
};
Linq doesn't know how to compare 2 Patient objects, so it can't determine which one is the "greatest". You need to make the Patient class implement IComparable<Patient>, to define how Patient objects are compared.
// Compare objets by Id value
public int CompareTo(Patient other)
{
return this.Id.CompareTo(other.Id);
}
Another option is to use the MaxBy extension method available in Jon Skeet's MoreLinq project:
var queryResults = PatientList.GroupBy(x => x.Value.Split('|')[1])
.Select(x => x.MaxBy(p => p.Id));
EDIT: I assumed there was a Patient class, but reading your code again, I realize it's not the case. PatientList is actually a collection of SelectListItem, so you need to implement IComparable in that class.
Related
I have a List of a Simple Struct that contains int's and strings.
Struct:
public struct ErrorType
{
public int RowNumber;
public int ColumnNumber;
public string ErrorMessage;
}
On of My methods return A list of these structs.
I want to convert each member in the list to its string form and separate it by a comma so that List is now an Array of strings. I could write a function to do this manually but I'd perfer to use linq to have a cleaner solution.
Having the array of strings I'll write it to a file using
string path = #"c:\temp\MyTest.txt";
// This text is added only once to the file.
if (!File.Exists(path))
{
// Create a file to write to.
List<ErrorType> ErrorTypesList = GetErrors();
//do some work to iterate over the members and do a string.join after
string[] ErrorListArray = ErrorTypesList.foreach( e => { });
File.WriteAllLines(path, createText);
}
does anyone have suggestions on how to fill in the foreach so that it returns each members in its ToString form followed by a comma?
The only way you'd be able to use LINQ for property iteration is if you're using reflection, and you shouldn't do that unless you absolutely have to (which it really doesn't look like you do).
The best you can do, though, still isn't too bad.
var rows = ErrorTypesList
.Select(c => "\"" + string.Join("\",\"", c.RowNumber, c.ColumnNumber, c.ErrorMessage) + "\"");
File.WriteAllLines(path, rows);
I took the liberty of putting fields in quotes as well, you may or may not want that, but it's easy to fix that code to do what you do want. You might also want to add in some escape logic, pending what sorts of contents c.ErrorMessage might have.
You could also use string.Format if you're more comfortable with that, but it doesn't really make a difference here.
var rows = ErrorTypesList
.Select(c => string.Format("\"{0}\", \"{1}\", \"{2}\"", c.RowNumber, c.ColumnNumber, c.ErrorMessage));
It seems you want something like this:
string[] ErrorListArray = ErrorTypesList
.Select(e => string.Join(",", e.RowNumber, e.ColumnNumber, e.ErrorMessage))
.ToArray();
Basically all I'm looking to do is something like the following:
string EntityFrameworkType = "Product";
string searchField = "ProductName";
string searchValue = "My Product";
using( var context = new entitycontext())
{
var result = (from x in context.EntityFrameworkType.Where(l=>l.searchField == searchValue) select x).FirstOrDefault();
}
of course this syntax won't work because context does not contain an entity called "EntityFrameworkType"...
Is it possible to do this another way??? What I'm looking to do in generalize my database duplicate check. In this example, I'm searching for any Product with the Name "My Product". But I'd like to be able to pass in these string for say, ProductCategory with ProductCategoryId = 1.... etc...
you can have a look here to get the idea of how it is done.
You'll need to learn about Expression
Let's say I have a table that I can query with EF + LINQ like this:
var results = dbContext.MyTable.Where(q => q.Flag = true);
Then, I know that if I want to limit the columns returned, I can just add a select in that line like this:
var results = dbContext.MyTable
.Select(model => new { model.column2, model.column4, model.column9 })
.Where(q => q.Flag == true);
The next step that I need to figure out, is how to select those columns dynamically. Said another way, I need to be able to select columns in a table withoutknowing what they are at compile time. So, for example, I need to be able to do something like this:
public IEnumerable<object> GetWhateverColumnsYouWant(List<string> columns = new List<string{ "column3", "column4", "column999"})
{
// automagical stuff goes here.
}
It is important to keep the returned record values strongly typed, meaning the values can't just be dumped into a list of strings. Is this something that can be accomplished with reflection? Or would generics fit this better? Honestly, I'm not sure where to start with this.
Thanks for any help.
I think you want some dynamic linq, Im no expert on this but i THINK it will go something like this
public static IEnumerable<object> GetWhateverColumnsYouWant(this IQueriable<T> query, List<string> columns = new List<string{ "column3", "column4", "column999"})
{
return query.Select("new (" + String.Join(", ", columns) + ")");
}
See scott Gu's blog here http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx and this question System.LINQ.Dynamic: Select(" new (...)") into a List<T> (or any other enumerable collection of <T>)
You could probably also do this by dynamically composing an expression tree of the columns youre wanting to select but this would be substantially more code to write.
I have the following snippet that I currently use to run a .Contains() with a list of Ids passed as a comma separated list from users. This code works perfectly and the data is filtered exactly as I want it to be:
// Handle id in() statements explicitly, dynamic expression can't parse them
var idIn = new Regex("id in ?(.*)", RegexOptions.IgnoreCase);
if (idIn.IsMatch(predicate))
{
Match list = new Regex(#"in ?\((.*)\)", RegexOptions.IgnoreCase).Match(predicate);
string ins = list.Groups[1].ToString();
// Split ins and store as List<>
List<int> splitValues = ins.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(i => Convert.ToInt32(i)).ToList();
return source.Where(u => splitValues.Contains(u.Id));
}
I want to be able to use this same idea, except with ANY property of the u object using reflection. I had a version of this working at some point, but cannot for the life of me figure out what has changed or why it stopped working. Here is the version I have that I cannot get working again:
Match splitIn = new Regex(#"([a-zA-Z0-9\.]*) IN ?\((.*)\)", RegexOptions.IgnoreCase).Match(predicate);
string property = splitIn.Groups[1].ToString();
string ins = splitIn.Groups[2].ToString().Trim(new[] {'\'', '"'}); // Trim off separator quotes
List<string> splitValues = ins.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList();
for (int i = 0; i < splitValues.Count; i++)
{
splitValues[i] = splitValues[i].Trim(new[] {'\'', '"'});
}
Expression<Func<U, bool>> contains = u => ListContainsProperty(u, splitValues, property);
return source.Where(contains);
private static bool ListContainsProperty<U>(U u, ICollection<string> list, string property)
{
string[] split = property.Split(new[] {"."}, StringSplitOptions.RemoveEmptyEntries);
object value = split.Aggregate<string, object>(u, (current, prop) => current.GetType().GetProperty(prop).GetValue(current, null));
return list.Contains(value.ToString());
}
As I said I once had SOME version of this working, but cannot figure out what has changed. Is there something blatantly obvious that I am missing that would help me get this functional again?
Edit: As far as I can tell the ListContainsProperty method is never actually running. Adding a "throw new Exception()" does nothing. I just get the full unfiltered list back.
I think the underlying problem is using "Expression"
you need to compile an Expression.
For example in your code
Expression<Func<U, bool>> contains = u => ListContainsProperty(u, splitValues, property);
is data and not a function. In order to use it you need to compile it.
Func<U, bool> compiled = contains.Compile();
"compiled" variable will call the "ListContainsProperty" method.
I have a program with multiple int variables where individual counts are added to the specific variable each time a set fail condition is encountered. I want the user to be able to track how many failures of each category they have encountered by a button click. I want to display the range on a datagridview in order from highest value integer down to lowest. I also need to display in the adjacent column the name of the test step that relates to the value. My plan was to use Array.sort to order the integers but i then lose track of their names so cant assign the adjacent string column. I tried using a hashtable but if i use the string as a key it sorts alphabetically not numerically and if i use the integer as a key i get duplicate entries which dont get added to the hash table. here is some of the examples i tried but they have the aforementioned problems. essentially i want to end with two arrays where the order matches the naming and value convention. FYI the variables were declared before this section of code, variables ending in x are the string name for the (non x) value of the integer.
Hashtable sorter = new Hashtable();
sorter[download] = downloadx;
sorter[power] = powerx;
sorter[phase] = phasex;
sorter[eeprom] = eepromx;
sorter[upulse] = upulsex;
sorter[vpulse] = vpulsex;
sorter[wpulse] = wpulsex;
sorter[volts] = voltsx;
sorter[current] = currentx;
sorter[ad] = adx;
sorter[comms] = commsx;
sorter[ntc] = ntcx;
sorter[prt] = prtx;
string list = "";
string[] names = new string[13];
foreach (DictionaryEntry child in sorter)
{
list += child.Value.ToString() + "z";
}
int[] ordered = new int[] { download, power, phase, eeprom, upulse, vpulse, wpulse, volts, current, ad, comms, ntc, prt };
Array.Sort(ordered);
Array.Reverse(ordered);
for (int i = 0; i < sorter.Count; i++)
{
int pos = list.IndexOf("z");
names[i] = list.Substring(0, pos);
list = list.Substring(pos + 1);
}
First question here so hope its not too longwinded.
Thanks
Use a Dictionary. And you can order it by the value : myDico.OrderBy(x => x.Value).Reverse(), the sort will be numerical descending. You just have to enumerate the result.
I hope I understand your need. Otherwise ignore me.
You want to be using a
Dictionary <string, int>
to store your numbers.I'm not clear on how you're displaying results at the end - do you have a grid or a list control?
You ask about usings. Which ones do you already have?
EDIT for .NET 2.0
There might be a more elegant solution, but you could implement the logic by putting your rows in a DataTable. Then you can make a DataView of that table and sort by whichever column you like, ascending or descending.
See http://msdn.microsoft.com/en-us/library/system.data.dataview(v=VS.80).aspx for example.
EDIT for .NET 3.5 and higher
As far as sorting a Dictionary by its values:
var sortedEntries = myDictionary.OrderBy(pair => pair.Value);
If you need the results to be a Dictionary, you can call .ToDictionary() on that. For reverse order, use .OrderByDescending(pair => pair.Value).