I have a method;
public List<Task> GetTasksByAssignedTo(Guid contactId)
{
List<Task> tasks = dc.Tasks.Where(x => x.ContactId == contactId).ToList();
return tasks;
}
which returns a list of items. Say I now wanted to specify the sort order I want to return the list in.
So I might sort by Name, DueDate, Completed etc, etc.
How could I include that in the method as a parameter? I don't want to use a switch statement rather I'd like to use a lambda if possible.
So;
List<Task> list = GetTasksByAssignedTo("guid", ??????);
Or is this the wrong approach.
I think that your approach is the wrong way to use LINQ.
LINQ uses a deferred execution model for a reason. It allows you to chain together a series of operations that get executed only when you tell it to compute the result - often with .ToList(), .ToArray(), .First() - but you can also force the computation by filtering with a OrderBy clause that uses a Func<T, ?> as its parameter.
Now you're returning a List<Task> which means that you've forced the execution - which is the right thing to do when you're ready to use the results - but if you then go on to do further operations you are potentially loading many more records into memory than you need to.
You could certainly do this:
public List<Task> GetTasksByAssignedTo<P>(Guid contactId, Func<Task, P> orderBy)
{
return dc.Tasks
.Where(x => x.ContactId == contactId)
.OrderBy(orderBy) // this forces evaluation - sort happens in memory
.ToList();
}
To make the execution happen in the database you need to change it like this:
public List<Task> GetTasksByAssignedTo<P>(
Guid contactId,
Expression<Func<Task, P>> orderBy)
{
return dc.Tasks
.Where(x => x.ContactId == contactId)
.OrderBy(orderBy)
.ToList(); // Now execution happens here
}
But the issue is what if you did this:
var query =
from t1 in GetTasksByAssignedTo(contactId, t => t.Name)
join t2 in GetTasksByAssignedTo(contactId, t => t.Name)
on t1.Name equals t2.Name
select new { t1, t2 };
Because your GetTasksByAssignedTo brings records into memory you are doing the join in memory. (Yes, the query is a bit contrived, but the principle is solid though.)
It's often much better to do it in the database.
Here's how to fix it:
public IQueryable<Task> GetTasksByAssignedTo<P>(
Guid contactId,
Expression<Func<Task, P>> orderBy)
{
return dc.Tasks
.Where(x => x.ContactId == contactId)
.OrderBy(orderBy);
}
Now the above query won't execute until you do query.ToList() and all will happen at the database.
But I have an even bigger issue.
You're hiding a lot of information in the GetTasksByAssignedTo. Someone using the code doesn't know that they're actually getting a list when they read the code and they really don't know if the actual implementation is doing the right thing. I think, for these kinds of queries, it's often better to leave it as plain LINQ.
Compare these:
var tasks1 = GetTasksByAssignedTo(contactId);
var tasks2 = GetTasksByAssignedTo(contactId, t => t.Name);
var tasks3 = GetTasksByAssignedToDescending(contactId, t => t.Name);
var tasks4 = (
from t in dc.Tasks
where t.ContactId == contactId
orderby t.Name descending
select t
).ToList();
The first query, tasks1 isn't too bad, but it doesn't tell you what the return type is;
The second query, tasks2 does something with some t and the property Name, but doesn't tell you what.
The third query, tasks3 give you a hint that it is sorting descending, but doesn't tell you if it's by the mysterious Name property or something else.
The fourth query, tasks4 tells you everything that you need to know - it's filtering tasks by ContactId, reverse ordering the results by Name, and finally returning a list.
Now, take a look at this query:
var query2 =
from t1 in dc.Tasks
where t1.ContactId == contactId
join t2 in dc.Tasks on t1.Name equals t2.Name
where t2.ContactId != contactId
orderby t2.Name descending
select t2;
I can read that quite easily and see what it is doing. Just imagine what the helper method name would be for this one! Or what insane nesting of helper methods would be required.
The bottom-line is that LINQ is the API for querying.
If you desperately want to create helper methods then use extension methods.
public static class TaskEx
{
public static IQueryable<Task> WhereAssignedTo(this IQueryable<Task> tasks,
Guid contactId)
{
return tasks.Where(t => t.ContactId == contactId);
}
public static IQueryable<Task> OrderByName(this IQueryable<Task> tasks)
{
return tasks.OrderBy(t => t.Name);
}
}
This then allows you to write this:
var tasks = dc.Tasks
.WhereAssignedTo(contactId)
.OrderByName()
.ToList();
And that is clear, concise, extensible, composable, re-usable, and you control when execution occurs.
You could pass a Func<Task, object> to your method for ordering:
public List<Task> GetTasksByAssignedTo(Guid contactId, Func<Task, object> someOrder)
{
List<Task> tasks = dc.Tasks.Where(x => x.ContactId == contactId)
.OrderBy(someOrder)
.ToList();
return tasks;
}
Now you can call your method like
Func<Task, object> someOrder = (Task t) => t.DueDate;
List<Task> list = GetTasksByAssignedTo(someGuid, someOrder);
Generally I agree with the comments though - it does not seem that ordering is required for a method named GetTasksByAssignedTo.
#BrokenGlass beat me to the punch.
The other option is to use an extension method which hides away the switch, and represent the different ordering options as an enumeration.
public static IEnumerable<Task> WithOrdering(this IEnumerable<Task> source, TaskOrdering order)
{
switch (order)
{
case TaskOrdering.Name:
return source.OrderBy(task => task.Name);
case TaskOrdering.DueDate:
return source.OrderByDescending(task => task.DueDate);
}
}
And then:
public List<Task> GetTasksByAssignedTo(Guid contactId, TaskOrdering order)
{
List<Task> tasks = dc.Tasks.Where(x => x.ContactId == contactId)
.WithOrdering(order)
.ToList();
return tasks;
}
I do this all the time. Allowing the predicate as a method parameter can be tricky because what happens if you want to do ascending/descending? You'll need another parameter for that (bool), then do a if/else check to do OrderBy or OrderByDescending.
Hide the logic in a filter, then you can re-use it anywhere in your app.
Try this... input parameters are in string. This is according modified solution from StackOverflow
/// <summary>
/// Sort List<typeparam name="T"></typeparam> objects base on string options
/// </summary>
/// <param name="SortDirection">Ascending or Descending</param>
/// <param name="ColumnName">Column name in complex object (object.ColumnName)</param>
public static class ListExtension
{
public static List<T> SortList<T>(this List<T> data, string sortDirection, string sortExpression)
{
try
{
switch (sortDirection)
{
case "Ascending":
data = (from n in data
orderby GetDynamicSortProperty(n, sortExpression) ascending
select n).ToList();
break;
case "Descending":
data = (from n in data
orderby GetDynamicSortProperty(n, sortExpression) descending
select n).ToList();
break;
default:
data = null; //NUL IF IS NO OPTION FOUND (Ascending or Descending)
break;
}
return data;
} catch(Exception ex){
throw new Exception("Unable to sort data", ex);
}
}
private static object GetDynamicSortProperty(object item, string propName)
{
//Use reflection to get order type
return item.GetType().GetProperty(propName).GetValue(item, null);
}
}
Related
I am trying to remove duplicate code throughout my project and I am at a standstill trying to figure this out. What I am trying to do is create a base linq query that will be reused to add things like Where, Take...etc in multiple different methods.
public IQueryable<Object> FooLinq(int id)
{
using (var ctx = new dbEntities())
{
var results =
(from account in ctx.account
join memberProducts in ctx.tblMemberProducts on account.Id equals memberProducts.AccountId
orderby account.date descending
select new{account,memberProducts}).ToList();
return results;
}
}
So that would be by base query above and I would have a seperate method that would reuse VioLinq but this time would use a where clause in it.
public List<IncomingViolations> Foo1(int id)
{
//Linq query FooLinq() where Name == "Bob"
}
You'll need to do two things:
Return the query prior to materializing it.
Make sure the context is still in scope when the final query is materialized.
These two requirements will play off each other somewhat, and there are a number of approaches you can take to meet them.
For example, you could make your method take the context as a parameter, forcing the caller to provide it and manage its lifecycle.
public IQueryable<AccountInfo> FooLinq(DbEntities ctx, int id)
{
return
from account in ctx.account
orderby account.date descending
select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
};
}
public List<IncomingViolations> Foo1(int id)
{
using(var ctx = new dbEntities())
{
//Linq query FooLinq() where Name == "Bob"
return FooLinq(ctx).Where(v => v.Name == "Bob").ToList();
}
}
You could alternatively inject the context as a constructor-injected dependency, and use a DI framework to manage the context's lifecycle.
You can do it as Queryable then add conditions to it.
For example:
public List<account> GetAccountsByName(string name, bool usePaging, int offset = 0, int take = 0) {
var query = GetMyQuery();
query = query.Where(x => x.Name == name);
query = query.OrderBy(x => x.Name);
if(usePaging) {
query = query.Take(take).Skip(offset);
}
query = PrepareSelectForAccount(query);
return query.ToList(); .
}
public IQueryable<account> GetMyQuery(){
return ctx.account.AsQueryable();
}
public IQueryable<account> PrepareSelectForAccount(IQueryAble<account> query){
return query.Select(select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
}
);
}
Sure, but don't call .ToList(), and return IQueryable<T> instead of List<T>. LINQ is based on the concept of deferred execution which means the query is not actually performed until the enumerable is iterated over. Until then, all you have done is built an object which knows how to do the query when the time comes.
By returning an IQueryable<T> from a function that sets up the "basic query," you are then free to tack on additional LINQ methods (such as .Where() or .Take()) to produce a modified query. At this point you are still simply setting up the query; it is actually performed only when you iterate over the enumerable, or call something like .ToList() which does that for you.
Mono.Android requires me to do async queries. I can do that like this:
DataServiceQuery<T> dataServiceQuery = query as DataServiceQuery<T>;
return (new TaskFactory<IEnumerable<T>>()).FromAsync(dataServiceQuery.BeginExecute(null, null), asyncResult => dataServiceQuery.EndExecute(asyncResult)).Result;
The problem is that I don't want to call this method every time I'm querying because this forces my users that are using my library to call that method, otherwise Mono will crash, so I'd like to make DataServiceQuery do this by default. I can do this by overriding the GetEnumerator() function from DataServiceQuery, but it's constructor is private so I'm not allowed to do that. The only option I have left I guess is to recompile System.Data.Services.Client.dll with my changes. Maybe there is a trick with DataServiceContext but I'm honestly out of options.
Thanks for your help,
Maxim
It is not possible to do this automatically, but you can make your statement nicer by making a new Select function called SelectAsync.
As a matter of fact this also fixes a problem with OData and LINQ because you had to wrap Selects in anonymous types, now you can call the Select after the query (which takes longer since you are querying the whole class, but this is not a problem to me).
Here is the code for the SelectAsync extension methods:
public static IEnumerable<T> SelectAsync<T>(this IQueryable<T> queryable)
{
return queryable.SelectAsync(x => x);
}
public static IEnumerable<TResult> SelectAsync<T, TResult>(this IQueryable<T> queryable, Function<T, TResult> selector)
{
var dataServiceQuery = queryable as DataServiceQuery<T>;
return Task.Run(async () => (await (new TaskFactory<IEnumerable<T>>()).FromAsync(dataServiceQuery.BeginExecute, dataServiceQuery. EndExecute, null)).Select(selector)).Result;
}
Executing a query with LINQ in Mono.Android is now more intuitive.
var itemNames = Items.Where(item => item.ID == 0).Select(item => item.Name);
var goodCars = Cars.Where(car => car.Fuel >= 0.5);
Translates to:
var itemNames = Items.Where(item => item.ID == 0).SelectAsync(item => item.Name);
var goodCars = Cars.Where(car => car.Fuel >= 0.5).SelectAsync();
As far as I know, this is as good as it can get because System.Data.Services is currently not fully supported in Mono.Android. This is a nice and intuitive workaround.
Sources:
https://www.mono-project.com/docs/web/wcf/
Is there a way to clear an IQueryable from its ordeyby clauses?
For example, we have a function that returns an ordered query:
public IQueryable<SomeType> GetOrderedQuery()
{
var query = from item in db.itemsTable
where item.x != null
orderby item.y, item.z
select item;
return query;
}
And we have another function that needs to use the same query, but it needs to have it unordered:
public IQueryable<SomeType> GetUnorderedQuery()
{
var query = GetOrderedQuery();
query.RemoveOrders(); // How to implement a RemoveOrders function?
return query;
}
How can a RemoveOrders function be implemented? (Doesn't matter if as an extension method or not)
If you don't want it ordered; don't order it. There's no robust way to walk back through an IQueryable<T> to get earlier states, let alone remove individual bits out of the middle. I suspect you want two queries:
public IQueryable<SomeType> GetUnorderedQuery()
=> db.itemsTable.Where(item => item.x != null);
public IOrderedQueryable<SomeType> GetOrderedQuery()
=> GetUnorderedQuery().OrderBy(item => item.y).ThenBy(item => item.z);
I have a basic IQueryable,
private static IQueryable<TestObject> GetFilteredQuery(Guid componentId, Guid productId)
{
IQueryable<TestObject> query = from t in ModelQuery.Query<TestObject>()
where t.ComponentId == componentId && t.ProductId == productId
select t;
return query;
}
This is trivial if I have to compare single componentId and productId.
My problem is how can I handle when I have a list of value pairs,
Guid[] componentIds, Guid[] productIds
where, its kind of a keyValue pair.
something like,
private static IQueryable<TestObject> GetFilteredQuery(Guid[] componentIds, Guid[] productIds)
{
IQueryable<TestObject> query = from t in ModelQuery.Query<TestObject>()
where (t.ComponentId must be present in componentIds[] && t.ProductId must be present in productIds)
select t;
return query;
}
Use Contains:
private static IQueryable<TestObject> GetFilteredQuery(Guid[] componentIds, Guid[] productIds)
{
IQueryable<TestObject> query =
from t in ModelQuery.Query<TestObject>()
where (componentIds.Contains(t.ComponentId)
&& productIds.Contains(t.ProductId))
select t;
return query;
}
Edit
AFAIK there is no way Linq2Sql is going to map a sequence of Guid tuples to native Sql (you would likely need an #Table parameter for this)
So here's one approach, viz to run a query the same contains as above, but using OR on the 2 filter lists. Sql will hopefully be able to filter a significant amount of data out at the database level.
The results (candidates) then need to be materialized, and then filtered in memory against the component and product pairs. I've done this by zipping the 2 guid arrays together (assuming similar length - possibly you want to remodel the arrays as an array of Pairs to express the intention more explicitly?)
private static IQueryable<TestObject> GetFilteredQuery(Guid[] componentIds,
Guid[] productIds)
{
var candidates = ModelQuery
.Query<TestObject>()
.Where(componentIds.Contains(
t.ComponentId) || productIds.Contains(t.ProductId))
.ToList();// Need to materialize
var guidPairs = componentIds.Zip(productIds,
(c, p) => new {ComponentId = c, ProductId = p});
return candidates
.Join(guidPairs,
c => new {ComponentId = c.ComponentId, ProductId = c.ProductId},
gp => gp,
(c, gp) => c)
.AsQueryable();
}
Note that the resultant queryable isn't really suitable for further composition, given that it has already been materialized. Also, if you can do additional filtering before hitting this, it would be beneficial. And I'm afraid I haven't actually tested this.
Use Contains:
where componentIds.Contains(t.ComponentId) && productIds.Contains(t.ProductId)
I have an enum called OrderStatus, and it contains various statuses that an Order can be in:
Created
Pending
Waiting
Valid
Active
Processed
Completed
What I want to do is create a LINQ statement that will tell me if the OrderStaus is Valid, Active, Processed or Completed.
Right now I have something like:
var status in Order.Status.WHERE(status =>
status.OrderStatus == OrderStatus.Valid ||
status.OrderStatus == OrderStatus.Active||
status.OrderStatus == OrderStatus.Processed||
status.OrderStatus == OrderStatus.Completed)
That works, but it's very "wordy". Is there a way to convert this to a Contains() statement and shorten it up a bit?
Sure:
var status in Order.Status.Where(status => new [] {
OrderStatus.Valid,
OrderStatus.Active,
OrderStatus.Processed,
OrderStatus.Completed
}.Contains(status.OrderStatus));
You could also define an extension method In() that would accept an object and a params array, and basically wraps the Contains function:
public static bool In<T>(this T theObject, params T[] collection)
{
return collection.Contains(theObject);
}
This allows you to specify the condition in a more SQL-ish way:
var status in Order.Status.Where(status =>
status.OrderCode.In(
OrderStatus.Valid,
OrderStatus.Active,
OrderStatus.Processed,
OrderStatus.Completed));
Understand that not all Linq providers like custom extension methods in their lambdas. NHibernate, for instance, won't correctly translate the In() function without additional coding to extend the expression parser, but Contains() works just fine. For Linq 2 Objects, no problems.
I have used this extension:
public static bool IsIn<T>(this T value, params T[] list)
{
return list.Contains(value);
}
You may use this as the condition:
Where(x => x.IsIn(OrderStatus.Valid, ... )
If that set of statuses has some meaning, for example those are statuses for accepted orders, you can define an extension method on your enum and use that in your linq query.
public static class OrderStatusExtensions
{
public static bool IsAccepted(this OrderStatuses status)
{
return status == OrderStatuses.Valid
|| status == OrderStatuses.Active
|| status == OrderStatuses.Processed
|| status == OrderStatuses.Completed;
}
}
var acceptedOrders = from o in orders
where o.Status.IsAccepted()
select o;
Even if you could not give the method a simple name, you could still use something like IsValidThroughCompleted. In either case, it seems to convey a little more meaning this way.
Assumnig that the enum is defined in the order you specified in the question, you could shorten this by using an integer comparison.
var result =
Order.Status.Where(x =>
(int)x >= (int)OrderStatus.Valid &
& (int)x <= (int)OrderStatus.Completed);
This type of comparison though can be considered flaky. A simply re-ordering of enumeration values would silently break this comparison. I would prefer to stick with the more wordy version and probably clean up it up by refactoring out the comparison to a separate method.
You could put these in a collection, and use:
OrderStatus searchStatus = new[] {
OrderStatus.Valid,
OrderStatus.Active,
OrderStatus.Processed,
OrderStatus.Completed };
var results = Order.Status.Where(status => searchStatus.Contains(status));