I can't read complicated lambda expression and only the super basic lambda expression only I know I am just starting to study lambda.
As my title above how to convert this lambda into linq?
var train = db.Certificates
.Join(db.TrainingSchedules, a => a.CertificateId, b => b.CertificateId, (a, b) => new { a, b })
.Where(x => x.a.Year.Value.Year == year && x.a.TrainingTypeId.Value == trainingTypeId && x.a.IsApproved.Value && x.b.EndDate >= DateTime.Now)
.Select(z => z.a).Distinct().Where(q => !db.Registrations.Where(s => s.EmployeeId == empId).Select(t => t.Certificate).Any(u => u.CertificateId == q.CertificateId));
Can someone explain to me why it has a different variables?. Like x , q , z , b?
As my title above how to convert this lambda into linq?
Have you every worked in Linq to know how does it looks like ?
If the above specified code is not Linq, then what is Linq, Lambda is an integral part of fluent representation of the Linq, since most of the APIs would need a Func delegate, that's where Lambda comes in.
Now regarding x , q , z , b, what do they represent ?
The Linq APIs are nothing but extension methods for IEnumerable<T>, like List<T>, Dictionary<TK,TV>, whether we write the fluent' or theSqlsyntax these variables represent, each and every element in the collection, which is processed as part of logic provided by theFunc Delegate, you can certainly use and should use more credible variable to represent the exact thing, similar to other parts of code whereint x, float y, DateTime z` is a bad way to code
Regarding the statement posted above consider following changes:
Rename a,b as cert,ts, which refers to a Certificate and Training Schedule classes respectively
Instead of generating anonymous type new { a, b }, create a class like CerificateTrainingSchedule that has all the elements of the Certificate and Training Schedule class respectively, it would be easier to work as you move forward
Rename x as cts, to represent combined CerificateTrainingSchedule
If its easy to read then separate Where clause in multiple chains, like:
.Where(cts => cts.a.Year.Value.Year == year)
.Where(cts => cts.a.TrainingTypeId.Value == trainingTypeId)
.Where(cts => cts.a.IsApproved.Value)
.Where(cts => cts.b.EndDate >= DateTime.Now)
Similarly the names of other variables can be modified to represent the true class and its objects, instead of random a,b,c,d. Also calls can be chained for clear understanding of the logic
Edit - // Modified Linq Query
// Joined / Merged version of Certificate and Training Schedule, add more fields as required, current one is based on certain assumptions of the fields / properties in the Certificate & TrainingSchedule classes respectively
public class CertificateTrainingSchedule
{
public int Year {get; set;} // Certificate Class Property
public int TrainingTypeId {get; set;} // Certificate Class Property
public bool IsApproved {get; set;} // Certificate Class Property
public DateTime EndDate {get; set;} // TrainingSchedule Class Property
}
var train = db.Certificates
.Join(db.TrainingSchedules, cert => cert.CertificateId, ts => ts.CertificateId, (cert, ts) => new CertificateTrainingSchedule{ Year = cert.Year, TrainingTypeId = cert.TrainingTypeId, IsApproved = cert.IsApproved,EndDate = ts.EndDate})
.Where(cts => cts.Year == year)
.Where(cts => cts.TrainingTypeId == trainingTypeId)
.Where(cts => cts.IsApproved)
.Where(cts => cts.EndDate >= DateTime.Now)
.Select(cts => new {cts.Year,cts.TrainingTypeId,cts.IsApproved})
.Distinct() // Allowing anonymous type to avoid IEqualityComparer<Certificate>
.Where(certMain => !db.Registrations.Where(s => s.EmployeeId == empId)
.Select(cert => new Certificate{Year = cert.Year,TrainingTypeId = cert.TrainingTypeId,IsApproved = cert.IsApproved})
.Any(cert => cert.CertificateId == certMain.CertificateId))
I assume that by your question, you mean you want a query expression that is equivalent to calling the LINQ methods explicitly. Without a good Minimal, Complete, and Verifiable code example, it's impossible to know for sure what a correct example would be. However, the following is I believe what you're looking for:
var train =
from q in
(from x in
(from a in db.Certificates
join b in db.TrainingSchedules on a.CertificateId equals b.CertificateId
select new { a, b })
where x.a.Year.Value.Year == year && x.a.TrainingTypeId == trainingTypeId &&
x.a.IsApproved.Value && x.b.EndDate >= DateTime.Now
select x.a).Distinct()
where !(from s in db.Registrations where s.EmployeeId == empId select s.Certificate)
.Any(u => u.CertificateId == q.CertificateId)
select q;
Note that not all of the LINQ methods have a C# query expression language equivalent. In particular, there's no equivalent for Distinct() or Any(), so these are still written out explicitly.
Can someone explain to me why it has a different variables?. Like x , q , z , b?
Each lambda expression has the input on the left side of the => and the result expression on the right. The variables you're referring to are the inputs. These are commonly written using single letters when writing lambda expressions, because the a lambda expression is so short, the meaning can be clear without a longer variable name. For that matter, independent lambda expressions could even use the same variable name.
Note that in the query expression syntax, not all of the variables "made it". In particular, we lost z and t because those variables were superfluous.
In an expression this long, it's possible you might find longer variable names helpful. But it's a trade-off. The query expression language is meant to provide a compact way to represent queries on data sources. Longer variable names could make it harder to understand the query itself, even as it potentially makes it easier to understand the intent of each individual part of the expression. It's very much a matter of personal preference.
I can't read complicated lambda expression and only the super basic lambda expression only I know I am just starting to study lambda.
Try reading this:
var train = db.Certificates
.Where(c => c.Year.Value.Year == year &&
c.TrainingTypeId.Value == trainingTypeId &&
c.IsApproved.Value &&
c.TrainingSchedules.Any(ts => ts.EndDate >= DateTime.Now) &&
!c.Registrations.Any(r => r.EmployeeId == empId));
If you can, then you are just fine.
Note that this is not an exact translation of the sample query, but is functionally equivalent (should produce the same result). The sample query is a good example of badly written query - variable naming, unnecessary multiplicative Join which requires then a Distinct operator (while GroupJoin would do the same w/o the need of Distinct), inconsistent handling of two similar detail criteria (Join for TrainingSchedules and Any for Registrations), overcomplicated criteria for Registrations part etc.
Shortly, don't write such queries. Concentrate on the desired result from the query and use the most logical constructs to express it. Avoid manual joins when you have navigation properties. If you don't have navigation properties, then add them to the model - it's easy one time action which helps a lot when writing queries. For instance, in my translation I assume you have something like this:
class Certificate
{
// Other properties ...
public ICollection<TrainingSchedule> TrainingSchedules { get; set; }
public ICollection<Registration> Registrations { get; set; }
}
class TrainingSchedule
{
// Other properties ...
public Certificate Certificate { get; set; }
}
class Registration
{
// Other properties ...
public Certificate Certificate { get; set; }
}
UPDATE: Here is the same using the query syntax:
var train =
from c in db.Certificates
where c.Year.Value.Year == year &&
c.TrainingTypeId.Value == trainingTypeId &&
c.IsApproved.Value &&
c.TrainingSchedules.Any(ts => ts.EndDate >= DateTime.Now) &&
!c.Registrations.Any(r => r.EmployeeId == empId)
select c;
Related
I'm trying to use lambda to join 5 tables. What I would like to know is if I'm doing it right and if there's a better way of doing it. Any suggestion is much appreciated.
var myList = _context.Event
.Join(_context.EventHost,
e => e.EventHostId,
eh => eh.Id,
(e, eh) => new { Event = e, EventHost = eh })
.Join(_context.EventAddress,
eh => eh.EventHost.EventAddressID,
ea => ea.ID,
(eh, ea) => new { EventAddress = ea, EventHost = eh })
.Join(_context.OrderHeaderGroup,
ea => ea.EventHost.Event.Id,
ohg => ohg.EventId,
(ea, ohg) => new { EventAddress = ea, OrderHeaderGroup = ohg })
.Join(_context.OrderHeader,
ohg => ohg.OrderHeaderGroup.OrderHeaderGroupID,
oh => oh.OrderHeaderGroupID,
(oh, ohg) => new { OrderHeader = oh, OrderHeaderGroup = ohg })
.Select(c => new {
c.OrderHeader.OrderHeaderGroup.EventId,
c.OrderHeader.EventAddress.EventAddress.Name,
c.OrderHeader.EventAddress.EventAddress.Phone,
c.OrderHeader.EventAddress.EventAddress.Address1,
c.OrderHeader.EventAddress.EventAddress.Address2,
c.OrderHeader.EventAddress.EventAddress.City,
c.OrderHeader.EventAddress.EventAddress.Country,
c.OrderHeader.EventAddress.EventAddress.PostalCode,
c.OrderHeader.EventAddress.EventAddress.Email,
c.OrderHeader.EventAddress.EventHost.Event.ConsultantId,
c.OrderHeader.EventAddress.EventHost.Event.StartDate,
c.OrderHeader.EventAddress.EventHost.Event.EndDate,
}).Where(x => x.ContactId == contactId && x.StartDate >= newStartDate && x.EndDate <= newEndDate).ToList();
Yes, use the query syntax, as is the recommendation from Microsoft: Check here
At compile time, query expressions are converted to Standard Query Operator method calls according to the rules set forth in the C# specification. Any query that can be expressed by using query syntax can also be expressed by using method syntax. However, in most cases query syntax is more readable and concise.
from event in _context.Event
join eventHost in _context.EventHost
on event.EventHostId equals eventHost.Id
join eventAddress in _context.EventAddress
on eventHost.EventAddressId equals eventAddress.Id
// etc
select new {/*You new anonymous type here*/}
The issue here isn't preferential code readability. Which, BTW, I think Lambda is far more readable than Query form. But that's because I'm a programmer.
The issue is that you are trying to take a SQL Developer's perspective and SHOVE It into programming. I did that when LinqToEntities was first a thing, where I start in SSMS et al, I write a query, then I do Transliteration into C#.
The solution is to use your ORM. If you use a feature rich ORM, you should have something called Navigation Properties. These already have implicit joins for you that are either implicitly, or explicitly declared in your Fluent mapping. So you shouldn't have to re-describe every join every time you write a LINQ statement.
If you trust the language and configure your system correctly, you can end up with something like this:
// This is not shortened, this should be the whole thing.
var myList = _context.OrderHeader
.Where(x => x.EventAddress.Something.ContactId == contactId
&& x.EventAddress.Something.StartDate >= newStartDate
&& x.EventAddress.Something.EndDate <= newEndDate)
.Select(c => new DetailEntityView(c)).ToList();
I also suggest using "View" classes. These aren't entities that are configured to persist to the DB, but are like DB Views, in C#. They are less used classes that just transfer data.
public class DetailEntityView
{
public int EventId { get; set; }
public string Name { get; set; }
// ... etc
public DetailEntityView(OrderHeader c)
{
EventId = c.OrderHeaderGroup.EventId;
Name = c.EventAddress.EventAddress.Name;
// ... etc
}
}
Now, some ORMs are less featured than the EF's and NH's of the world. So in that case, I'm sorry you or your team chose something else.
Also, all of your joins are on IDs, which is as it should be. But SOME Custom queries join on field that are not constrained, like name fields or other values. In those cases, you do need to add those specific joins to your query. But 1.) Ew. And 2.) Why? And 3.) Then you can do what you did in your OP.
Presumed I have a table "Article" with multiple columns, e.g.:
CREATE TABLE dbo.Article (
Id int NOT NULL,
ProducerId INT NOT NULL,
Barcode nvarchar(50) NOT NULL,
DescriptionText nvarchar(50) NOT NULL,
ActiveFlag BIT NOT NULL
)
In my ASP.NET Core application I am using LINQ to query that table, e.g.:
IQueryable<Article> query = _context.Article
.Where( p => p.Active == true );
That works of course.
Now I am getting some parameter, which is a very simple Object with a List < ArticleQuery > or alternativly a IEnumerable < ArticleQuery >.
ArticleQuery.cs:
public class ArticleQuery
{
[Required]
public IEnumerable<ArticleRequest> Parts { get; set; }
}
public class ArticleRequest
{
public int ProducerId { get; set; }
public string Barcode { get; set; }
}
Actually I have no clue how to integrate that into LINQ. I tried lots of stuff, but in the end I never figured out to get something like this join:
IQueryable<Article> query = _context.Article
.Join(articleQuery.ArticleRequest,
x => new { a = x.Barcode, b = x.Barcode},
y => new { a = y.ProducerId, b = y.ProducerId},
(x, y) => x);
Also this pseudo-code didn't work (but the Join seems to be the better try):
IQueryable<Article> query = _context.Article.Where( p =>
p.Active == true &&
articleQuery.ArticleRequest.ProducerId.Contains(p.ProducerId) &&
articleQuery.ArticleRequest.Barcode.Contains(p.Barcode)
);
Any ideas how that works correctly?
Unfortunately, this is a situation that normal LINQ to Entities code can't handle very well.
The syntax you'd want to use looks like this:
IQueryable<Article> query = _context.Article
.Where( p =>
p.Active == true &&
articleQuery.ArticleRequest.Any(
r => r.ProducerId == p.ProducerId &&
r.ArticleRequest.Barcode == p.Barcode
)
);
Unfortunately, LINQ to Entities can't handle mixing in-memory collections into database queries like this.
In theory, you could represent your ArticleRequest as a model in the database and perform a query similar to the one above, but based on an IQueryable<ArticleRequest> from your context rather than just a parameter variable. But that would be a terribly hacky workaround.
Some people resort to materializing the entire set of Article objects so they can use a LINQ to Objects provider:
IQueryable<Article> query = _context.Article
.AsEnumerable() // DANGER: materializes *all* the articles
.Where( p =>
p.Active == true &&
articleQuery.ArticleRequest.Any(
r => r.ProducerId == p.ProducerId &&
r.ArticleRequest.Barcode == p.Barcode
)
);
But this approach won't scale well as you get more articles.
Another approach is to build expressions trees dynamically, which would result in an Expression that looks like this:
p =>
p.Active == true &&
((p.ProducerId == 12 && p.BarCode == "12345") ||
(p.ProducerId == 13 && p.BarCode == "54321") ||
...
)
But the code required to produce this kind of statement dynamically is pretty ugly and very difficult to understand.
A better solution would probably be to use a bunch of Unions. One concise way to represent that is:
IQueryable<Article> query = articleQuery.ArticleRequest.Aggregate(
_context.Article.Where(p => false),
(q, r) => q.Union(context.Article.Where(p =>
r.ProducerId == p.ProducerId &&
r.ArticleRequest.Barcode == p.Barcode))
)
.Where(p => p.Active == true);
This will produce a SQL query that unions the results of several one-off queries against each other. With a little more C# code, you could remove the superfluous "Where False" part of the query.
There are other libraries like Dynamic LINQ and LINQKit which are designed to help with this sort of problem, but it's been a long time since I've played with them and I can't vouch for how well they'll work against modern versions of Entity Framework.
Alternatively, you can avoid using LINQ entirely and just construct some custom SQL for this one query. That's probably what I'd do. Just be careful to guard against SQL injection on that barcode!
I solved the problem by myself. Not that perfect, but it's working.
I created a new database for my webservice with a table "RequestArticles":
CREATE TABLE dbo.RequestArticle(
RequestId UNIQUEIDENTIFIER NOT NULL,
ProducerId INT NOT NULL,
Barcode NVARCHAR(30) NOT NULL
)
I wrote my requests into that table:
Guid requestGuid = Guid.NewGuid();
foreach (var requestArticle in requestedArticles.Articles) requestArticle.RequestId = requestGuid;
_context.RequestArticle.AddRange(requestedArticles.Articles); //IEnumerable list of articles
_context.SaveChanges();
After that I just manually joined my new request-table.
IQueryable<Articles> query = _context.Articles.FromSql<Articles>(
#"SELECT a.*, ra.Barcode AS RequestedBarcode
FROM dbo.Articles a
INNER JOIN Webservice.dbo.RequestArticle ra
ON ra.ProducerId = a.ProducerId AND ra.Barcode = a.Barcode
WHERE ra.RequestId = {1}",
requestGuid);
At the moment I am suffering to replace the new static database-name within the SQL-String with a different string from my new context, but the basics work fast like a charm. Even though it's not the solution I was hoping for, as I thought LINQ+EF are smarter than that. :-)
Question: How do I initialize var in the following code? [Then, of course, I'll remove var declaration from the if statement]
The last line of the following code returns the well know error: The name lstCurrent does not exists in current context. Clearly the error is because the var is defined inside if statement and used outside if statement
Note: I'm selecting only a few columns from the table and hence dealing with anonymous type. Some examples I saw online did not work - probably since my code is selecting anonymous type. But this is just a guess.
var lstCurrent =????;
if(Type==1)
var lstCurrent =_context.Customers().Where(t =>t.type=="current").Select(c => new { c.LastName, c.City});
else
lstCurrent = _context.Customers().Where(...).Select(...)
return View(lstCurrent.ToList());
var is not a type - it means "I don't care to (or can't) specify what the type is - let the compiler do it for me".
In your case, you're assigning it the result of one of two queries, one of which returns an anonymous type, so you can't specify the type since you don't know the name of the anonymous type (hence the term "anonymous").
In order to use var, the compiler needs some expression at initialization to know what the actual type is.
I'd suggest something like:
var lstCurrent = Type==1
? _context.Customers().Where(t =>t.type=="current").Select(c => new { c.LastName, c.City})
: _context.Customers().Where(...).Select(...)
But note that your "selects" must return the same type (or anonymous types with the exact same fields) or you won't be able to use var.
In the end I would try to bake the condition into your Where clause for less repetetive code:
bool isTypeOne = Type==1;
var lstCurrent = _context.Customers()
.Where(t => isTypeOne ? t.type=="current" : ...)
.Select(c => new { c.LastName, c.City})
Try
IEnumerable lstCurrent;
if(Type == 1)
lstCurrent = foo;
else
lstCurrent = bar;
How do I initialize var in the following code? [Then, of course, I'll
remove var declaration from the if statement]
This is not possible since the type of the object you assign at the left should be known. For instance
var a = "text";
The type of a is known at compile time since the right hand expression is a string. This cannot be done with a sequence of anonymous types, like the one you define.
I can see two options. One is that D Stanley already mentioned. The other is to define a class with two properties like below:
public class PersonCity
{
public string LastName { get; set; }
public string City { get; set; }
}
and then project each element of your query to a PersonCity object.
lstCurrent context.Customers()
.Where(t =>t.type=="current")
.Select(c => new PersonCity
{
LastName = c.LastName,
City = c.City
});
Doing so, you can define now you lstCurrent as below:
var lstCurrent = Enumerable.Empty<PersonCity>();
Important Note
In case of your queries return different types, the above are meaningless. Both queries (one in if and the other at else) should return the same type.
This is a common trap when expecting to use an implicit type declaration or when refactoring code that already has an implicit type (var). The current accepted answer is very much valid but reducing all expression variations into a single 1-liner linq expression can easily impact on readability of the code.
The issue in this case is complicated by the projection to an anonymous type at the end of the query, which can be solved by using an explicit type definition for the projection, but it is simpler to break up the query construction into multiple steps:
var customerQuery = _context.Customers().AsQueryable();
if (Type == 1)
customerQuery = customerQuery.Where(t => t.type == "current");
else
customerQuery = customerQuery.Where(...);
... // any other filtering or sorting expressions?
var lstCurrent = customQuery.Select(c => new { c.LastName, c.City});
return View(lstCurrent.ToList());
or of course that last segment could have been a one liner or if there are no further references to lstCurrent the compiler may optimise that into the following:
return View(customQuery.Select(c => new { c.LastName, c.City}).ToList());
In this example I have deliberately cast to IQueryable<T> to ensure this solution is compatible with both IQueryable<T>/DbSet<T> contexts and repository style IEnumerable<T> contexts.
This variation is usually the first that comes to mind, but we are still declaring the source of the query in two places, which increases the ambiguity of this code and the risk of divergence in later refactoring (by accidentally editing only one branch and not maintaining the code in the other branch):
IQueryable<Customer> customerQuery = null;
if (Type == 1)
customerQuery = _context.Customers().Where(t => t.type == "current");
else
customerQuery = _context.Customers().Where(...);
... // any other filtering or sorting expressions?
var lstCurrent = customQuery.Select(c => new { c.LastName, c.City});
return View(lstCurrent.ToList());
A different solution is to explicitly define the output as its own concrete class:
public class CustomerSummary
{
public string LastName { get;set; }
public string City { get;set; }
}
...
List<Customers> customers = null;
if (Type == 1)
customers = _context.Customers().Where(c => c.type == "current")
.Select(c => new CustomerSummary
{
LastName = c.LastName,
City = c.City
}).ToList();
else
customers = _context.Customers().Where(c => ...)
.Select(c => new CustomerSummary
{
LastName = c.LastName,
City = c.City
}).ToList();
... // any other filtering or sorting expressions?
return View(customers);
It's a lot of code for a once-off, but if you make it abstract enough it could be re-used for other scenarios, I would still combine this with the first code example, that keeps the source, filter and projection logic separated, over the lifetime of an application these 3 elements tend to evolve differently, so separating out the code makes refactoring or future maintenance easier to complete and review.
In our application we want to have standard methods for various conditions in our database. For instance, we have different types of transactions, and we want to create standard methods for retrieving them within other queries. However, this gives us the error:
Method '' has no supported translation to SQL
The method might look like this:
public static bool IsDividend(this TransactionLog tl)
{
return tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
}
To be used as such:
var dividends = ctx.TransactionLogs.Where(x => x.IsDividend());
Of course, if I copy the logic from IsDividend() into the Where clause, this works fine, but I end up duplicating this logic many places and is hard to track down if that logic changes.
I think if I would convert this to an expression like this it would work, but this is not as preferable a setup as being able to use methods:
public Expression<Func<TransactionLog, bool>> IsDividend = tl => tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
var dividends = ctx.TransactionLogs.Where(IsDividend);
Is there a way to force Linq to evaluate the method as an expression? Or to "transform" the method call into an expression within a linq query? Something like this:
var dividends = ctx.TransactionLogs.Where(tl => ToExpression(tl.IsDividend));
We are using Linq-to-SQL in our application.
Well having static property containing the expressions seems fine to me.
The only way to make it work with Methods would be to create a method which returns this expression, and then call it inside where:
public class TransactionLog
{
Expression<Func<TransactionLog, bool>> IsDividend() {
Expression<Func<TransactionLog, bool>> expression = tl => tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
return expression;
}
}
public class TransactionLogExtension
{
Expression<Func<TransactionLog, bool>> IsDividend(this TransactionLog log) {
Expression<Func<TransactionLog, bool>> expression = tl => tl.SourceTypeID == (int)JobType.Dividend || tl.SourceTypeID == (int)JobType.DividendAcct;
return expression;
}
}
and use it via
var dividends = ctx.TransactionLogs.Where(TransactionLog.IsDividend());
or as extension method
var dividends = ctx.TransactionLogs.Where(x.IsDividend());
But none of it is will work with var dividends = ctx.TransactionLogs.Where(x => x.IsDividend()); because x => x.IsDividend(); itself is an expression tree and your database provider can't translate "IsDividend" into an SQL statement.
But the other two options will at least allow you to pass in parameters (which doesn't work if you store the Expressions as instance or static properties).
I think that LINQ to SQL doesn't fully supports even common and build-in functions. (At least EF does not do it). And moreover - when it deals with user defined methods. I predict that your variant with expression will fall as well as the variant with method call unless you call it after enumeration (ToList or similar method). I suggest to keep the balance between 1) stored procedures at server, 2) common conditions hardcoded in Where clauses in C#, 3) expression trees generation in C#. All these points are relatively complex, for sure (and to my mind there is no easy and general solution).
Try using Extension Methods, like so:
public static class TransactionLogExtensions {
public static IQueryable<TransactionLog> OnlyDividends(this IQueryable<TransactionLog> tl)
{
return tl.Where(t=>t.SourceTypeID == (int)JobType.Dividend || t.SourceTypeID == (int)JobType.DividendAcct);
}
}
Call it like so:
var dividends=db.TransactionLogs.OnlyDividends();
or
var dividends=db.TransactionLogs.OnlyDividends().OrderBy(...);
For this scenario you can use Func. Linq works very good with those.
Here is the simple example of using Func in Linq query. Feel free to modify and use.
Func<int,bool> isDivident = x => 3==x;
int[] values = { 3, 7, 10 };
var result = values.Select (isDivident );
Probably a few things wrong with my code here but I'm mostly having a problem with the syntax. Entry is a model for use in Entries and contains a TimeStamp for each entry. Member is a model for people who are assigned entries and contains an fk for Entry. I want to sort my list of members based off of how many entries the member has within a given period (arbitrarily chose 30 days).
A. I'm not sure that the function I created works correctly, but this is aside from the main point because I haven't really dug into it yet.
B. I cannot figure out the syntax of the Linq statement or if it's even possible.
Function:
private bool TimeCompare(DateTime TimeStamp)
{
DateTime bound = DateTime.Today.AddDays(-30);
if (bound <= TimeStamp)
{
return true;
}
return false;
}
Member list:
public PartialViewResult List()
{
var query = repository.Members.OrderByDescending(p => p.Entry.Count).Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp));
//return PartialView(repository.Members);
return PartialView(query);
}
the var query is my problem here and I can't seem to find a way to incorporate a boolean function into a .where statement in a linq.
EDIT
To summarize I am simply trying to query all entries timestamped within the past 30 days.
I also have to emphasize the relational/fk part as that appears to be forcing the Timestamp to be IEnumerable of System.Datetime instead of simple System.Datetime.
This errors with "Cannot implicitly convert timestamp to bool" on the E.TimeStamp:
var query = repository.Members.Where(p => p.Entry.First(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
This errors with Operator '<=' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'System.DateTime'
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
EDIT2
Syntactically correct but not semantically:
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count);
The desired result is to pull all members and then sort by the number of entries they have, this pulls members with entries and then orders by the number of entries they have. Essentially the .where should somehow be nested inside of the .count.
EDIT3
Syntactically correct but results in a runtime error (Exception Details: System.ArgumentException: DbSortClause expressions must have a type that is order comparable.
Parameter name: key):
var query = repository.Members.OrderByDescending(p => p.Entry.Where(e => e.TimeStamp <= timeComparison));
EDIT4
Closer (as this line compiles) but it doesn't seem to be having any effect on the object. Regardless of how many entries I add for a user it doesn't change the sort order as desired (or at all).
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members.OrderByDescending(p => p.Entry.Select(e => e.TimeStamp.Day <= timeComparison).FirstOrDefault());
A bit of research dictates that Linq to Entities (IE: This section)
...var query = repository.Members.OrderByDescending(...
tends to really not like it if you use your own functions, since it will try to map to a SQL variant.
Try something along the lines of this, and see if it helps:
var query = repository.Members.AsEnumerable().Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp).OrderByDescending(p => p.Entry.Count));
Edit: I should just read what you are trying to do. You want it to grab only the ones within the last X number of days, correct? I believe the following should work, but I would need to test when I get to my home computer...
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit2: This may be a lack of understanding from your code, but is e the same type as p? If so, you should be able to just reference the timestamp like so:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.TimeStamp <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit3: In Edit3, I see what you are trying to do now (I believe). You're close, but OrderByDescending would need to go on the end. Try this:
var query = repository.Members
.Select(p => p.Entry.Where(e => e.TimeStamp <= timeComparison))
.OrderByDescending(p => p.Entry.Count);
Thanks for all the help Dylan but here is the final answer:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members
.OrderBy(m => m.Entry.Where(e => e.TimeStamp.Day <= timeComparison).Count());
return PartialView(query);
}