Entity Framework DateTime Functions - c#

I'm trying to filter by date based on two datetimes. MinTime and MaxTime are both nullable DateTimes in SQL, I want to filter based on these, but I'm getting an error:
This function can only be invoked from LINQ to Entities
public static IEnumerable<ExclusionRule> GetExclusionRules(AppointmentTypes? apptType, string resourceName, TimeSpan? minTime, TimeSpan? maxTime, DayOfWeek? day) {
using (var db = new BusinessObjectContainer()) {
db.ExclusionRules.MergeOption = MergeOption.NoTracking;
var items = db.ExclusionRules;
int intDay = day.HasValue ? (int) day.Value : -1,
intApptType = apptType.HasValue ? (int)apptType.Value : -1;
var filteredApptType = apptType.HasValue ? items.Where(i => !i.t_AppointmentType.HasValue|| i.t_AppointmentType == intApptType) : items;
var filteredResource = string.IsNullOrWhiteSpace(resourceName) ? filteredApptType : filteredApptType.Where(i => !string.IsNullOrEmpty(i.ResourceName) && resourceName.ToLower().Equals(i.ResourceName.ToLower()));
IEnumerable<ExclusionRule> filteredMinDate;
if (minTime.HasValue) {
filteredMinDate = filteredResource.Where(i => (!i.MinTime.HasValue) || (EntityFunctions.CreateTime(i.MinTime.Value.Hour, i.MinTime.Value.Minute, 0) >= minTime.Value));
} else {
filteredMinDate = filteredResource;
}
IEnumerable<ExclusionRule> filteredMaxDate;
if (maxTime.HasValue) {
// this throws the exception
filteredMaxDate = filteredMinDate.Where(i => (!i.MaxTime.HasValue) || EntityFunctions.CreateTime(i.MaxTime.Value.Hour, i.MaxTime.Value.Minute, 0) <= maxTime.Value);
} else {
filteredMaxDate = filteredMinDate;
}
var filteredWeekDay= day.HasValue ? filteredMaxDate.Where(i => !i.t_DayOfWeek.HasValue|| i.t_DayOfWeek == intDay) : filteredMaxDate;
return filteredWeekDay.ToList();

You are using EntityFunctions* in a linq statement on an IEnumerable<ExclusionRule>. But you can only use it on an IQueryable that has an Entity Framework query provider. So you should start with IQueryable<ExclusionRule> (from EF) or just create DateTimes in the regular.Net way.
*DbFunctions as of Entity Framework 6.

MinTime.Value.Hour etc. might have to be executed in a Query and it seems to me that you are excecuting them in .Net memory. That's why you get the error.
LINQ to entities:
http://msdn.microsoft.com/en-us/library/bb386964.aspx

This function can only be invoked from LINQ to Entities
EntityFunctions is applicable is LINQ to Entities queries only.
The EntityFunctions class contains methods that expose canonical functions to use in LINQ to Entities queries. http://msdn.microsoft.com/en-us/library/dd456873.aspx

I got around this by calling .ToList() before the DateTime filtering. This pulls everything into .NET so the querying can be done there.
As there are a relatively small number of items in my database this wasn't a problem.

Related

Obtaining entities from DbSet from a list of matching objects

I'm using Entity Framework Core 6 and I want to find a series of entities in a DbSet. The entities I want to obtain are the ones match some properties in a list of input objects.
I've tried something like this:
public IEnumerable<MyEntity> FindEntities(IEnumerable<MyEntityDtos> entries)
{
return dbContext.MyDbSet.Where(r => entries.Any(e => e.Prop1 == r.Prop1 && e.Prop2 == r.Prop2));
}
But I get the classic EF Core exception saying that my LINQ cannot be translated to a database query (the problem in particular is the entries.Any(...) instruction)
I know I can just loop over the list of entries and obtain the entities one by one from the DbSet, but that is very slow, I was wondering if there was a more efficient way to do this in EF Core that I don't know about.
I think this should work:
public IEnumerable<MyEntity> FindEntities(IEnumerable<MyEntityDtos> entries)
{
var props1=entries.Select(x=>x.Prop1).ToArray();
var props2=entries.Select(x=>x.Prop2).ToArray();
return dbContext.MyDbSet.Where(r => props1.Contains(r.Prop1) && props2.Contains(r.Prop2));
}
In the end, I've done this:
public static IEnumerable<MyEntity> GetRangeByKey(this DbSet<MyEntity> dbSet, IEnumerable<MyEntity> toFind)
{
var keys = new HashSet<string>(toFind.Select(e => e.Id));
IEnumerable<MyEntity> result = null;
for (int i = 0; i < keys.Length; i += 1000)
{
var keyChunk = keys[i..(Math.Min(i + 1000, keys.Length))];
var res = dbSet.Where(x => keyChunk.Any(k => x.ResourceArn == k));
if (result == null)
{
result = res;
}
else
{
result = result.Concat(res);
}
}
return result;
}
Basically I get the keys to find in a HashSet and use it to perform a Where query, which will be translated to a SQL IN clause which is quite fast. I do it in chunks because there's a maximum number of values you can put in a IN clause before the DB engine refuses it.

need to use Count() for null and non null values in Linq

Here is my code:
var value = query.Select(
a => new CHART_MODEL
{
TEXT = "xyz",
COUNT1 = (a.TOPLAM_FIYAT != null).ToString().Count(),
COUNT2 = (a.TOPLAM_FIYAT == null) ? ToString().Count() : 0
}
).ToList();
Here is the error:
System.NotSupportedException: 'LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.'
and CHART_MODEL
public class CHART_MODEL
{
public int COUNT1 { get; set; }
public int COUNT2 { get; set; }
public string TEXT { get; set; }
}
My question is: I have to calculate null and non null values with Count(). but it doesn't allow me to write. I don't know how to rearrange linq query code in correct way with Count().
This is an error you get because EF doesn't know how to execute the .ToString() method in sql.
Methods like that which EF doesn't have correlated methods in plain sql, will throw an error like the one you received.
What you can do is use an Immediate Execution.methods like: ToList or ToArray will force the execution of the query, hence loading the data into memory and when the data is loaded the rest of the operators are performed using Linq to objects on that the data that was brought in memory.
So, you can load the data using toList() and after that use the rest of the code and methods like toString() won't throw an error.
query.toList().Select(..)
You can read more about Deferred Execution vs Immediate Execution here:
https://samirbehara.com/2016/01/04/deferred-execution-vs-immediate-execution-in-linq/
The simplest way would be something like
var value = new CHART_MODEL
{
TEXT = "xyz",
COUNT1 = query.Where(a=>a.TOPLAM_FIYAT != null).Count(),
COUNT2 = query.Where(a=>a.TOPLAM_FIYAT == null).Count()
};
Note, this will issue two select statements.
If you really want to avoid the two select statements, you have to introduce a dummy grouping such as
var value = (from r in query
group r by 1 into results
select new CHART_MODEL
{
TEXT = "xyz",
COUNT1 = results.Where(a => a.TOPLAM_FIYAT != null).Count(),
COUNT2 = results.Where(a => a.TOPLAM_FIYAT == null).Count()
}).Single();
Just complementing on #sgmoore's answer, you can place you filter condition inside the .Count method itself:
var value = new CHART_MODEL
{
TEXT = "xyz",
COUNT1 = query.Count(a => a.TOPLAM_FIYAT != null),
COUNT2 = query.Count(a => a.TOPLAM_FIYAT == null)
};
There is the new is null and is not null syntax introduced in C#9. It reads really nice, but it is still not recommended by Microsoft to use in Linq-to-SQL.

Converting string to nullable DayOfWeek field using Linq cause an error

In my sql database WorkDay field is in string format and in model it is nullable DayOfWeek, i.e public DayOfWeek? WorkDay { get; set; }. While Converting database WorkDay field into model WorkDay field it will generate an error like:
Could not translate expression 'Table(StaffSchedule)' into SQL and
could not treat it as a local expression.
I have also tried to create three different linq statements which are as below.
1) Retrieve Data from StaffSchedule table.
2) Apply select operation on it.
3) Apply AddRange operation on selected data.
results.AddRange(dataContext.StaffSchedules
.Where(x => !x.Excluded)
.Where(x => x.DistrictID == districtId && x.District.Active && (x.Position == positionTeacher || x.Position == positionDirector || x.Position == positionAssistant || x.Position == positionAssistantDirector))
.Select(x => new Core.StaffSchedule()
{
ID = x.ID,
Staff = x.Staff.SelectSummary(),
Position = (StaffPosition)Enum.Parse(typeof(StaffPosition), x.Position, true),
Class = refs.Class,
District = x.District.SelectSummary(),
Time = null,
Reoccurring = false,
Inherited = true,
ReoccourringStart = x.ReoccourringStart,
ReoccourringEnd = x.ReoccourringEnd,
WorkDay = x.WorkDay == null ? (DayOfWeek?)null : (DayOfWeek)Enum.Parse(typeof(DayOfWeek), x.WorkDay, true)
}));
This is the conversion code for string to nullable DayOfWeek field. Which cause an error in my case.
WorkDay = x.WorkDay == null ? (DayOfWeek?)null : (DayOfWeek)Enum.Parse(typeof(DayOfWeek), x.WorkDay, true)
I have already gone through below link.
How to solve issue "Could not translate expression ...into SQL and could not treat it as a local expression."
Try to convert dataContext.StaffSchedules to IEnumerable by calling ToList()
method before making the query like this
results.AddRange(dataContext.StaffSchedules.ToList()
.Where(x => !x.Excluded)....the rest of you query
Search for difference between IEnumerable and IQueryable for more detailed explain
You can't translate any C# code to SQL so x.WorkDay == null ? (DayOfWeek?)null : (DayOfWeek)Enum.Parse(typeof(DayOfWeek), x.WorkDay, true) won't work in Linq to Entities.
Try to select your data after the query execution by writing AsEnumerable() before Select. Don't do it at the beginning of the query because you will fetch all the data from a db table.
results.AddRange(dataContext.StaffSchedules
//everything (well almost) from this point is going to be translated into SQL
.Where(x => !x.Excluded)
.AsEnumerable() //everything from here is going to be executed after the query ends so it can be any C# code
.Select(x => new Core.StaffSchedule()
{
//now this should work
WorkDay = x.WorkDay == null ? (DayOfWeek?)null : (DayOfWeek)Enum.Parse(typeof(DayOfWeek), x.WorkDay, true)
});

Handling TimeSpan in LINQ where TimeSpan is first referenced in query

I understand that LINQ cannot use TimeSpan, however I want to do a where condition on a DateTime with an added TimeSpan. My Issue is however that the TimeSpan is first referenced within the query. I have tried doing it in memory but that also causes issues.
entiteis is of type:
System.Data.Entity.IDbSet<CharterRequestDTO>
And biddingToCloseIn is defined as:
[NotMapped]
public TimeSpan BiddingToCloseIn
{
get { return TimeSpan.FromTicks(BiddingToCloseInTicks); }
set { BiddingToCloseInTicks = value.Ticks; }
}
Normal:
var charterRequestDtoIds =
(from e in entities
where e.ClientId == clientId
&& e.Status != TrackingState.Void
&& DateTime.Now < e.CreatedAt.AddDays(30).Add(e.BiddingToCloseIn)
select e.Id);
In Memory:
var charterRequestDtoIds =
from e in entities.Where( e => e.ClientId == clientId
&& e.Status != TrackingState.Void
&& DateTime.Now < e.CreatedAt.AddDays(30).Add(e.BiddingToCloseIn) )
select e.Id;
Error for both:
System.NotSupportedException: LINQ to Entities does not recognize the
method 'System.DateTime Add(System.TimeSpan)' method, and this method
cannot be translated into a store expression.
Instead of adding a TimeSpan, add milliseconds (or seconds, minutes, according to the precision you need.)
AddDays(30).AddMilliseconds(e.BiddingToCloseIn.TotalMilliseconds)

Linq to SQL replace .Any() with .Contains()

Got a dictionary with the persons Id as a key. And each value is a List with a class that contains a datetime.
I want to get all contracts from the database where each date in the list is in between the contracts from and until -date. There could be multiple for each person.
I can't use .Any() function. If I do I'll get this error: " Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator."
This is an example with the .Any method that doesn't work with linq to sql. And I need an other way to manage this.
public class SimpleObject
{
public bool Test { get; set; }
public DateTime DateTime { get; set; }
}
private void Test(Dictionary<int, List<SimpleObject>> dataBaseObjDictionary)
{
using (var db = TpContext.Create())
{
var selectedObj = db.Contracts.Where(x => dataBaseObjDictionary.ContainsKey(x.PersonRef) && dataBaseObjDictionary.Values.Any(y => y.Any(a => x.FromDate <= a.DateTime) && y.Any(a=>a.DateTime >= x.UntilDate)));
}
}
I think this should do it. It looks like you were relying on y to be the same with the two y.anys. In addition, you were checking if a was greater than until date and greater than from date so I fixed those.
var selectedObj = db.Contracts.Where(x => dataBaseObjDictionary.ContainsKey(x.PersonRef)
&& dataBaseObjDictionary.Values.Any(
y => y.Any(a => x.FromDate <= a.DateTime
&& a.DateTime <= x.UntilDate)
)
);

Categories