MVC3 website. EF4.1 Code First.
I am saving the sort column and direction off to session, so that when the user comes back to the page the grid is still in the same sort order.
I want to be able to specify how to sort the collection with the values I have saved like so.
this.Issues = this.db.ITIssues.Orderby(this.sort + " " + this.sortdir)...
Currently I have to use a switch statement and handle every different combination of fields + sort direction.
Is there a better way?
switch (this.Sort)
{
case "ITApplication.Name":
if (this.SortDir == "ASC")
this.Issues = this.db.ITIssues.OrderBy(i => i.ITApplication.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
else
this.Issues = this.db.ITIssues.OrderByDescending(i => i.ITApplication.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
case "ITIssueType.Name":
if (this.SortDir == "ASC")
this.Issues = this.db.ITIssues.OrderBy(i => i.ITIssueType.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
else
this.Issues = this.db.ITIssues.OrderByDescending(i => i.ITIssueType.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
case "CurrentStatus.Name":
if (this.SortDir == "ASC")
this.Issues = this.db.ITIssues.OrderBy(i => i.CurrentStatus.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
else
this.Issues = this.db.ITIssues.OrderByDescending(i => i.CurrentStatus.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
case "CurrentAssignedTo.Fname":
if (this.SortDir == "ASC")
this.Issues = this.db.ITIssues.OrderBy(i => i.CurrentAssignedTo.Fname).Where(i => i.ITAppGroupID == this.ITAppGroupID);
else
this.Issues = this.db.ITIssues.OrderByDescending(i => i.CurrentAssignedTo.Fname).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
case "CreatedBy.Fname":
if (this.SortDir == "ASC")
this.Issues = this.db.ITIssues.OrderBy(i => i.CreatedBy.Fname).Where(i => i.ITAppGroupID == this.ITAppGroupID);
else
this.Issues = this.db.ITIssues.OrderByDescending(i => i.CreatedBy.Fname).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
case "CurrentPriority.Name":
if (this.SortDir == "ASC")
this.Issues = this.db.ITIssues.OrderBy(i => i.CurrentPriority.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
else
this.Issues = this.db.ITIssues.OrderByDescending(i => i.CurrentPriority.Name).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
default:
this.Issues = this.db.ITIssues.OrderByDescending(i => i.ID).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
}
I've had to do something similar recently, what you are looking for is Dynamic Linq! Take a look at this:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
There seems to be two obstacles with your code, the first involves getting a property name from the string represented by this.Sort. That's probably going to involve some reflection code. The second part involves shortening the amount of code needed to specify ascending sort vs descending sort.
You could do this is several ways. The first idea I thought of was to make an extension method for OrderBy which took in a bool SortDir argument. There are a few overloads for OrderBy allowing for Func<T,TKey> KeySelectors and an IComparer<T>. I'll show you the simple one with just a keySelector, but you may want to implement all of them for a fully generic solution
public static class IEnumerableExtentions
{
public static IEnumerable<T> OrderBy<T,TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, bool reverse)
{
return reverse ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);
}
}
With this in place, you could write your code without all those if's
switch (this.Sort)
{
case "ITApplication.Name":
this.Issues = this.db.ITIssues.OrderBy(i => i.ITApplication.Name, this.SortDir == "ASC").Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
/*...*/
}
As a second solution to determining sort order, you pass in an IComparer which uses SortDir. Here's the IComparer implementation you could use
class ReversableComparer : IComparer<IComparable>
{
private bool _reverse;
public ReversableComparer(bool reverse)
{
_reverse = reverse;
}
public int Compare(IComparable x, IComparable y)
{
return _reverse ? y.CompareTo(x) : x.CompareTo(y);
}
}
Then you could write your code
switch (this.Sort)
{
case "ITApplication.Name":
this.Issues = this.db.ITIssues.OrderBy(i => i.ITApplication.Name, new ReversableComparer(this.SortDir != "ASC")).Where(i => i.ITAppGroupID == this.ITAppGroupID);
break;
/*...*/
}
That's a bit more code to write, but its there if you need it.
As far as the property name/reflection goes, if you're using Linq as part of Linq to SQL, the dynamic linq link provided by #AlbertVo might be worth a look. If you're working more with Linq to Objects, then instead of i => i.ITApplication.Name try playing with i => i.GetType().GetProperty(this.Sort).GetValue(i, null)
Of course you'll need System.Reflection to make that work and it may be slower than your original solution.
I hope some of that helped. Cheers.
EDIT
As a summary, these two techniques can be combined. Your final code might look like this:
public static class IEnumerableExtentions
{
public static IEnumerable<T> OrderBy<T,TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, bool reverse)
{
return reverse ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);
}
}
void Main()
{
this.Issues = this.db.ITIssues
.OrderBy(i => i.GetType().GetProperty(this.Sort).GetValue(i, null),
this.SortDir == "ASC")
.Where(i => i.ITAppGroupID == this.ITAppGroupID);
}
Related
IQueryable<PortServicesTariffDetail> detalis = _context.PortServicesTariffDetails
.Where(w => w.PortServicesTariffId == TariffId &&
w.PortServicesTariffDetailId == DetailId);
if (detalis.Count() > 0)
{
var mydetial = detalis.GroupBy(g => new { g.StartRate,g.EndRate }).AsQueryable();
_context.PortServicesTariffDetails.RemoveRange(mydetial.AsQueryable());
_context.SaveChanges();
}
Depends on the start and end rate remove range, I have raise some issue like anonymous type. so what can I do???
Argument 1: cannot convert from 'System.Linq.IQueryable>' to 'System.Collections.Generic.IEnumerable'
Try this
foreach(var x in mydetial)
{
_context.PortServicesTariffDetails.RemoveRange(x.ToList());
}
_context.SaveChanges();
But I don't understand why you're grouping everything to remove it?
Try this :
var detalis = _context.PortServicesTariffDetails
.Where(w => w.PortServicesTariffId == TariffId &&
w.PortServicesTariffDetailId == DetailId).ToList();
if (detalis.Count() > 0)
{
var mydetial = detalis.GroupBy(g => new { g.StartRate,g.EndRate }).AsQueryable();
_context.PortServicesTariffDetails.RemoveRange(mydetial.AsQueryable());
_context.SaveChanges();
}
I think there is no need to use Group by,
You can simply use RemoveRange
_context.PortServicesTariffDetails.RemoveRange(detalis);
_context.SaveChanges();
With linq I want to use order by with specific column but I need two switches because i don't know how to use desc or asc in one
public class CustomersRepository : RepositoryBase<Customers>
{
public List<Customers> GetAll(CustomersProperties property, SortEnum sortEnum, int page, int limit)
{
var query = _context.Set<Customers>();
switch (sortEnum)
{
case SortEnum.Ascending:
switch (property)
{
case CustomersProperties.Name:
query = query.OrderBy(x => x.Name);
break;
case CustomersProperties.Surname:
query = query.OrderBy(x => x.Lastname);
break;
default:
throw new ArgumentOutOfRangeException("property");
}
break;
case SortEnum.Descending:
break;
default:
throw new ArgumentOutOfRangeException("sortEnum");
}
return query.Skip(page * limit)
.Take(limit).ToList();
}
}
Is it possible to do without two switch cases?
Be aware that query.OrderBy(x => x.Name); does not do anything since the sorted collection is returned from OrderBy, and you're not capturing that return.
That said, there's not a way to "dynamically" choose the direction in Linq. However, a conditional switch would be a little cleaner. Another option would be to capture the sort expression in a variable:
Expreccion<Func<Customers, string>> propExp;
switch (property)
{
case CustomersProperties.Name:
propExp = ((Customers)x => x.Name)
break;
case CustomersProperties.Surname:
propExp = ((Customers)x => x.Lastname);
break;
default:
throw new ArgumentOutOfRangeException("property");
}
query = sortEnum == SortEnum.Ascending
? query.OrderBy(propExp);
: query.OrderByDescending(propExp);
You could make your own overload, something like this:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector, SortEnum sort) {
switch (sort) {
case SortEnum.Ascending:
return source.OrderBy(keySelector);
case SortEnum.Descending:
return source.OrderByDescending(keySelector);
default:
throw new ArgumentOutOfRangeException("sort");
}
}
// later on..
query = query.OrderBy(x => x.LastName, sortEnum);
You can prepare sortProperty Expression first and then use it with either OrderBy or OrderByDescending:
public List<Customers> GetAll(CustomersProperties property, SortEnum sortEnum, int page, int limit)
{
var query = _context.Set<Customers>();
Expression<Func<Customers, string>> sortProperty;
switch (property)
{
case CustomersProperties.Name:
sortProperty = x => x.Name;
break;
case CustomersProperties.Surname:
sortProperty = x => x.Lastname;
break;
default:
throw new ArgumentOutOfRangeException("property");
}
switch (sortEnum)
{
case SortEnum.Ascending:
query = query.OrderBy(sortProperty);
break;
case SortEnum.Descending:
query = query.OrderByDescending(sortProperty);
break;
default:
throw new ArgumentOutOfRangeException("sortEnum");
}
return query.Skip(page * limit)
.Take(limit).ToList();
}
I've got the following struct that is the key for my dictionary:
public struct CodeAttribute
{
public int ProcessorId;
public Enums.TransactionType transactionType;
public string ErrorMessage;
}
I've got the following dictionary (one value for now as it's just an example):
var errors = new Dictionary<CodeAttribute, int>
{
{CreateCodeAttributeList(2, Enums.TransactionType.Order, "Invalid ProcessorId sent in the Payment Request"), 100 }
};
And I'm trying to pull out the item in the dictionary that matches on the struct that has a match for both its ProcessorId and TransactionType properties:
private static string GetRelatedMessage(int errorCode, Dictionary<CodeAttribute, int> errorsList)
{
CodeAttribute codeAttribute = errorsList.Where(e => e.Key.ProcessorId == _processorId)
.Where(e => e.Key.transactionType == _transactionType) == errorCode;
return codeAttribute.ErrorMessage;
}
I also want to match on error code as part of the filtering, not just paymentprocessorId and transactionType, just a side note. The item in the dictionary must match all 3 values in order to get the right one in our case.
UPDATE
I tried this as well,and yes I get the error that it can't convert IEnumerable to CodeAtribute
CodeAttribute codeAttributes = errorsList.Where(e => e.Key.ProcessorId == _processorId)
.Where(e => e.Key.transactionType == _transactionType)
.Where(e => e.Value.Equals(errorCode));
UPDATE
with the help of Sam I think this may work
CodeAttribute codeAttribute = errorsList.FirstOrDefault(e => e.Key.ProcessorId ==
_processorId && e.Key.transactionType == _transactionType
&& e.Value == errorCode).Key;
If I understand correctly then you want
var codeAttribute = errorsList.FirstOrDefault(e =>
e.Key.ProcessorId == _processorId
&& e.Key.transactionType == _transactionType
&& e.Value == errorCode);
if(codeAttribute == null)
{
//no item matches in the dictionary.
}
return codeAttribute.Key.ErrorMessage;
Note that codeAttribute will be a KeyValuePair so you will need the codeAttribute.Key.ErrorMessage as your return value.
You don't need to use Where as that will return an IEnumerable so this won't work if you want a single item.
You probably need to go with something like this:
CodeAttribute codeAttribute = errorsList.FirstOrDefault(e => e.Key.ProcessorId == _processorId && e.Key.transactionType ==_transactionType)
While the other answers are correct, I would probably write it like this:
var errorMessage = errorsList
.Where(e => e.Key.ProcessorId == _processorId
&& e.Key.transactionType == _transactionType
&& e.Value == errorCode)
.Select(e => e.Key.ErrorMessage)
.FirstOrDefault();
That is, push the condition to filter earlier on, select the data I want from that result-set, and then take the first result (should one exist) of the transformed data.
Since the IEnumerable queries are lazy then this will still stop on the first sucessfully filtered object.
Since the source is a Dictionary, it may be also prudent to set up a relevant Equals/GetHashCode and structure the code such that it will be used.
In latest version of LINQ to Twitter v2.1.08 there is no longer support for paging. How do I get a certain page without page property?
Cheers
int curPageIndex=5;
string pageindex="5";
string cmd = "next";
using (var twitterCtx = new TwitterContext(myauth))
{
try
{
// set up the "main query"
IQueryable<Status> test = from tweet in twitterCtx.Status select tweet;
switch (cmd)
{
case "prev":
test = pageindex.Length > 0
? test.Where(p => p.Type == StatusType.Home && p.Page == curPageIndex)
: test.Where(p => p.Type == StatusType.Home);
break;
case "next":
test = pageindex.Length > 0
? test.Where(p => p.Type == StatusType.Home && p.Page == curPageIndex)
: test.Where(p => p.Type == StatusType.Home);
break;
default:
//
test = test.Where(p => p.Type == StatusType.Home);
break;
}
Solution: Changed Page parameter to SinceID and MaxID
//Get the statusids in the query, add or subtract so you skip current id's
ulMaxId = test.Min(status => ulong.Parse(status.StatusID)) - 1;
ulSinceID = test.Max(status => ulong.Parse(status.StatusID)) + 1;
//Return ID above and use them in future calls (below)
//Now you can navigate timelines, depending if you are stepping forward or backwards
? test.Where(p => p.Type == StatusType.Home && p.SinceID == ulong.Parse(sinceid)
...
? test.Where(p => p.Type == StatusType.Home && p.MaxID == ulong.Parse(maxid))
While working on a personal project I wanted a simple service to extract items out of Outlook and host in WCF in a "RESTful" design. In the process I came up with this rather beastly class.
What other scary linq code have people have seen?
public IQueryable<_AppointmentItem> GetAppointments(DateTime date)
{
var dayFlag = (OlDaysOfWeek)(int)Math.Pow(2, (int)date.DayOfWeek);
return
OlDefaultFolders.olFolderCalendar.GetItems<_AppointmentItem>()
.Select(a => new
{
Appointment = a,
RecurrencePattern = a.IsRecurring ?
a.GetRecurrencePattern() : null
})
.Where(a =>
a.Appointment.Start.Date <= date &&
(
(a.RecurrencePattern == null &&
a.Appointment.End.Date >= date) ||
(
a.RecurrencePattern != null &&
(
(a.RecurrencePattern.DayOfMonth == 0 ||
a.RecurrencePattern.DayOfMonth == date.Day) &&
(a.RecurrencePattern.DayOfWeekMask == 0 ||
((a.RecurrencePattern.DayOfWeekMask &
dayFlag) != 0)) &&
(a.RecurrencePattern.MonthOfYear == 0 ||
a.RecurrencePattern.MonthOfYear == date.Month)
)
)
)
)
.Select(a => a.Appointment);
}
[OperationContract()]
[WebGet(
UriTemplate = "/appointments/{year}/{month}/{day}",
RequestFormat = WebMessageFormat.Xml,
ResponseFormat = WebMessageFormat.Xml,
BodyStyle = WebMessageBodyStyle.Bare
)]
[ContentType("text/xml")]
public XElement ListAppointments(string year, string month, string day)
{
try
{
int iYear, iMonth, iDay;
int.TryParse(year, out iYear);
int.TryParse(month, out iMonth);
int.TryParse(day, out iDay);
if (iYear == 0) iYear = DateTime.Now.Year;
if (iMonth == 0) iMonth = DateTime.Now.Month;
if (iDay == 0) iDay = DateTime.Now.Day;
var now = new DateTime(iYear, iMonth, iDay).Date; // DateTime.Now;
return GetAppointments(now).ToXml();
}
catch (System.Exception ex)
{
return new XElement("exception", ex.ToString());
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.Outlook;
namespace WhitedUS.ServiceModel.Office.Linq
{
public static class OutlookUtilities
{
public static IQueryable<T> GetItems<T>(
this OlDefaultFolders defaultFolderType)
{
return
new ApplicationClass()
.Session
.GetDefaultFolder(defaultFolderType)
.Items
.OfType<T>()
.AsQueryable();
}
public static XElement ToXml<T>(this IEnumerable<T> input)
{
if (input == null)
return null;
Type typ = typeof(T);
var root = XName.Get(typ.Name.Trim('_'));
return new XElement(root,
input
.Select(x => x.ToXml<T>())
.Where(x => x != null)
);
}
public static XElement ToXml<T>(this object input)
{
if (input == null)
return null;
Type typ = typeof(T);
var root = XName.Get(typ.Name.Trim('_'));
return new XElement(root,
typ.GetProperties()
.Where(p => p.PropertyType.IsValueType ||
p.PropertyType == typeof(string))
.Select(p => new { Prop = p, Getter = p.GetGetMethod() })
.Where(p => p.Getter != null)
.Select(p => new { Prop = p.Prop, Getter = p.Getter,
Params = p.Getter.GetParameters() })
.Where(p => (p.Params == null || p.Params.Count() <= 0))
.Select(p => new { Name = p.Prop.Name,
Value = p.Getter.Invoke(input, null) })
.Where(p => p.Value != null)
.Select(p => new XAttribute(XName.Get(p.Name), p.Value))
);
}
}
}
Also see: What is the worst abuse you’ve seen of LINQ syntax?
A fully LINQified RayTracer is pretty scary.
Actually, the scariest Linq query I ever saw was my first one. It was just familiar enough to make me think I understood it and just different enough to make me doubt that I really did.