Related
the information that the user enters can be sorted in many ways ascending and descending and i'm trying to make the user choose how he want to see the data:
so is there any way to set a variable that the user enter and sort the data without repeating the code multiple time depending on the input like:
var empName = el.Select(i => new { i.ID, i.FullName });
if(emsort.Text="text1")
empName.OrderBy(i.text1);
else if...
by doing something shorter like:
string sort=emsort.Text ;
empName.OrderBy(sort);
If I understood your problem right, that you want to sort dynamically with the property name which is stored in emsort.Text, then you can use Expressions:
assuming empName is IEnumerable<Employee>, then use this:
private static Func<Employee, dynamic> GetSortable(string sortablePoperty)
{
var param = Expression.Parameter(typeof(Employee), "e");
var member = Expression.Property(param, sortablePoperty);
Expression memberAsObject = Expression.Convert(member, typeof(object));
return Expression.Lambda<Func<Employee, dynamic>>(memberAsObject, param).Compile();
}
then use it:
string sort=emsort.Text ;
empName.OrderBy(GetSortable(sort));
if empName is IQueryable<Employee>, then use this:
private static Expression<Func<Employee, dynamic>> GetSortable(string sortablePoperty)
{
var param = Expression.Parameter(typeof(Employee), "e");
var member = Expression.Property(param, sortablePoperty);
Expression memberAsObject = Expression.Convert(member, typeof(object));
return Expression.Lambda<Func<Employee, dynamic>>(memberAsObject, param);
}
If the User in your question is some software that calls your function, then the user is aware of the type of the objects in the el sequence: he knows which properties the elements in the el sequence are, and he knows how he wants them ordered.
Why not let this user give you the keySelector to use in the OrderBy?
Creating an extension function:
static class QueryableExtensions
{
IQueryable<MyType> SelectAndSort<TSource, TResult>(this IQueryable<TSource source,
Expression<Func<TSource, TResult>> selector,
Expression<Func<TSource, TKey>> sortKeySelector,
System.ComponentModel.ListSortDirection sortDirection)
{
var selectBeforeOrdering = myRepository.el.Select(selector);
// note: this is still an IQueryable, only the query has been made,
// the database is not accessed yet!
IQueryable<TResult> result = (sortDirection == ListSortDirectrion.Ascending) ?
// sort in ascending order:
selectBeforeOrdering.OrderBy(sortKeySelector) :
// else: selec in descending order:
selectBeforeOrdering.OrderByDescending(sortKeySelector);
return result;
}
}
Usage: suppose your user knows your el, and he want several fields of it, ordered by one of the properties of your el
using (var myDbContext = new DbContext(...))
{
IQueryable myEls = myDbContext.el;
var mySelectedItems = myEls.SelectAndSore(
// selector: what properties do I as a user want?
el => new
{
Id = el.Id,
FullName = el.FullName,
... // other desired properties
},
// sortSelector: how do I want my selected items to be sorted?
selectedItem => selectedItem.FullName,
// direction:
ListSortDirection.Descending);
// note: until now the database has not been accessed
// only the Expression of the query has been created.
// depending on what you want as a result: ToList / ToArray / First / ...
return mySelectedItems.ToList();
}
If, on the other hand your user is not software, but an operator, who has to select by which column he wants his items to be ordered, he has to have a method to tell the computer which column should be sorted.
This is usually done by clicking the column. Another method could be by selecting a value in a combo box. Anyway, you'll have to attach something to the column or to the combobox values that holds the sortKeySelector:
class MySortableColumn<TDisplayedProperty> : DataGridViewColumn
// or whatever column class you are using
{
public Expression<Func<MyDisplayedItem, TDisplayedProperty>> SortKeySelector{get;set;}
}
Now you have several columns that each display one of the properties of MyDisplayedItem:
var columnId = new MySortableColumn<int>()
{
SortKeySelector = displayedItem => myDisplayedItem.Id,
};
var columnFullName = new MyStortableColumn<string>()
{
SortKeySelector = displayedItem => myDisplayedItem.Id,
}
// etc.
Whenever the operator clicks on a column, the sortKey is extracted and used as parameter to sort the items:
void SortColumn(SortableColumn column)
{
var sortedItems = Sort(column.SortKeySelector, ListSortOrder...);
Display(sortedItems);
}
As I assume, el is materialized(i.e., for example, it is not just entry point to database), so you can use reflection:
var sort = emsort.Text;
PropertyInfo property = null;
var sortedData = el.Select(i => new { i.ID, i.FullName })
.OrderBy(x =>
{
property = property ?? x.GetType().GetProperty(sort);
return property.GetValue(x);
}).ToList();
this is a part of the code that i had to write to do it :
if (emord.Text == "Ascending")
{
if (emsort.Text == "ID")
{
var empName = el.Select(i => new { i.ID, i.FullName }).OrderBy(x => x.ID);
var empNamenAmp = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per }).OrderBy(x => x.ID);
var empNamenAmpnwh = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per, i.Hours }).OrderBy(x => x.ID);
var empNamenwh = el.Select(i => new { i.ID, i.FullName, i.Hours }).OrderBy(x => x.ID);
var empNamenbd = el.Select(i => new { i.ID, i.FullName, i.Date }).OrderBy(x => x.ID);
var empNamenad = el.Select(i => new { i.ID, i.FullName, i.Location }).OrderBy(x => x.ID);
var empNamenpn = el.Select(i => new { i.ID, i.FullName, i.PhoneNb }).OrderBy(x => x.ID);
var empNamena = el.Select(i => new { i.ID, i.FullName, i.Age }).OrderBy(x => x.ID);
if (empfilcomb.Text == "Name")
{
dispfil.Clear();
foreach (var x in empName)
dispfil.Text += x.ID + ", " + x.FullName + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Amount paid")
{
dispfil.Clear();
foreach (var x in empNamenAmp)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name,Amount paid and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenAmpnwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Birthday")
{
dispfil.Clear();
foreach (var x in empNamenbd)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Date + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Address")
{
dispfil.Clear();
foreach (var x in empNamenad)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Location + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Phone Number")
{
dispfil.Clear();
foreach (var x in empNamenpn)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.PhoneNb + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Age")
{
dispfil.Clear();
foreach (var x in empNamena)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Age + Environment.NewLine;
}
}
else if (emsort.Text == "Name")
{
var empName = el.Select(i => new { i.ID, i.FullName }).OrderBy(x => x.FullName);
var empNamenAmp = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per }).OrderBy(x => x.FullName);
var empNamenAmpnwh = el.Select(i => new { i.ID, i.FullName, i.Salary, i.Currency, i.Per, i.Hours }).OrderBy(x => x.FullName);
var empNamenwh = el.Select(i => new { i.ID, i.FullName, i.Hours }).OrderBy(x => x.FullName);
var empNamenbd = el.Select(i => new { i.ID, i.FullName, i.Date }).OrderBy(x => x.FullName);
var empNamenad = el.Select(i => new { i.ID, i.FullName, i.Location }).OrderBy(x => x.FullName);
var empNamenpn = el.Select(i => new { i.ID, i.FullName, i.PhoneNb }).OrderBy(x => x.FullName);
var empNamena = el.Select(i => new { i.ID, i.FullName, i.Age }).OrderBy(x => x.FullName);
if (empfilcomb.Text == "Name")
{
dispfil.Clear();
foreach (var x in empName)
dispfil.Text += x.ID + ", " + x.FullName + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Amount paid")
{
dispfil.Clear();
foreach (var x in empNamenAmp)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name,Amount paid and Work Hours")
{
dispfil.Clear();
foreach (var x in empNamenAmpnwh)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Salary + " " + x.Currency + " " + x.Per + ", " + x.Hours + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Birthday")
{
dispfil.Clear();
foreach (var x in empNamenbd)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Date + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Address")
{
dispfil.Clear();
foreach (var x in empNamenad)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Location + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Phone Number")
{
dispfil.Clear();
foreach (var x in empNamenpn)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.PhoneNb + Environment.NewLine;
}
else if (empfilcomb.Text == "Name and Age")
{
dispfil.Clear();
foreach (var x in empNamena)
dispfil.Text += x.ID + ", " + x.FullName + ", " + x.Age + Environment.NewLine;
}
}
and i was asking if there is a shorter way to do it:withing what i'm allowed to use. Seems that there isn't but thanks a lot for the help i'm sure what you guys provided is useful.
In my form I have four RadioButtons, based on user selection, this code is executed:
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
if (radioButtonName.Checked)
{
var Qr = from n in mylist where n.Name == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
if (radioButtonAge.Checked)
{
var Qr = from n in mylist where n.Age == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
if (radioButtonGender.Checked)
{
var Qr = from n in mylist where n.Gender == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
if (radioButtonOccupation.Checked)
{
var Qr = from n in mylist where n.Occu == textBoxSearch.Text select new { n.Name, n.Age, n.Occu, n.Gender };
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " " + " Occupation: " + item.Occu + " " + " Gender: " + item.Gender);
}
}
}
The code seems very redundant and repeated, but also I can't find a way to handle all 4 RadioButtons in a single line that has only one variable linked to user choice.
myList is a List of a class I created that has 4 string properties (Name, Age, Gender, Occu)
wrap everything in a function like this one:
public void foo(RadioButton radioButton, Expression<Func<MyItem, bool>> expression)
{
if (radioButton.Checked)
{
var Qr = mylist.AsQueryable().Where(expression).Select(x => String.Format("Name: {0}, Age: {1}, Occ: {2}, Gender: {3}", x.Name, x.Age, x.Occu, x.Gender)).ToList();
foreach (var item in Qr)
{
listBox1.Items.Add(item);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
foo(radioButtonName, c => c.Gender == textBoxSearch.Text);
foo(radioButtonAge, c => c.Age == textBoxSearch.Text);
foo(radioButtonGender, c => c.Gender == textBoxSearch.Text);
foo(radioButtonOccupation, c => c.Occu == textBoxSearch.Text);
}
public class MyItem
{
public String Occu { get; set; }
public String Age { get; set; }
public String Name { get; set; }
public String Gender { get; set; }
}
The only difference is in filter (where) all the other can be combined:
private void button1_Click(object sender, EventArgs e) {
var lines = mylist
.Where(item => radioButtonName.Checked && item.Name == textBoxSearch.Text ||
radioButtonAge.Checked && item.Age == textBoxSearch.Text ||
radioButtonGender.Checked && item.Gender == textBoxSearch.Text ||
radioButtonOccupation.Checked && item.Occu == textBoxSearch.Text)
.Select(item => string.Format("Name: {0} Age: {1} Occupation: {2} Gender: {3}",
item.Name, item.Age, item.Occu, item.Gender));
listBox1.Items.Clear();
foreach (string line in lines)
listBox1.Items.Add(line);
}
You can use a dictionary to map the RadioButton to its Filter in one go. Assuming MyClass is the type of objects in your List:
private void button1_Click(object sender, EventArgs e)
{
var mapping = new Dictionary<RadioButton, Func<MyClass, bool>>()
{
{ radioButtonName , x => x.Name == textBoxSearch.Text },
{ radioButtonAge, x => x.Age == textBoxSearch.Text },
{ radioButtonGender, x => x.Gender == textBoxSearch.Text},
{ radioButtonOccupation, x => x.Occu == textBoxSearch.Text}
};
foreach(var map in mapping.Where(x=> x.Key.Checked))
{
var Qr = mylist.Where(map.Value).Select(n=> new {n.Name, n.Age, n.Occu, n.Gender});
foreach (var item in Qr)
{
listBox1.Items.Add("Name: " + item.Name + " " + " Age: " + item.Age + " "
+ " Occupation: " + item.Occu + " " + " Gender: "
+ item.Gender);
}
}
}
This way you can easily plug new Radio Buttons using one simple line in the dictionary.
You could first generate an anonymous list of your radio buttons and where predictions and then iterate through it (MyItem in this case is sample/placeholder for what your list contains, since I don't know the actual class name):
private void button1_Click(object sender, EventArgs e)
{
// Generate anonymous list of objects that are different
var radios = new[]
{
new { RadioButton = radioButtonName, CallBack = new Func<MyItem, bool>(x => x.Name == textBoxSearch.Text) },
new { RadioButton = radioButtonAge, CallBack = new Func<MyItem, bool>(x => x.Age == textBoxSearch.Text) },
new { RadioButton = radioButtonGender, CallBack = new Func<MyItem, bool>(x => x.Occu == textBoxSearch.Text) },
new { RadioButton = radioButtonOccupation, CallBack = new Func<MyItem, bool>(x => x.Gender == textBoxSearch.Text) },
};
// Iterate through list and add items to ListBox1, if RadioButtton is checked
listBox1.Items.Clear();
foreach (var radio in radios)
{
if (!radio.RadioButton.Checked)
{
continue;
}
var Qr = mylist.Where(radio.CallBack).Select(n => new { n.Name, n.Age, n.Occu, n.Gender });
foreach (var item in Qr)
{
listBox1.Items.Add($"Name: {item.Name} Age: {item.Age} Occupation: {item.Occu} Gender: {item.Gender}");
}
}
}
I am having some issues with doing a search. I am trying to find a customer on either Name+Lastname, reference or CompanyName. I managed to get it working, however the the ordering is not 100%.
Here is my original object
var q = from row in DataAccess.metadata.db_Customer
where row.accountID == accountID
&& row.isActive
orderby row.Name
select new bl_customerNames
{
customerID = row.customerID,
CustomerName = row.Name + " " + row.LastName,
Company = row.Company,
Reference = row.reference,
Email = row.Email,
CurrencyCode = row.CurrencyCode,
isSuspended = row.isSuspended
};
Then I did the following filter.
if (!string.IsNullOrEmpty(search))
{
q = q.Where(x => (x.CustomerName.Contains(search) || x.Reference.Contains(search) || x.Company.Contains(search)));
}
Although this does a fair job the list does not really make logical sense as its does not favor starts with above Contains.
I then came up with a ranking solution where I would rank Starts with higher than Contains.
Here is my code for that:
var q = from row in DataAccess.metadata.db_Customer
where row.accountID == accountID
&& row.isActive
orderby row.Name
select new bl_customerNames
{
customerID = row.customerID,
CustomerName = row.Name + " " + row.LastName,
Company = row.Company,
Reference = row.reference,
Email = row.Email,
CurrencyCode = row.CurrencyCode,
isSuspended = row.isSuspended,
Rank = ((row.Name + " " + row.LastName).StartsWith(search) || row.reference.StartsWith(search) || row.Company.StartsWith(search)) ? 1 : ((row.Name + " " + row.LastName).Contains(search) || row.reference.Contains(search) || row.Company.Contains(search)) ? 2 : 0
};
I would then filter like this:
q = q.Where(r=>r.Rank > 0).OrderBy(r => r.Rank);
However if I search on reference it does not return anything.
So My question is will my current method work also why does nothing return if I search on reference or Company Name? What would the correct way of doing a StartsWith then Contains and still keeping some kind of alphabetical ordering after StartsWith List.
Please keep in mind I am still a junior developer and any help would be appreciated.
I went back to the basics and this is what I came up with.
//Get customers that starts with
var qSW = from row in DataAccess.metadata.db_Customer
where row.accountID == accountID
&& row.isActive
where (row.Name + " " + row.LastName).StartsWith(search)
|| row.Company.StartsWith(search)
|| row.reference.StartsWith(search)
select new bl_customerNames
{
customerID = row.customerID,
CustomerName = row.Name + " " + row.LastName,
Company = row.Company,
Reference = row.reference,
Email = row.Email,
CurrencyCode = row.CurrencyCode,
isSuspended = row.isSuspended
};
//Order
qSW = qSW.OrderBy(r => r.CustomerName).ThenBy(r => r.Company).ThenBy(r => r.Reference);
//Get customers that contains
var qC = from row in DataAccess.metadata.db_Customer
where row.accountID == accountID
&& row.isActive
where (row.Name + " " + row.LastName).Contains(search)
|| row.Company.Contains(search)
|| row.reference.Contains(search)
select new bl_customerNames
{
customerID = row.customerID,
CustomerName = row.Name + " " + row.LastName,
Company = row.Company,
Reference = row.reference,
Email = row.Email,
CurrencyCode = row.CurrencyCode,
isSuspended = row.isSuspended
};
//If search is less then 3 chars limit to 10 to help with performance
if(search.Count() > 2)
qC = qC.OrderBy(r => r.CustomerName).ThenBy(r => r.Company).ThenBy(r => r.Reference);
else
qC = qC.OrderBy(r => r.CustomerName).ThenBy(r => r.Company).ThenBy(r => r.Reference).Take(10);
//Concat list leaving out duplicates
var SList = qSW.Concat(qC);
// Return List
return SList.ToList();
It seems to be doing the job and performance seems very good.
var query = from str in strdizi
where str.StartsWith(str.Substring(0, 1))
orderby str
group str by str.Substring(str.LastIndexOf(Convert.ToChar(str.Substring(0, 1))))
into ws
where ws.Count() >= 1
select ws;
foreach (var item in query)
{
Console.WriteLine(item.Key + " " + item.Count());
foreach (var items in item)
{
Console.WriteLine(items);
}
}
but the output is;
Andy 1
Andy
Arthur 1
Arthur
I want ;
A - 1
Andy
Arthur
Ashlynn
Thanks for helping. Sory for my bad english.
You don't need the where condition, it is enough to group the data by substring:
var strdizi = new string [] {"Andy", "Arthur", "Ashlynn", "Ben", "Chris"};
var query = from str in strdizi
group str by str.Substring(0, 1 )
into ws
where ws.Count() >= 1
select ws;
foreach (var item in query)
{
Console.WriteLine(item.Key + " " + item.Count());
foreach (var items in item)
{
Console.WriteLine(items);
}
}
This gives the results:
A 3
Andy
Arthur
Ashlynn
B 1
Ben
C 1
Chris
As I prefer the method syntax over query syntax, this is how you would do it:
var strdizi = new string [] {"Andy", "Arthur", "Ashlynn", "Ben", "Chris"};
var query = strdizi.GroupBy(g=>g.Substring(0,1));
foreach (var item in query)
{
Console.WriteLine(item.Key + " " + item.Count());
foreach (var items in item)
{
Console.WriteLine(items);
}
}
class Program
{
static void Main(string[] args)
{
string s = "Stack Overflows";
var x = from c in s.ToLower()
group c by c into a
select new { a.Key, Count = a.Count() };
Console.WriteLine(Convert.ToString(x));
Console.Read();
}
}
Output is system.linq.Enumable+
i want output like a 2 g 1 s 1 p 2 r 1
Console.WriteLine(String.Join(" ", x.Select(y=>y.Key + " " + y.Count)));
or using lambda syntax
string s = "Stack Overflows";
Console.WriteLine(String.Join(" ", s.GroupBy(c => c)
.Select(g => g.Key + " " + g.Count())));
You can also use aggregate function like this:
Console.WriteLine(x.Select(y => String.Format("{0} {1}", y.Key, y.Count)).Aggregate((y, z) => y + String.Format(" {0}", z)));
Aggregate function can be used for any types (not only strings)
Try this code instead of your code, I have modified #L.B code
string s = "Stack Overflows";
var x = String.Join("", (from c in s.ToLower()
group c by c into a
select new { a.Key, Count = a.Count() }).Select(y => y.Key + " " + y.Count));