Select a specific column with multiple includes in Entity Framework - c#

I'm trying to get specific columns from a context with several includes, but when I try:
Context.Include(i => i.c)
.Include(i => i.l).Select(s=> new LocationCatalog { Name = s.Name})
.Include(i => i.p)
.Include(i => i.li)
.Include(i => i.pcl)
VS throws an error after the select.
How can I achieve this? I want to specify the columns for each include.
The error says that for example: i.p doesn't contains definition for i.l

That's not possible with Entity Framework. You either include the entire table (with Include, as you are doing) or you don't include it at all.
If you want to load only specific columns, you can do this, but see how it's a manual process:
Context
.Select(i => new YourType
{
c = i.l,
l = i.l,
x = new X
{
a = i.x.a // only the properties you want here
}
...
});
The moment you use Select, Include is completely ignored, so you cannot use both.

Related

Issue with many-to-many query with linq to entities

I've got a table
Application
ApplicationID,
NAme
ApplicationSteps
AplicationStepID,
AplicationID,
StepID
ApplicationStepCriterias
ApplicationStepID,
CriteriaID
So I've got one SelectedCriteriaID - a user choose from a dropdown one criteria and he wants all the applications which has this SelectedCriteriaID in the table ApplicationStepCriterias
I tried
var ds = context.Applications
.Where(a => a.ApplicationSteps
.Select(x=>x.ApplicationStepCriterias
.Select(t=>t.CriteriaId))
.Contains(SelectesdCriteria));
But as I have as result IEnumerable<IEnumerable<int>> I cannot use Contains
Just I get a list of all the CriteriaIds for each ApplicationStep(also a sequence). Just I cannot think of way to get in one list all the CriteriIds.
First, let me try to get the names right. This is not a pure many-to-many association, because the junction class is part of the class model. It is what I unofficially call a 1-n-1 association. So you have
Application -< ApplicationSteps >- ApplicationStepCriterias
I'd strongly recommend to use singular names for your classes ...
Application -< ApplicationStep >- ApplicationStepCriterion
... so you can use plural for collection property names without getting confused.
If I'm right so far, you query should be
context.Applications
.Where(a => a.ApplicationSteps
.Any(x => selectedCriteria
.Contains(x.ApplicationStepCriterion.CriteriaId));
(and I'd also prefer CriterionId, probably referring to a Criterion class)
You may try something like this:
var applicationStepIds = context.ApplicationStepCriterias
.Where(i => i.CriteriaID == selectedCriteria)
.Select(i => i.ApplicationStepID)
.Distinct();
var applicationIds = context.ApplicationSteps
.Where(i => applicationStepIds.Contains(i.AplicationStepID))
.Select(i => i.AplicationID)
.Distinct();
var result = context.Applications.Where(i => applicationIds.Contains(i.ApplicationId));

Entity Framework, Getting part of an entity

I have an entity with one field I do not want to return sometimes.
I am setting it to null right now. Is there a way to specify this in the query itself instead of clearing it out like I am here?
public async Task<IQueryable<XYZXY>> GetStuff()
{
histories =
_db.Stuffs
.Where(n => n.NationId == User.NationId)
.OrderBy(x => x.DateSent);
await histories.ForEachAsync(d => d.Attachment = null);
return histories;
}
What you are looking for is called projection, this is what stuff do you want to project into your result set from the server.
In EF projection is done by a combination of the select line of your query and any Includes which you have done.
If attachment is a second table accessed by a navigation property it wont be returned by your current query (IE it will be null) unless you are doing lazy loading (normally signified by the virtual keyword on the nav property eg public virtual Attachment Attachment {get;set;}). If attachment is a column projection is more complicated
You have 2 options, use an annonomous type eg:
_db.Stuffs
.Where(n => n.NationId == User.NationId)
.OrderBy(x => x.DateSent)
.Select(x=> new { A = x.A, B = x.B .... /*Dont list attachment*/});
or reuse the existing object
_db.Stuffs
.Where(n => n.NationId == User.NationId)
.OrderBy(x => x.DateSent)
.Select(x=> new Stuff { A = x.A, B = x.B .... /*Dont list attachment*/});
Do note custom projections will not be tracked so changing a property and calling save wont work.

LINQ to Entities: Get more recent date from each group

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.

Why aren't my included entities actually included?

I'm using Entity Framework 4 in Visual Studio 2010, with C#.
I have a method used in a repository that returns a object set with various navigation properties included. Up until recently, this method looked like this...
private IEnumerable<VRTSystem> GetSystems() {
return ctx
.Include(s => s.Customer.CustomerType)
.Include(s => s.VRTSystemProductConfigurations);
}
...where ctx is an ObjectSet of generic type VRTSystem. The full method had a lot more .Include()s than this, but this is enough to show the point.
This worked fine, however I needed to add some code to ensure that only VRTSystemProductConfigurations that had the Active flag set to true were returned. Following the advice commonly given for such situations, I changed the code to look like this...
private IEnumerable<VRTSystem> GetSystems() {
return ctx
.Include(s => s.Customer.CustomerType)
.Include(s => s.VRTSystemProductConfigurations)
.Select(s => new {
System = s,
VRTSystemProductConfigurations = s.VRTSystemProductConfigurations.Where(pc => pc.Active)
})
.Select(s => s.System);
}
However, this new version does not include any of the navigation properties, they are all null.
Anyone any idea why?
This is because Entity Framework is not entirely stupid. It sees that in the end only Systems are queried, so it cuts everything in between and returns Systems only. And part of the trick you're executing here is to disable lazy loading, so the navigation properties are null and will remain null.
You have to remove the last Select out of the scope of the EF query provider by adding an AsEnumerable:
return ctx
.Include(s => s.Customer.CustomerType)
.Select(s => new {
System = s,
VRTSystemProductConfigurations = s.VRTSystemProductConfigurations.Where(pc => pc.Active)
})
.AsEnumerable()
.Select(s => s.System);
And you don't want to include VRTSystemProductConfigurations, because that's the collection you want to load partly.

EntityFramework - Load IDs to levels down without loading other columns

I have 3 entities: Banners, Packs and Files.
One Banner has many packs and one pack has many files.
I need to get all the files ids for all banners. I tried the following:
IList<BannerModel> banners = context.Banners
.OrderBy(x => Guid.NewGuid())
.Take(count)
.Select(x =>
new BannerModel {
Images = x.Packs.SelectMany(p => p.Files.Select(f => f.Id)).ToList()
}).ToList();
However, I am getting an error on the Files id selecting:
System.NotSupportedException: LINQ to Entities does not recognize the
method 'System.Collections.Generic.List1[System.Int32]
ToList[Int32](System.Collections.Generic.IEnumerable1[System.Int32])'
method, and this method cannot be translated into a store expression.
at
System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.DefaultTranslator.Translate(ExpressionConverter
parent, MethodCallExpression call) at ...
Any idea of what might be wrong?
NOTE: I have Lazy Loading disabled.
var allFileIDs = context.Banners.SelectMany(b =>
b.Packs.SelectMany(p => p.Files.Select(f => f.ID)))
.ToList();
Resulting SQL will look like:
SELECT [t2].[ID]
FROM [Banners] AS [t0], [Packs] AS [t1], [Files] AS [t2]
WHERE ([t1].[BannerID] = [t0].[ID]) AND ([t2].[PackID] = [t1].[ID])
No lazy-loading, single query.
Misunderstood your question about getting all file ids. If you need to project each banner to BannerModel:
context.Banners.Select(b => new BannerModel {
Images = b.Packs.SelectMany(p => p.Files.Select(f => f.ID))
}).ToList();
You see exception, because you have ToList() in expression, which cannot be converted into SQL. Change BannerModel.Images to be of type IEnumerable<int> instead of List<int>, and remove ToList() call from select statement.
One more option if you don't want to change type of BannerModel.Images:
context.Banners.Select(b => b.Packs.SelectMany(p => p.Files.Select(f => f.ID)))
.ToList() // brings next projection into memory
.Select(ids => new BannerModel { Images = ids.ToList() })
.ToList();

Categories

Resources