Use linq to sort on changed field - c#

I have a linq query using C# and would like to sort on a changed field. The field is a partial date field defined in the table as YYYYMM. However, I want it displayed in my repeater as,
MM/YYYY but need to sort it as YYYYMM. Below is the code and GrantMonthID is the field in question. The repeater displays the data in MM/YYYY order.
Thank you.
var linqQuery = from nn in grantsReceivedDetailList
where (nn.ArrearAuditID == Convert.ToInt32(AdminBasePage.ArrearAuditId))
select
new
{
nn.GrantsReceivedID,
nn.PayeeMPINumber,
Firstname = participantRepository.GetParticipantDetailsbyMemberParticipantIndex(Convert.ToInt32(nn.PayeeMPINumber)).FirstName + " " +
participantRepository.GetParticipantDetailsbyMemberParticipantIndex(Convert.ToInt32(nn.PayeeMPINumber)).LastName,
nn.IVANumber,
GrantMonthID = nn.GrantMonthID.ToString().Substring(4, 2) + "/" + nn.GrantMonthID.ToString().Substring(0, 4),
nn.GrantAmount,
nn.Comments
};
linqQuery = linqQuery.OrderByDescending(y => y.GrantMonthID);
// Execute the linq query and databind
grantListRepeater.DataSource = linqQuery;
grantListRepeater.DataBind();

Just create a property to sort on, and a property to bind to:
var query = from x in someList
select new
{
SortField = FormatForSort(x.Field),
DisplayField = FormatForDisplay(x.Field)
};
query = query.OrderBy(x => x.SortField);
Alternately, select it by the Date, and format it how you want in the repeater instead of using LINQ since this is more of a View concern.

Add to your anonymous type field which will contain original database date:
var linqQuery =
from nn in grantsReceivedDetailList
where (nn.ArrearAuditID == Convert.ToInt32(AdminBasePage.ArrearAuditId))
select new {
nn.GrantsReceivedID,
nn.PayeeMPINumber,
Firstname = participantRepository.GetParticipantDetailsbyMemberParticipantIndex(Convert.ToInt32(nn.PayeeMPINumber)).FirstName + " " +
participantRepository.GetParticipantDetailsbyMemberParticipantIndex(Convert.ToInt32(nn.PayeeMPINumber)).LastName,
nn.IVANumber,
GrantMonthID = nn.GrantMonthID.ToString().Substring(4, 2) + "/" +
nn.GrantMonthID.ToString().Substring(0, 4),
nn.GrantAmount,
nn.Comments,
GrantMonthIdOriginal = nn.GrantMonthID // this field
};
And sort by this field:
linqQuery = linqQuery.OrderByDescending(y => y.GrantMonthIdOriginal);
grantListRepeater.DataSource = linqQuery;
grantListRepeater.DataBind();

Related

An expression tree may not contain a call or invocation that uses option arguments in C# Linq

