I have a query over a single entity (with some navigation properties) and need to project them into a model for consumption by API clients. It looks basically like this:
repository.CreateQuery<Reviews>()
.Where(/* criteria */)
.Select(m => new
{
ID = m.ID,
Reviewers = m.IsAnonymous
? m.Reviewers.Take(1).Select(r => new { Name = "Anonymous" })
: m.Reviewers.Select(r => new { Name = r.Name })
})
LINQ to Entities fails to execute this at runtime. In the Visual Studio debugger, the exception message is "Specified method is not supported". In LinqPad, the inner exception is "The nested query is not supported. Operation1='Case' Operations2='Collect'".
Any ideas how to work around this? I'd rather not force the query to execute to get the objects in memory because the point of this conditional query is to solve a performance issue, so as far as I can tell I really need to solve this within the scope of the L2E query.
Update: the projected types aren't anonymous in my real application. I just used anonymous ones here because it was convenient to contrive the example. In similar fashion, the real queries involve a number of additional properties which mean a great deal more data coming from the mapped tables.
You can use union to do what you want:
var query1 = repository.CreateQuery<Reviews>()
.Where(/* criteria */);
var queryAnonimous = query1.Where(m=>m.IsAnonymous)
.Select(m => new
{
ID = m.ID,
Reviewers = m.Reviewers.Take(1).Select(r => new { Name = "Anonymous" })
})
var queryNotAnonymous = query1.Where(m=>!m.IsAnonymous)
.Select(m => new
{
ID = m.ID,
Reviewers = m.Reviewers.Select(r => new { Name = r.Name })
})
var unionQuery = queryAnonimous.union(queryNotAnonymous);
Related
I have a problem trying to reuse some subqueries. I have the following situation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId);
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
This works fine, and generates the exact queries as wanted. The problem is that I want to have this subquery dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId to be taken from a method and not repeat it every time I need it.
I've tested it with the database, where I have values in the corresponding tables, and I've tested the query with the database without any data in the corresponding tables. It works fine with no problems or exceptions.
But when I try to extract the repeating subquery in a separate method and execute it against empty database tables (no data) the .First() statement throws error. Here is the code:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).First().ClimaticZoneId;
}
and the query generation:
var rooms = dbContext.Rooms.Select(r => new
{
RoomId = r.Id,
Zones = r.Zones.Select(zr => zr.Zone),
Name = r.Name,
Levels = r.Levels.Select(lr => lr.Level),
IdealSetpoint = (double?)r.Group.Setpoints.First(sp => sp.ClimaticZoneId == GetClimaticZoneId()).Setpoint??int.MinValue,
Devices = r.Devices.Select(rd => rd.Device)
}).ToList();
var tagsTypes = rooms.Select(r => r.Devices.Select(d => GetSetpointTagTypeId(d.DeviceTypeId))).ToList().SelectMany(x => x).Distinct().ToList();
predicate = predicate.And(pv => tagsTypes.Contains(pv.TagSettings.TagTypeId) &&
pv.ClimaticZoneId == GetClimaticZoneId());
var setpoints = valuesSubquery.Include(t=>t.TagSettings).Where(predicate).ToList();
After execution I get InvalidOperationException "Sequence do not contain any elements" exception in the GetClimaticZoneId method:
I'm sure that I'm not doing something right.
Please help!
Regards,
Julian
As #Gert Arnold suggested, I used the GetClimaticZoneId() method to make a separate call to the database, get the Id and use it in the other queries. I gust modified the query to not generate exception when there is no data in the corresponding table:
protected long GetClimaticZoneId()
{
return dbContext.ClimaticZonesLogs.OrderByDescending(cz => cz.Timestamp).FirstOrDefault()?.ClimaticZoneId??0;
}
I'm sending an IQueryable to a method that parses data for jQuery DataTables. It builds the filter from FORM data and creates a LINQ statement with filter, sorting, paging, etc that gets sent back to the page as JSON. I'd like to extend the parser and add Totals to the result set. When I add a GroupBy statement, the query is not evaluated on the server, but locally instead. It will only execute on the server if the original IQueryable has an anonymous projection...
This is a .Net Core 2.1 website. I know in the past GroupBy could not be executed locally but it does at 2.1. I've tried projection with a class and to an anonymous type, and it only works properly with an anonymous type. I really need to be able to do this with a class.
The following iQueryable gets sent to the parser:
var query = Context.InvoiceHeaders
.AsNoTracking()
.Where(x=>x.Slsno.Equals("13"))
.Select(x => new InvoiceHeaderSummary()
{
SalesNumber = x.Slsno,
OrderNumber = x.Ordnum,
ItemAmount = x.Itmamt,
SpecialChargeAmount = x.Sc1amt,
TaxAmount = x.Taxamt,
InvoiceTotal = x.Invamt
})
var parser = new Parser<InvoiceHeaderSummary>(Request.Form, query);
I'm trying to extend the parser by adding a List of totals to the output. But since I'm sending in an IQueryable with a class projection (InvoiceHeaderSummary), it doesn't execute on the server. I get the warning about it being evaluated locally:
var totalList = query
.GroupBy(i => 1)
.Select(g => new
{
TotalInvoice = g.Sum(i => i.InvoiceTotal)
})
.ToList();
I tried creating the entire LINQ method inline and it DOES run properly (notice I'm using an anonymous projection prior to the GroupBy rather than InvoiceHeaderSummary class):
var query = Context.InvoiceHeaders
.AsNoTracking()
.Where(x=>x.Slsno.Equals("13"))
.Select(x => new
{
SalesNumber = x.Slsno,
OrderNumber = x.Ordnum,
ItemAmount = x.Itmamt,
SpecialChargeAmount = x.Sc1amt,
TaxAmount = x.Taxamt,
InvoiceTotal = x.Invamt
})
.GroupBy(i => 1)
.Select(g => new
{
TotalInvoice = g.Sum(i => i.InvoiceTotal)
})
.ToList();
Is there a way to properly write this so it runs optimally??
Apparently one of the EF Core 2.x query translation defects / bugs.
The only workaround I've found is to use the GroupBy overload with element selector and select the data you want to aggregate into anonymous type:
var totalList = query
.GroupBy(i => 1, i => new { i.InvoiceTotal }) // <--
.Select(g => new
{
TotalInvoice = g.Sum(i => i.InvoiceTotal)
})
.ToList();
this code written by #Rahul Singh in this post Convert TSQL to Linq to Entities :
var result = _dbContext.ExtensionsCategories.ToList().GroupBy(x => x.Category)
.Select(x =>
{
var files = _dbContext.FileLists.Count(f => x.Select(z => z.Extension).Contains(f.Extension));
return new
{
Category = x.Key,
TotalFileCount = files
};
});
but this code have problem when used inside database context and we should use ToList() like this to fix "Only primitive types or enumeration types are supported in this context" error :
var files = _dbContext.FileLists.Count(f => x.Select(z => z.Extension).ToList().Contains(f.Extension));
the problem of this is ToList() fetch all records and reduce performance, now i wrote my own code :
var categoriesByExtensionFileCount =
_dbContext.ExtensionsCategories.Select(
ec =>
new
{
Category = ec.Category,
TotalSize = _dbContext.FileLists.Count(w => w.Extension == ec.Extension)
});
var categoriesTOtalFileCount =
categoriesByExtensionFileCount.Select(
se =>
new
{
se.Category,
TotalCount =
categoriesByExtensionFileCount.Where(w => w.Category == se.Category).Sum(su => su.TotalSize)
}).GroupBy(x => x.Category).Select(y => y.FirstOrDefault());
the performance of this code is better but it have much line of code, any idea about improve performance of first code or reduce line of second code :D
Regards, Mojtaba
You should have a navigation property from ExtensionCategories to FileLists. If you are using DB First, and have your foreign key constraints set up in the database, it should do this automatically for you.
If you supply your table designs (or model classes), it would help a lot too.
Lastly, you can rewrite using .ToList().Contains(...) with .Any() which should solve your immediate issue. Something like:
_dbContext.FileLists.Count(f => x.Any(z => z.Extension==f.Extension)));
im using Entity Framework designer first and I need to create custom Model Objects starting from the db objects.
I don't want to use IEnumerable cause it will query too many fields.
The goal is to remove the inner select within this function:
using (var db = new dbEntities())
{
var departments= db.departments
.Include(p => p.employee)
.Where(...)
.Select(p => new CustomDepartmentModel()
{
ID = p.ID,
Employees = p.employee
.Select(q => new CustomEmployeeModel()
{
ID = q.ID,
Name= q.Name
}).ToList()
});
return departments.ToList();
}
by using this function:
public static IQueryable<CustomEmployeeModel> ToModel(this IQueryable<employee> Employee)
{
return Employee.Select(u => new CustomEmployeeModel()
{
ID = u.ID,
Name = u.Name
});
}
But I always get the error: "LINQ to Entities does not recognize the method ToModel".
I did try to use it in these ways without luck:
Employees = p.employee.AsQueryable().ToModel().ToList() //1
Employees = db.Entry(p).Collection(f => f.employee).Query().ToModel().ToList() //2
I think that I need to use something like this:
public static System.Linq.Expressions.Expression<Func<IQueryable<employee>, IQueryable<CustomEmployeeModel>>> ToModel()
{
return p => p.Select(u => new CustomEmployeeModel()
{
ID = u.ID,
Name = u.Name
});
}
but I really can't figure out how to use it.
I think that I need to use something like this: [snip] but I really can't figure out how to use it.
That's exactly what you need, but you also need LINQKit to make the query work. With it, your code would look like this:
var toModel = ToModel();
var departments2 = db.departments
.AsExpandable()
.Include(p => p.employee)
.Where(p => true)
.Select(p => new CustomDepartmentModel()
{
ID = p.ID,
Employees = toModel.Invoke(p.employee).ToList()
});
The problem is that LINQ to Entities is trying to translate your ToModel() method into a SQL query (since that's what LINQ to Entities is supposed to do), and it can't find a way to do it, hence the error that you're seeing.
In order for you to call ToModel() you'll need to have the information already come in from the database, which will then make any LINQ query a LINQ to Objects query, which will be more than able to do what you are asking for. You can do this by calling ToList() before calling ToModel().
I'm trying to group Reports by Type and return the most recent Created date for each Report Type, using LINQ to Entities. I did my research and read lots of questions and most of them are solved using a similar query than mine:
var query = ctx.DataWorkspace.ApplicationData.Reports
.GroupBy(i => i.ReportType)
.Select(i => new { name = i.Key, y = i.Max(h => h.DateCreated) });
But I get this error:
A query cannot return non-Entity types that contain embedded entities
What am I doing wrong? Is it because it's LINQ-to-Entities?
Error message is quite descriptive. Your anonymous type contains a property which is typed as an entity (name = i.Key part). You can't do that, because LINQ to Entities would not be able to track changes to these entities.
You can take just some of the properties from ReportType instead of entire entity:
var query = ctx.DataWorkspace.ApplicationData.Reports
.GroupBy(i => i.ReportType)
.Select(i => new { name = i.Key.Name, y = i.Max(h => h.DateCreated) });
i.Key.Name is just an example. Your entity probably has different property/properties you care about.