We have a program that uses Linq-To-Sql and does a lot of similar queries on our tables. In particular, we have a timestamp column, and we pull the most recent timestamp to see if the table has changed since our last query.
System.Data.Linq.Binary ts;
ts = context.Documents
.Select(r => r.m_Timestamp)
.OrderByDescending(r => r)
.FirstOrDefault();
We repeat this query often on different tables that's relevant for the current form/query whatever. What I would like to do is create a "function" or something that can repeat the last 3 lines of this query (and ideally would work on every table). Here's what I would like to write:
System.Data.Linq.Binary ts;
ts = context.Documents
.GetMostRecentTimestamp();
But I have no idea how to create this "GetMostRecentTimestamp". Also, these queries are never this simple. They usually filter by the Customer, or by the current order, so a more valid query might be
ts = context.Documents
.Where(r => r.CustomerID == ThisCustomerID)
.GetMostRecentTiemstamp();
Any help? Thanks!
Update [Solution]
I selected Bala R's answer, here's the code updated so it compiles:
public static System.Data.Linq.Binary GetMostRecentTimestamp(this IQueryable<Data.Document> docs)
{
return docs
.Select(r => r.m_Timestamp)
.OrderByDescending(r => r)
.FirstOrDefault();
}
The only drawback to this solution is that I will have to write this function for each table. I would have loved Tejs's answer, if it actually worked, but I'm not re-designing my database for it. Plus DateTime is a not a good way to do timestamps.
Update #2 (Not so fast)
While I can do a query such as Documents.Where( ... ).GetMostRecentTimestamp(), this solution fails if I try to do an association based query such as MyCustomer.Orders.GetMostRecentTimestamp(), or MyCustomer.Orders.AsQueryable().GetMostRecentTimestamp();
This is actually pretty easy to do. You simply need to define an interface on the entities you wish to provide this for:
public class MyEntity : ITimestamp
Then, your extenstion method:
public static DateTime GetMostRecentTimestamp<T>(this IQueryable<T> queryable)
where T : ITimestamp
{
return queryable.Select(x => x.m_Timestamp)
.OrderByDescending(r => r)
.FirstOrDefault()
}
This is then useful on any entity that matches the interface:
context.Documents.GetMostRecentTimestamp()
context.SomeOtherEntity.GetMostRecentTimestamp()
How about an extension like this
public static DateTime GetMostRecentTimestamp (this IQueryable<Document> docs)
{
return docs.Select(r => r.m_Timestamp)
.OrderByDescending(r => r)
.FirstOrDefault();
}
Hmm...
DateTime timeStamp1 = dataContext.Customers.Max(c => c.TimeStamp);
DateTime timeStamp2 = dataContext.Orders.Max(c => c.TimeStamp);
DateTime timeStamp3 = dataContext.Details.Max(c => c.TimeStamp);
I created a pair of extension methods that could help you out: ObjectWithMin and ObjectWithMax:
public static T ObjectWithMax<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
where TResult : IComparable<TResult>
{
if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");
var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();
return elements.Aggregate(seed,
(s, x) =>
projection(x).CompareTo(s.Projection) >= 0
? new {Object = x, Projection = projection(x)}
: s
).Object;
}
public static T ObjectWithMin<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
where TResult : IComparable<TResult>
{
if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");
var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();
return elements.Aggregate(seed,
(s, x) =>
projection(x).CompareTo(s.Projection) < 0
? new {Object = x, Projection = projection(x)}
: s
).Object;
}
These two are more efficient than OrderBy().FirstOrDefault() when used on in-memory collections; however they're unparseable by IQueryable providers. You'd use them something like this:
ts = context.Documents
.Where(r => r.CustomerID == ThisCustomerID)
.ObjectWithMax(r=>r.m_Timestamp);
ts is now the object having the most recent timestamp, so you don't need a second query to get it.
Related
Hi can someone help me how best we can use whereif in LINQ, here I have a code which works fine, but I want to convert this query with WhereIf.
public async Task LoadQuery(IEnumerable<string> codes)
{
var query = _dBContext.QueryTable.Where(x => !x.InActive).AsQueryable();
if (codes!= null && codes.Any())
query = query.Where(x => codes.Contains(x.FirstCode) || query.Contains(x.SecondCode));
else
query = query.Where(x => !x.HasException.HasValue);
var data = query.ToList();
}
I have tried it with WhereIF ienumerable but not succeed. Here is the link which I followed.
https://extensionmethod.net/csharp/ienumerable-t/whereif
WhereIf isn't really suitable for your case, for 2 reasons:
You're calling two different functions on your if-else, while WhereIf is built to accept a single function (predicate) to be executed if some condition is satisfied.
WhereIf is an extension method for IEnumerable<TSource>, while your'e trying to use it as an extension method for IQueryable<TSource>.
If you insist, you'd have to define an extension method for IQueryable<TSource>, and in doing so, just define it as WhereIfElse:
public static class ExtensionMethods
{
public static IQueryable<TSource> WhereIfElse<TSource>(this IQueryable<TSource> source, bool condition, Func<TSource, bool> predicateIf, Func<TSource, bool> predicateElse)
{
if (condition)
return source.Where(predicateIf).AsQueryable();
else
return source.Where(predicateElse).AsQueryable();
}
}
So, let's say that query's type is IQueryable<Item> (replace Item with your actual type):
public async Task<List<Item>> LoadQuery(IEnumerable<string> codes)
{
var query = _dBContext.QueryTable.Where(x => !x.InActive).AsQueryable();
query = query.WhereIfElse(
// condition
codes != null && codes.Any(),
// predicateIf
(Item x) => codes.Contains(x.FirstCode) || codes.Contains(x.SecondCode),
// predicateElse
(Item x) => !x.HasException.HasValue
);
var data = query.ToList();
return data;
}
P.S. note I changed your return value, though there still isn't an await.
bool condition = codes!= null && codes.Any();
var data = _dBContext.QueryTable
.WhereIf(condition, a=> codes.Contains(a.FirstCode) || codes.Contains(a.SecondCode))
.WhereIf(!condition, a=> !a.HasException.HasValue && !a.InActive).ToList();
Is it possible to use Except() for two List's that have two different classes but a common field? I have List<User1> and List<User2> collections. They have different properties except Id column and I want to find the different records between them using this Id column. I'm trying to use List<>.Except() but I'm getting this error:
The type arguments for method 'System.Linq.Enumerable.Except(System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerable)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Here's what I'm trying:
List<User1> list1 = List1();
List<User2> list2 = List2();
var listdiff = list1.Except(list2.Select(row => row.Id));
What am I doing wrong?
List1 contains instances of User1 and List2 contains instances of User2.
What type of instance should be produced by list1.Except(list2.Select(row => row.Id))?
In other words if type inference was not available, what would you replace var with?
If User1 and User2 inherit from the same ancestor (with ID), use List<User> instead.
Otherwise:
var list2Lookup = list2.ToLookup(user => user.Id);
var listdiff = list1.Where(user => (!list2Lookup.Contains(user.Id))
Not Except, but the correct results and similar performance:
// assumes that the Id property is an Int32
var tempKeys = new HashSet<int>(list2.Select(x => x.Id));
var listdiff = list1.Where(x => tempKeys.Add(x.Id));
And, of course, you can wrap it all up in your own re-usable extension method:
var listdiff = list1.Except(list2, x => x.Id, y => y.Id);
// ...
public static class EnumerableExtensions
{
public static IEnumerable<TFirst> Except<TFirst, TSecond, TKey>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TKey> firstKeySelector,
Func<TSecond, TKey> secondKeySelector)
{
// argument null checking etc omitted for brevity
var keys = new HashSet<TKey>(second.Select(secondKeySelector));
return first.Where(x => keys.Add(firstKeySelector(x)));
}
}
Briefly, make lists to be List<object> and use C# feature from .NET 4.0: dynamic.
Example:
var listDiff = list1
.AsEnumerable<object>()
.Except(list2
.AsEnumerable<object>()
.Select(row => ((dynamic)row).ID));
If you just want the Ids in list1 that are not in list2, you can do:
var idsInList1NotInList2 = list1.Select(user1 => user1.Id)
.Except(list2.Select(user2 => user2.Id));
If you need the associated User1 objects too, here's one way (assuming Ids are unique for a User1 object):
// Create lookup from Id to the associated User1 object
var user1sById = list1.ToDictionary(user1 => user1.Id);
// Find Ids from the lookup that are not present for User2s from list2
// and then retrieve their associated User1s from the lookup
var user1sNotInList2 = user1sById.Keys
.Except(list2.Select(user2 => user2.Id))
.Select(key => user1sById[key]);
EDIT: vc74's take on this idea is slightly better; it doesn't require uniqueness.
public static IEnumerable<TSource> Except<TSource, CSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> TSelector, IEnumerable<CSource> csource, Func<CSource, TKey> CSelector)
{
bool EqualFlag = false;
foreach (var s in source)
{
EqualFlag = false;
foreach (var c in csource)
{
var svalue = TSelector(s);
var cvalue = CSelector(c);
if (svalue != null)
{
if (svalue.Equals(cvalue))
{
EqualFlag = true;
break;
}
}
else if (svalue == null && cvalue == null)
{
EqualFlag = true;
break;
}
}
if (EqualFlag)
continue;
else
{
yield return s;
}
}
}
Try
list1.Where(user1 => !list2.Any(user2 => user2.Id.Equal(user1.Id)));
this is by far my toughest question yet and I'm hoping someone has stumbled upon this issue before and found an elegant answer. Basically, I've got a few linq extension methods (which just happen to be in subsonic but would be applicable in any linq derivative) that are working perfectly (extensions for .WhereIn() and .WhereNotIn()). these methods operate to transform the linq to the sql equivalents of in(). Now the code below works perfectly when supplying known typed parameters (i.e. an array or params array):
public static IQueryable<T> WhereIn<T, TValue>(
this IQueryable<T> query,
Expression<Func<T, TValue>> selector,
params TValue[] collection) where T : class
{
if (selector == null) throw new ArgumentNullException("selector");
if (collection == null) throw new ArgumentNullException("collection");
ParameterExpression p = selector.Parameters.Single();
if (!collection.Any()) return query;
IEnumerable<Expression> equals = collection.Select(value =>
(Expression)Expression.Equal(selector.Body,
Expression.Constant(value, typeof(TValue))));
Expression body = equals.Aggregate(Expression.Or);
return query.Where(Expression.Lambda<Func<T, bool>>(body, p));
}
usage:
var args = new [] { 1, 2, 3 };
var bookings = _repository.Find(r => r.id > 0).WhereIn(x => x.BookingTypeID, args);
// OR we could just as easily plug args in as 1,2,3 as it's defined as params
var bookings2 = _repository.Find(r => r.id > 0).WhereIn(x => x.BookingTypeID, 1,2,3,90);
However, now for the complicated part. I'd like to be able to pass an IQueryable object into an overload version of the above that accepts a second linq object as the parameter in order to achieve the equivalent of select * from table1 where table1.id in(select id from table2). here is the method signature that actually compiles ok but has the all important logic missing:
public static IQueryable<T> WhereIn<T, TValue, T2, TValue2>(
this IQueryable<T> query,
Expression<Func<T, TValue>> selector,
T2 entity2,
Expression<Func<T2, TValue2>> selector2) where T : class
{
if (selector == null) throw new ArgumentNullException("selector");
if (selector2 == null) throw new ArgumentNullException("selector2");
ParameterExpression p = selector.Parameters.Single();
ParameterExpression p2 = selector2.Parameters.Single();
/* this is the missing section */
/* i'd like to see the final select generated as
*
* select * from T where T.selector in(select T2.selector2 from T2)
*/
return null;
// this is just to allow it to compile - proper return value pending
}
usage:
var bookings = _repository.Find(r => r.BookingID>0)
.WhereIn(x => x.BookingTypeID, new BookingType(), y => y.BookingTypeID);
am i barking up an non existent (expression) tree here :-) - or is this pretty do-able.
all the best - here's hoping.
jim
Why would you not just use a join?
var query = from x in table1
join y in table2 on x.Id equals y.Id
select x;
Or if there might be multiple y values for each x:
var query = from x in table1
join z in table2.Select(y => y.Id).Distinct() on x.Id equals z
select x;
I would expect queries like that to be well optimized in SQL databases.
Or if you really want to use Where:
var query = table1.Where(x => table2.Select(y => y.Id).Contains(x.Id));
I may be missing something bigger... or it could be that translating the above queries into extension methods is what you're looking for :)
i eventually opted for an extension method to achieve this but still isn't 100% sucessful.
I'll drop the actual full working code here at some point later, once i've integrated it with all my other options.
Suppose I have the two following Linq-To-SQL queries I want to refactor:
var someValue1 = 0;
var someValue2= 0;
var query1 = db.TableAs.Where( a => a.TableBs.Count() > someValue1 )
.Take( 10 );
var query2 = db.TableAs.Where( a => a.TableBs.First().item1 == someValue2)
.Take( 10 );
Note that only the Where parameter changes. There is any way to put the query inside a method and pass the Where parameter as an argument?
All the solutions posted in the previous question have been tried and failed in runtime when I try to enumerate the result.
The exception thrown was: "Unsupported overload used for query operator 'Where'"
Absolutely. You'd write:
public IQueryable<A> First10(Expression<Func<A,bool>> predicate)
{
return db.TableAs.Where(predicate).Take(10);
}
(That's assuming that TableA is IQueryable<A>.)
Call it with:
var someValue1 = 0;
var someValue2= 0;
var query1 = First10(a => a.TableBs.Count() > someValue1);
var query2 = First10(a => a.TableBs.First().item1 == someValue2);
I believe that will work...
The difference between this and the answers to your previous question is basically that this method takes Expression<Func<T,bool>> instead of just Func<T,bool> so it ends up using Queryable.Where instead of Enumerable.Where.
If you really want reusability you can try to write your own operators. E.g. instead of repeatedly writing:
var query =
Products
.Where(p => p.Description.Contains(description))
.Where(p => p.Discontinued == discontinued);
you can write simple methods:
public static IEnumerable<Product> ByName(this IEnumerable<Product> products, string description)
{
return products.Where(p => p.Description.Contains(description));
}
public static IEnumerable<Product> AreDiscontinued(IEnumerable<Product> products, bool isDiscontinued)
{
return products.Where(p => p.Discontinued == discontinued);
}
and then use it like this:
var query = Products.ByName("widget").AreDiscontinued(false);
We're working on a Log Viewer. The use will have the option to filter by user, severity, etc. In the Sql days I'd add to the query string, but I want to do it with Linq. How can I conditionally add where-clauses?
if you want to only filter if certain criteria is passed, do something like this
var logs = from log in context.Logs
select log;
if (filterBySeverity)
logs = logs.Where(p => p.Severity == severity);
if (filterByUser)
logs = logs.Where(p => p.User == user);
Doing so this way will allow your Expression tree to be exactly what you want. That way the SQL created will be exactly what you need and nothing less.
If you need to filter base on a List / Array use the following:
public List<Data> GetData(List<string> Numbers, List<string> Letters)
{
if (Numbers == null)
Numbers = new List<string>();
if (Letters == null)
Letters = new List<string>();
var q = from d in database.table
where (Numbers.Count == 0 || Numbers.Contains(d.Number))
where (Letters.Count == 0 || Letters.Contains(d.Letter))
select new Data
{
Number = d.Number,
Letter = d.Letter,
};
return q.ToList();
}
I ended using an answer similar to Daren's, but with an IQueryable interface:
IQueryable<Log> matches = m_Locator.Logs;
// Users filter
if (usersFilter)
matches = matches.Where(l => l.UserName == comboBoxUsers.Text);
// Severity filter
if (severityFilter)
matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);
Logs = (from log in matches
orderby log.EventTime descending
select log).ToList();
That builds up the query before hitting the database. The command won't run until .ToList() at the end.
I solved this with an extension method to allow LINQ to be conditionally enabled in the middle of a fluent expression. This removes the need to break up the expression with if statements.
.If() extension method:
public static IQueryable<TSource> If<TSource>(
this IQueryable<TSource> source,
bool condition,
Func<IQueryable<TSource>, IQueryable<TSource>> branch)
{
return condition ? branch(source) : source;
}
This allows you to do this:
return context.Logs
.If(filterBySeverity, q => q.Where(p => p.Severity == severity))
.If(filterByUser, q => q.Where(p => p.User == user))
.ToList();
Here's also an IEnumerable<T> version which will handle most other LINQ expressions:
public static IEnumerable<TSource> If<TSource>(
this IEnumerable<TSource> source,
bool condition,
Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
{
return condition ? branch(source) : source;
}
When it comes to conditional linq, I am very fond of the filters and pipes pattern.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/
Basically you create an extension method for each filter case that takes in the IQueryable and a parameter.
public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}
Doing this:
bool lastNameSearch = true/false; // depending if they want to search by last name,
having this in the where statement:
where (lastNameSearch && name.LastNameSearch == "smith")
means that when the final query is created, if lastNameSearch is false the query will completely omit any SQL for the last name search.
Another option would be to use something like the PredicateBuilder discussed here.
It allows you to write code like the following:
var newKids = Product.ContainsInDescription ("BlackBerry", "iPhone");
var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
.And (Product.IsSelling());
var query = from p in Data.Products.Where (newKids.Or (classics))
select p;
Note that I've only got this to work with Linq 2 SQL. EntityFramework does not implement Expression.Invoke, which is required for this method to work. I have a question regarding this issue here.
It isn't the prettiest thing but you can use a lambda expression and pass your conditions optionally. In TSQL I do a lot of the following to make parameters optional:
WHERE Field = #FieldVar OR #FieldVar IS NULL
You could duplicate the same style with a the following lambda (an example of checking authentication):
MyDataContext db = new MyDataContext();
void RunQuery(string param1, string param2, int? param3){
Func checkUser = user =>
((param1.Length > 0)? user.Param1 == param1 : 1 == 1) &&
((param2.Length > 0)? user.Param2 == param2 : 1 == 1) &&
((param3 != null)? user.Param3 == param3 : 1 == 1);
User foundUser = db.Users.SingleOrDefault(checkUser);
}
I had a similar requirement recently and eventually found this in he MSDN.
CSharp Samples for Visual Studio 2008
The classes included in the DynamicQuery sample of the download allow you to create dynamic queries at runtime in the following format:
var query =
db.Customers.
Where("City = #0 and Orders.Count >= #1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");
Using this you can build a query string dynamically at runtime and pass it into the Where() method:
string dynamicQueryString = "City = \"London\" and Order.Count >= 10";
var q = from c in db.Customers.Where(queryString, null)
orderby c.CompanyName
select c;
You can create and use this extension method
public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
return isToExecute ? source.Where(predicate) : source;
}
Just use C#'s && operator:
var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")
Edit: Ah, need to read more carefully. You wanted to know how to conditionally add additional clauses. In that case, I have no idea. :) What I'd probably do is just prepare several queries, and execute the right one, depending on what I ended up needing.
You could use an external method:
var results =
from rec in GetSomeRecs()
where ConditionalCheck(rec)
select rec;
...
bool ConditionalCheck( typeofRec input ) {
...
}
This would work, but can't be broken down into expression trees, which means Linq to SQL would run the check code against every record.
Alternatively:
var results =
from rec in GetSomeRecs()
where
(!filterBySeverity || rec.Severity == severity) &&
(!filterByUser|| rec.User == user)
select rec;
That might work in expression trees, meaning Linq to SQL would be optimised.
Well, what I thought was you could put the filter conditions into a generic list of Predicates:
var list = new List<string> { "me", "you", "meyou", "mow" };
var predicates = new List<Predicate<string>>();
predicates.Add(i => i.Contains("me"));
predicates.Add(i => i.EndsWith("w"));
var results = new List<string>();
foreach (var p in predicates)
results.AddRange(from i in list where p.Invoke(i) select i);
That results in a list containing "me", "meyou", and "mow".
You could optimize that by doing the foreach with the predicates in a totally different function that ORs all the predicates.