I am trying to do a case statement for one of the properties when selecting an anonymous type in the first part and then convert it to a list of my return type (retList). In the retList part at the bottom when I set QuarterName = p.QuarterName I get the following error on the DatePart functions from the section above:
An expression tree may not contain a call or invocation that uses
optional arguments
public static IEnumerable<Product> GetProducts(int categoryId)
{
using (var context = new DbContext())
{
var pList = (from p in context.Products
where (p.CategoryId == proformaId)
select new
{
Id = p.Id,
ProductName = p.ProductName,
QuarterName = pa.Quarter != "ExtraQuarter" ? "Q" + DateAndTime.DatePart(DateInterval.Quarter, p.PurchaseDate) +
"-" + DateAndTime.DatePart(DateInterval.Year, p.PurchaseDate) :
"<b><i>" + p.Quarter + "</i></b>"
}).ToList();
var retList = from p in pList
select new ProformaAssumption()
{
Id = pa.Id,
ProductName = p.ProformaId,
QuarterName = p.QuarterName
};
return retList;
}
The DatePart methods have additional, optional parameters. C# doesn't allow Expression Trees to leverage the optional parameters, so you'll need to provide the whole parameter list to each of these method calls.
According to the documentation, FirstDayOfWeek.Sunday and FirstWeekOfYear.Jan1 are the values that would be used if you didn't provide a value for the optional parameters.
QuarterName = pa.Quarter != "ExtraQuarter"
? "Q" +
DateAndTime.DatePart(DateInterval.Quarter, p.PurchaseDate,
FirstDayOfWeek.Sunday, FirstWeekOfYear.Jan1) +
"-" + DateAndTime.DatePart(DateInterval.Year, p.PurchaseDate,
FirstDayOfWeek.Sunday, FirstWeekOfYear.Jan1)
: "<b><i>" + p.Quarter + "</i></b>"

Concatenating two fields in LINQ select

I have a dropdownlist, ddCourse, that I'm populating with the following LINQ query:
var db = new DataClasses1DataContext();
ddCourse.DisplayMember = "COURSE_TITLE";
ddCourse.ValueMember = "COURSE_ID";
ddCourse.DataSource = db.COURSE_MASTERs.OrderBy(c => c.COURSE_TITLE)
.Select(c => new { c.COURSE_ID, c.COURSE_TITLE })
.ToList();
There's another field, though, that I'd like to concatenate to the COURSE_TITLE field in my selection. So, I'd like my selection to look like:
.Select( c => new {c.COURSE_ID, c.CIN + " " + c.COURSE_TITLE})
The only problem is that this, apparently, isn't how it's done. I'm basically wanting to join c.CIN with c.COURSE_TITLE (and have a space in the middle). Can someone offer me some pointers on how to accomplish this?
The reason I want to do this is that, right now, the only thing appearing in the dropdownlist is the course title. I'd like to have the course ID number (CIN) concatenated to it when it displays.
EDIT: For clarification, I'm using Linq-to-SQL.
use this
.Select( c => new {c.COURSE_ID, COURSE_TITLE =string.Format("{0} {1}" ,c.CIN ,c.COURSE_TITLE)})
You need to name your anonymous members:
.Select( c => new {COURSE_ID = c.COURSE_ID, COURSE_TITLE = c.CIN + " " + c.COURSE_TITLE})
Write your Select like this:
.Select( c => new {c.COURSE_ID, COURSE_TITLE = c.CIN + " " + c.COURSE_TITLE})
Anonymous types need to have their column names specified, in case they cannot be inferred.
For c.COURSE_ID C# is smart enough to generate a member called COURSE_ID in the anonymous type. For the expression c.CIN + " " + c.COURSE_TITLE it cannot.

ToString() method not supported

I am trying to create a selectlist in c#. My code :
var ceremonies = db.Ceremonies;
var Ceremonies =
from c in ceremonies
select new SelectListItem
{
Text = c.Name + "_" + c.Date,
Value = c.Id.ToString()
};
But here i am getting exception something like ToString() method not supported. Whats the problem ?
Your query is being transformed into SQL - but the call to ToString can't be handled properly. Generally the simplest way of fixing this is to effectively split the query into the part that needs to be done in the database, then switch to LINQ to Objects via AsEnumerable:
var ceremonies = db.Ceremonies
.Select(c => new { c.Name, c.Date, c.Id }
.AsEnumerable()
.Select(c => new SelectListItem {
Text = c.Name + "_" + c.Date,
Value = c.Id.ToString()
});
As an aside, declaring two local variables which vary only by case gives pretty nasty readability.

Override default sort in LINQ query using Array as Comparer

I have a list of ID numbers that I pull from a session variable and convert to an Array for use in a LINQ query that will then be used to populate a dropdown list. This is basically a list of recent clients that have been accessed by the program. I would like it to show them in the order they were last accessed.
Example array values (66, 78, 55, 24, 80)
After the LINQ query, it orders them by ID by default: (24, 55, 66, 78, 80)
So when it shows in the dropdownlist it is sorted this way when all I really needed was for it to stay in the order of the array values, only reversed: (80, 24, 55, 78, 66)
Code:
// Grab pre-built session variable with comma delimited int values
string RecentClients = Session["RecentClients"].ToString();
// Convert to Array
int[] RecentClientsArray =
Array.ConvertAll(RecentClients.Split(','), n => Convert.ToInt32(n));
// Display in gridview to see current order of array (For debug purposes)
gridArray.DataSource = RecentClientsArray;
gridArray.DataBind();
// Setup LINQ
DataClasses1DataContext db = new DataClasses1DataContext();
// Populate RecenClientList using RecentClientsArray to compare to ConsumerID
var RecentClientList =
(from c in db.ClientNames
where RecentClientsArray.Contains(c.ConsumerID)
select new { FullName = c.LastName + ", " + c.FirstName + " #" + c.ConsumerID,
recentConsumerID = c.ConsumerID});
// Display in gridview to see new order after populating var RecentClientList
// (for debug purposes)
gridList.DataSource = RecentClientList;
gridList.DataBind();
// Set and bind values for dropdown list.
ddLastClients.DataSource = RecentClientList;
ddLastClients.DataTextField = "FullName";
ddLastClients.DataValueField = "recentConsumerID";
ddLastClients.DataBind();
EDIT Corrected Code using accepted solution:
// Grab pre-built session variable with comma delimited int values
string RecentClients = Session["RecentClients"].ToString();
// Convert to Array
int[] RecentClientsArray = Array.ConvertAll(RecentClients.Split(','), n => Convert.ToInt32(n));
// Setup LINQ
DataClasses1DataContext db = new DataClasses1DataContext();
// Populate RecenClientList using RecentClientsArray to compare to ConsumerID
var RecentClientList = (from c in db.ClientNames
where RecentClientsArray.Contains(c.ConsumerID)
select new { FullName = c.LastName + ", " + c.FirstName + " #" + c.ConsumerID, recentConsumerID = c.ConsumerID });
var clients = (from item in RecentClientsArray
join client in RecentClientList.ToList()
on item equals client.recentConsumerID
select client).Reverse().Distinct();
// Set and bind values for dropdown list.
ddLastClients.DataSource = clients;
ddLastClients.DataTextField = "FullName";
ddLastClients.DataValueField = "recentConsumerID";
ddLastClients.DataBind();
I would leave your queries as you have written then and then handle the ordering in memory.
Given
var RecentClientsArray = ...
var RecentClientList = ...
You can use Enumerable.Join to bring these together. Join has the property of preserving the order of the outer sequence in the join operation. Use the join to get the initial order based on the array, and then use Reverse to fulfill the final part of your requirement. So you might simply have
var clients =
(from item in RecentClientsArray
join client in RecentClientList.ToList() // materialize result from db
on item equals client.recentConsumerID
select client).Reverse();
You should see the clients in the reverse order of the array, and then you would use this as the datasource on your controls.
I think you need to use OrderBy(Func<>, IComparer<>) and specify your own Comparer which will implement your (copied straight from stackoverflow, sorry)... no elegant solution for its implementation yet, tho
Try the below, I am using List<int> because I need access to IndexOf, which I use in the added orderby clause of the LINQ query :
List<int> RecentClientsArray = Array.ConvertAll(RecentClients.Split(','), n => Convert.ToInt32(n)).ToList();
// other code
var RecentClientList = (from c in db.ClientNames
where RecentClientsArray.Contains(c.ConsumerID)
orderby RecentClientsArray.IndexOf(c.ConsumerID) descending
select new { FullName = c.LastName + ", " + c.FirstName + " #" + c.ConsumerID, recentConsumerID = c.HorizonID });
My solution: use dictionary with reverse array items.
Here is my code:
// Grab pre-built session variable with comma delimited int values
string RecentClients = Session["RecentClients"].ToString();
// Convert to Array
int[] RecentClientsArray = Array.ConvertAll(RecentClients.Split(','), n => Convert.ToInt32(n));
//NEW- I use dictionary to get the right place in reverse
int ind = 0;
Dictionary<int, int> dic = new Dictionary<int, int>();
foreach (var item in RecentClientsArray.Reverse())
{
dic.Add(item, ind);
ind++;
}
// Display in gridview to see current order of array (For debug purposes)
gridArray.DataSource = RecentClientsArray;
gridArray.DataBind();
// Setup LINQ
DataClasses1DataContext db = new DataClasses1DataContext();
// Populate RecenClientList using RecentClientsArray to compare to ConsumerID
//NEW- use orderby with dictionary
var RecentClientList = (from c in db.ClientNames
where RecentClientsArray.Contains(c.ConsumerID)
orderby dic[c.ConsumerID]
select new { FullName = c.LastName + ", " + c.FirstName + " #" + c.ConsumerID, recentConsumerID = c.HorizonID, ID = c.ConsumerID });
// Display in gridview to see new order after populating var RecentClientList (for debug purposes)
gridList.DataSource = RecentClientList;
gridList.DataBind();
// Set and bind values for dropdown list.
ddLastClients.DataSource = RecentClientList;
ddLastClients.DataTextField = "FullName";
ddLastClients.DataValueField = "recentConsumerID";
ddLastClients.DataBind();
Good Luck,

Good way to sort object by date property in anonymous type?

I have the following code:
var resultArticles = from a in articleItems
select new
{
Title = a.Title,
ArticleDate = a[Constants.FieldNames.ArticleStartDate] != null ?
((DateTime)a[Constants.FieldNames.ArticleStartDate]).ToString(Constants.Date.Format): string.Empty,
ByLine = a[Constants.FieldNames.Byline],
FileRef = SPUtility.ConcatUrls(web.Url, a.Url)
};
var sortedArticles = resultArticles.OrderBy(a => a.ArticleDate).ToList();
rptArticles.DataSource = sortedArticles;
rptArticles.DataBind();
I guess there must be a better way to sort/order here because if I have the dates (dd/mm/yyy)
12.01.2011
11.02.2011
10.02.2011
13.01.2011
08.02.2011
it only sorts by the day and don't take the month into consideration so the result in sortedArticles is as follows:
08.01.2011
10.02.2011
11.02.2011
12.01.2011
13.01.2011
I obviously want to display the latest article first, i.e. 11.02.2011
Any suggestions?
Thanks in advance.
The problem is that in your Select, you're calling ToString on your date field. As a result ArticleDate is being projected as a string. This is why it's not sorting correctly.
Projecting the ArticleDate as a nullable Date is probably your best bet
var resultArticles = from a in articleItems
select new
{
Title = a.Title,
ArticleDate = a[Constants.FieldNames.ArticleStartDate] != null ?
((DateTime)a[Constants.FieldNames.ArticleStartDate]) : default(DateTime?),
ByLine = a[Constants.FieldNames.Byline],
FileRef = SPUtility.ConcatUrls(web.Url, a.Url)
};
Also, for something simple like this, you can use the more concise "dot notation"
var resultArticles = articleItems.Select(a => new {
Title = a.Title,
ArticleDate = a[Constants.FieldNames.ArticleStartDate] != null ?
((DateTime)a[Constants.FieldNames.ArticleStartDate]) : default(DateTime?),
ByLine = a[Constants.FieldNames.Byline],
FileRef = SPUtility.ConcatUrls(web.Url, a.Url)
};
At this point you can sort this collection by ArticleDate, which will be stored as a true Date, instead of a string
resultArticles.OrderBy(a => a.ArticleDate).ToList();
Use the following syntax
var q = from el in dataSource orderby el.SortField select new {
//your projection
};
The point here is to sort during the selection in one query.
Edit
By using this statement you sort by actual DateTime and project string representation.

Categories