Can someone tell me why I can't select new objects from MS SQL database in Entity Framework in this way:
public static Expression<Func<LeaveDay, bool>> IsInDatesRange(DateTime startDate, DateTime endDate){
return ld => ld.StartDate <= endDate && ld.EndDate >= startDate;
}
this.ObjectContext.People.Select(p => new NewPeopleObject
{
Guid = p.Guid,
FirstName = p.FirstName,
LastName = p.LastName,
LeaveDays = p.CalendarData.LeaveDays.AsQueryable()
.Where(LeaveDayExpressions.IsInDatesRange(startDate, endDate))
.Select(ld => new LeaveDaySummary
{
StartDate = ld.StartDate,
EndDate = ld.EndDate,
})
})
Without AsQueryable() I can't compile application, because LeaveDayExpressions.IsInDatesRange is static Expression. I have tried pass only Func to Where clause but it throws Internal .NET Framework Data Provider error 1025. With Expression and AsQueryable on LeaveDays I get this exception:
Code supposed to be unreachable
People is ObjectSet collection with one CalendarData object on one People and CalendarData has EntityCollection set of LeaveDays.
NewPeopleObject is a class with few properties and IEnumarable LeaveDaySummaries collection.
What can I do to pass Expression to Where clause without parsing linq to sql error?
I can't test the exact case (ObjectContext, ObjectSet etc. indicate some older EF version), but I was able to reproduce both aforementioned runtime exceptions in the latest EF6.1.3 (using DbContext and DbSet) as well.
Let forget about Func approach - EF needs to translate the query to SQL, so Expression is a must. Which in turn requires AsQueryable(). So far so good.
The problem is that EF does not like custom methods inside expression tree - it usually has no issues with top level methods, but definitely has issue with nested calls like in the sample (the problematic expression is part of the outer select expression).
I can't tell why, but in most of the cases (including this and when possible) putting the expression into local variable outside of the query and using it inside resolves the issue:
var leaveDayPredicate = LeaveDayExpressions.IsInDatesRange(startDate, endDate);
var result = this.ObjectContext.People.Select(p => new NewPeopleObject
{
Guid = p.Guid,
FirstName = p.FirstName,
LastName = p.LastName,
LeaveDays = p.CalendarData.LeaveDays.AsQueryable()
.Where(leaveDayPredicate)
.Select(ld => new LeaveDaySummary
{
StartDate = ld.StartDate,
EndDate = ld.EndDate,
})
});
For more advanced scenarios (like expression using something from the outer expression), you might need some expression processing library, for instance LINQKit Invoke / Expand / AsExpandable custom extension methods etc.
Related
I am getting the following error:
Only parameterless constructors and initializers are supported in LINQ
to Entities.
I am trying to query data from a result set, and part of the new result set returns a new date field.
var result = debts.Select(x => new
{
x.Snowball.User.Email,
x.SnowballID,
x.Description,
x.ID,
x.DueDayOfMonth,
Due = new DateTime(ExecutionDate.Year,
ExecutionDate.Month,
x.DueDayOfMonth)
}).ToList();
Is there a way I can create this deduced datetime?
I came here looking for the same thing, but on further research found another way using DbFunctions.CreateDateTime in System.Data.Entity. Using this method your query would look like this:
var result = debts.Select(x => new
{
x.Snowball.User.Email,
x.SnowballID,
x.Description,
x.ID,
x.DueDayOfMonth,
Due = DbFunctions.CreateDateTime(ExecutionDate.Year,
ExecutionDate.Month,
x.DueDayOfMonth, 0,0,0)
}).ToList();
EDIT: This is for EF6 and later, for earlier versions of EF use System.Data.Entity.Core.Objects.EntityFunctions instead of DbFunctions
LINQ to Entities has a boundary for the query.
The IQueryable result allows one or more filters and selections etc. to be added step by step if you wish.
While the query is being added to nothing has hit the database yet; only when you access the result set will the query be constructed by the LINQ Provider (a single Transact SQL Query for example), then run on the database, and its result returned.
In the case you have shown, you are trying to create a new object (Date - but could also be one of your own classes) in the database's result set.
So, for each row returned from the database query you are trying to create a column from an object (from your code base) different for each row (need to pass the parameters to the constructor).
Because the underlying provider cannot guarantee to be able to create arbitrary objects from an external source on the fly, this is not allowed (prevented by the LINQ Provider).
The query that will be run against the database is 'debts'. This will return all of the rows from the query unfiltered (as there is no WHERE clause).
The next query will be run in your code. In your code you can use parameterised constructors - and is now LINQ To Objects.
So the way to distinguish the separate stages of the query, and do what you want to do, is to place the ToList() at the end of the query that runs against the database so that the query is generated, run, and the results returned.
Then construct and run your next query (in your code) on those results.
var result = debts.ToList().Select(x => new
{
x.Snowball.User.Email,
x.SnowballID,
x.Description,
x.ID,
x.DueDayOfMonth,
Due = new DateTime(ExecutionDate.Year, ExecutionDate.Month, x.DueDayOfMonth)
});
You could create your own Date class with a parameter-less constructor and set it using property initializer syntax like
Due = new Date(){Year = ExecutionDate.Year, Month = ExecutionDate.Month, Day = x.DueDayOfMonth}
Then just define a cast from Date to DateTime.
You may try this:
var result = debts.Select(x =>
new
{
x.Snowball.User.Email, x.SnowballID, x.Description, x.ID,
x.DueDayOfMonth,
exY = ExecutionDate.Year,
exM = ExecutionDate.Month,
exD = x.DueDayOfMonth)
}).ToList()
.Select(a => new {Email, SnowballID, Description, ID, Due=new DateTime(exY, exM, exD)})
.ToList()
I am trying to create a custom collection from an IQueryable object, where i am trying to perform a select statement but getting an error cannot convert to store expression. I am new to Lambda Expression. Kindly help me how to fix this problem.
Getting error at line c.Event.FirstUpper()
public static string FirstCharToUpper(string input)
{
if (String.IsNullOrEmpty(input))
return string.Empty;
var trimmed = input.Trim();
return trimmed.First().ToString().ToUpper() + trimmed.Substring(1);
}
public static Expression<Func<string, string>> GetFirstCaseToUpperExpression()
{
var expression = NJection.LambdaConverter.Fluent.Lambda.TransformMethodTo<Func<string, string>>()
.From(() => StringFormatter.FirstCharToUpper)
.ToLambda();
return expression;
}
Calling the Expression
return new List<LoggerModel>(
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode)).OrderByField(sortBy, ascendingOrder).Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
I suppose you are using Entity Framework or a smiliar O/R mapper.
Think about what you are doing here: you are writing a LINQ query that should be executed against your database. To do this, it will translate your LINQ query into a SQL query which will then be executed against your database.
But FirstCharToUpper() is a custom method in your code. Your database does not know anything about it, so your O/R mapper's LINQ provider cannot translate it into anything meaningful in SQL, hence you get the error.
So what you need to do is to first "finish" the query against your database to have the results in-memory and after that, apply any further processing that can only be done within the boundaries of your code on that in-memory collection.
You can do this simply by inserting .AsEnumerable() in your LINQ query before you do the select with your custom expression:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.AsEnumerable()
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.FirstUpper()
})
When calling AsEnumerable(), the query against your database will be executed and the results are copied into an IEnumerable in memory. The Select() afterwards will now already be executed against the in-memory collection and not against the database anymore, thus it can use your custom FirstCharToUpper() method.
Edit based on your comments below:
Everything above is still valid, but in the comments you said your function needs to return IQueryable. In your case, what your FirstCharToUpper() method is doing is pretty simple and the LINQ-to-Entities provider does support methods like ToUpper and Substring. So I'd recommend to simply get rid of your helper method and instead write your LINQ query to do just that with methods that Entity Framework can translate to valid SQL:
logDB.PELoggers
.Where(c => (c.SubscriberCode == SubscriberCode))
.OrderByField(sortBy, ascendingOrder)
.Select(c => new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = c.Event.Substring(0, 1).ToUpper()
+ c.Event.Substring(1)
})
This will result in a SQL query that will already return the content in Event with an uppercase first letter right from the database.
To also support the IsNullOrEmpty check and the Trim you are doing (both also supported by LINQ-to-Entities) I recommend to change the lambda syntax to the LINQ query syntax so you can use the let statement for the trimming, which makes the code cleaner:
from c in logDB.PELoggers
let trimmedEvent = c.Event.Trim()
where c.SubscriberCode == SubscriberCode
select new LoggerModel()
{
DateTime = c.DateTime.Value,
Event = !string.IsNullOrEmpty(trimmedEvent)
? trimmedEvent.Substring(0, 1).ToUpper()
+ trimmedEvent.Substring(1)
: string.Empty
};
In case you do not want to have this done in the LINQ query, you would need to do the uppercasing at some point later when your query against the DB has been executed, for example right in the View that will show your data. Or one option could be to apply the uppercasing in the Event property setter of your LoggerModel:
public class LoggerModel
{
// ...
private string event;
public string Event
{
get { return event; }
set { event = FirstCharToUpper(value); }
}
// ...
}
But there is no way to make custom functions work inside LINQ-to-Entities queries.
I am trying to find a way to move UTC time to Local before doing a sql grouping. I am using the System.Linq.Dynamic (managed here https://github.com/kahanu/System.Linq.Dynamic ). It works great for doing dynamic selects without having at compile time the required fields. In our case, we store all datetimes in UTC. In this dynamic select, its possible that someone would want to do a groupby on the Hour, year, month, etc. We have to move the data to a local time in this case, to prevent confusion.
Example:
var select = queryable.Select(string.Format("new ({0}, {1})", datetimeColumn, someOtherColumn));
Normally in our tsql or even in entity framework using lambda expressions, you can add in your desired offset. But in the dynamic linq option, it appears that you can't perform any date operations such as DateTime.AddHours(x) or DateTime.Subtract(x) like you could with Linq2Sql. Entity Framework 6 wants you to use DbFunctions.AddHours(x), etc. However the dynamic linq code, without modification, will not accept the DbFunctions without error.
Example:
var select = queryable.Select(string.Format("new (System.Data.Entity.DbFunctions.AddHours({0},7) as {0}, {1})", datetimeColumn, someOtherColumn));
Returns an error: No property or field 'System' exists in type XXX
(removing the namespace doesn't help).
Using the desired code:
var select = queryable.Select(string.Format("new ({0}.AddHours(7), {1})", datetimeColumn, someOtherColumn));
Results with error: LINQ to Entities does not recognize the method 'System.DateTime AddHours(Double)' method, and this method cannot be translated into a store expression.
I want to have SQL perform the datetime math prior to the groupby. Once the groupby happens, there is no concept of UTC any longer as the user will see the localized result set.
I'm afraid that Ill just to update my github fork with some extensions to support passing in the entity framework extensions, but before I did, wanted to see if anyone else has a solution or idea.
Note: I am not using DateTimeOffset due to possibilities of changing SQL data store technologies.
You can post process the query expression with custom ExpressionVisitor and replace the unsupported methods with their DbFunctions equivalents.
Here is a starting point just to get the idea:
public static class QueryableExtensions
{
public static IQueryable BindDbFunctions(this IQueryable source)
{
var expression = new DbFunctionsBinder().Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery(expression);
}
class DbFunctionsBinder : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object != null && node.Object.Type == typeof(DateTime))
{
if (node.Method.Name == "AddHours")
{
var timeValue = Visit(node.Object);
var addValue = Visit(node.Arguments.Single());
if (timeValue.Type != typeof(DateTime?)) timeValue = Expression.Convert(timeValue, typeof(DateTime?));
if (addValue.Type != typeof(int?)) addValue = Expression.Convert(addValue, typeof(int?));
var methodCall = Expression.Call(
typeof(DbFunctions), "AddHours", Type.EmptyTypes,
timeValue, addValue);
return Expression.Convert(methodCall, typeof(DateTime));
}
}
return base.VisitMethodCall(node);
}
}
}
and sample usage:
var select = queryable
.Select(string.Format("new ({0}.AddHours(7), {1})", datetimeColumn, someOtherColumn))
.BindDbFunctions();
I have the following methods:
private IEnumerable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLS() {
IQueryable<int> talismanIdCollection = this._cc.TLSTransactionView.Select(x => x.kSECSYSTrans);
return this._cc.CTNTransactionView
.Where(x => !talismanIdCollection.Contains(x.kSECSYSTrans));
}
public IEnumerable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLSPast24Hours() {
DateTime previousDate = DateTime.Now.Date.AddDays(-1.0);
return this.RetrieveCTNTransactionsNotInTLS()
.Where(x => x.dSECSYSTimeStamp >= previousDate);
}
public IEnumerable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo() {
DateTime previousDate = DateTime.Now.Date.AddDays(-1.0);
IQueryable<int> talismanIdCollection = this._cc.TLSTransactionView
.Select(x => x.kSECSYSTrans);
return this._cc.CTNTransactionView
.Where(x => !talismanIdCollection.Contains(x.kSECSYSTrans))
.Where(x=> x.dSECSYSTimeStamp >= previousDate);
}
For some reason, the SQL output generated by Entity Framework 6 does not match the results.
The RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo() method will properly give a SQL Output Statement that has the following:
select ...... from ... where ... AND ([Extent1].[dSECSYSTimeStamp] >= #p__linq__0)}
The other one does not have the filter for the dSECSYSTimeStamp when I View the SQL Statement Output.
The methods I am comparing are the RetrieveCTNTransactionsNotInTLSPast24Hours() and the RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo().
I have compared the SQL using VS as well as attaching a Debug.Writeline() to the Database.Log in the context.
From debugging and looking at the SQL output, one seems to contain the date filter whereas the other doesn't and yet they both provide the correct result.
I have tried looking at the SQL (by breakpointing and seeing the output) from using the following:
System.Diagnostics.Debug.WriteLine("Running first method");
var result = this.repo.RetrieveCTNTransactionsNotInTLSPast24Hours();
var count = result.Count();
System.Diagnostics.Debug.WriteLine("Running Second method");
var resultTwo = this.repo.RetrieveCTNTransactionsNotInTLSPast24HoursVersionTwo();
var count2 = resultTwo.Count();
I am using EF 6.0.
Note: The results are the same as both do exactly the same thing and output the same result. However, I am curious and would like to understand why the SQL Generated isn't the same?
The issue is you are returning an IEnumerable from your method. If you do this, you force SQL to run the query (unfiltered) and then use C# to then run the second query. Alter your internal query to return an IQueryable. This will allow the unexecuted expression tree to be passed into the second query, which will then be evaluated when your run it.
i.e.
private IQueryable<CTNTransactionsView> RetrieveCTNTransactionsNotInTLS()
You should then get the same SQL.
I think, you just define a query expression, but not use it immediately, in this time, I will add .ToList() in the Linq expression's end, but you have to change method's return type into a right type like List<kSECSYSTrans> before this operation. I just seldom use the type IEnumerable.
I am trying to get the columns dynamically. In NHibernate i can do this:
var list = _session.CreateCriteria(typeof(Person))
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("FirstName"))
.Add(Projections.Property("LastName"))
.Add(Projections.Property("Jersey"))
.Add(Projections.Property("FortyYard"))
.Add(Projections.Property("BenchReps"))
.Add(Projections.Property("VertJump"))
.Add(Projections.Property("ProShuttle"))
.Add(Projections.Property("LongJump"))
.Add(Projections.Property("PersonSchoolCollection"))
)
.SetResultTransformer(new NHibernate.Transform.AliasToBeanResultTransformer(typeof(Person)))
.List<Person>();
What is the equivalent in Linq?
As you also tag linq-to-sql and linq-to-entities I assume you are looking for an equivalent in Linq-to-Sql or Entity Framework. The two answers (so far) would be such equivalents if _session.Query<Person>() were replaced by context.Persons. (Although Darius's answer would throw an exception saying that you can't create entity instances in an entity query).
But apart from the possibility to use Select to create an ad hoc projection, one of the newer features of AutoMapper makes it even easier. Automapper is a very popular tool to map (say: project) a list of types to another list of types. But until recently its drawback was that it only worked on in-memory lists, i.e. the projection did not propagate into the SQL query. So it could not be used to cut back the number of queried fields (as NHibernate projections does).
This problem was described in Stop using AutoMapper in your Data Access Code (the title says it all). But it also offered a preliminary but excellent fix, which was later adopted by Automapper itself.
This feature makes it possible to write very succinct code like:
var dtoList = context.Persons.Project().To<PersonDto>();
(after the mapping between Person and PersonDto was registered in Automapper).
Now the SQL query only contains the fields that are used for PersonDto.
Isn't that would work:
_session.Query<Person>()
.Select(new {FirstName, LastName, Jersey, FortyYard})
.ToList()
.Select(x => new Person() {
FirstName = x.FirstName,
LastName = x.LastName,
Jersey = x.Jersey,
FortyYard = x.FortyYard
}
);
var list = from person in context.Persons
select new Person()
{
FirstName = person.FirstName,
LastName = person.LastName,
Jersey = person.Jersey,
FortyYard = person.FortyYard,
BenchReps = person.BenchReps,
VertJump = person.VertJump,
ProShuttle = person.ProShuttle,
LongJump = person.LongJump,
PersonSchoolCollection = person.PersonSchoolCollection
};