How to include related field in query - entity-framework

I have an Entity Called Attachment.
Id/Name/ParentId
I have a second entity called AttachmentVersion.
Id/AttachmentId/Size/Date/other unwanted fields
It is related by an Id.
This is the query I want to write.
_context.Attachments.Where(a=> a.ParentId == 5)
//Include AttachementVersion.Size only in result.

If you want to get only most recent AttachmentVersion, then you should query AttachmentVersions:
using System.Data.Entity;
var result = db.AttachmentVersions
.Where(m => m.AttachmentId == attachmentId)
.Include(m => m.Attachment)
.OrderByDescending(m => m.Date)
.FirstOrDefault();
Update
If you want to get list of attachments according to some filter, you can achieve what you want by querying AttachmentVersions and grouping them.
var result = db.AttachmentVersions
.Where(m => m.Attachment.ParentId == 5)
.Include(m => m.Attachment)
.GroupBy(m => m.AttachmentId)
.Select(g => new
{
// There will be one Attachment because we
// grouped by AttachmentId
Attachments = g.Select(m => m.Attachment).Take(1),
// Get only size of latest version
Sizes = g.OrderByDescending(m => m.Date)
.Select(m => m.Size)
.Take(1)
})
// Load into memory to be able to use Single()
.AsEnumerable()
.Select(m => new
{
Attachment = m.Attachemts.Single(),
LastSize = m.Sizes.Single()
})
.ToList();

Related

Entity Framework ThenInclude filtered from original dataset

I have a query where my ThenInclude Where needs to access data in the original data set e.g.
var bikes = context.Bikes
.Include(bike => bikes.Models)
.ThenInclude(model => model.Spec.Where(spec=> spec.SpecYear == bike.YearCreated ))
.ToList();
I currently have this in two separate queries but it would save a lot of time if I completed this request on the database.
Try to use projection instead of Include:
var bikes = context.Bikes
.Select(bike = new Bike
{
Id = bike.Id,
// ... other fields
Models = bike.Models.Select(m => new Model
{
Id = m.Id
// ... other fields
Spec = m.Spec
.Where(spec => spec.SpecYear == bike.YearCreated)
.ToList()
})
})
.ToList();

How to get results from another query list if first linq query returns empty?

In this EF query, for the contacts list, I'm trying to query to get records for ContactTypeA and populate results in contacts list. If there are no records for ContactTypeA, then I want it to query for records for ContactTypeB and populate results in contacts list. I tried to use DefaultIfEmpty but that method only accepts single value and not a list. 'contacts' is a List object. Any ideas or even an alternative to DefaultIfEmpty? Thanks.
select(i => new transaction{
....
contacts = contactRepository.All.Where(c => c.AccountId == i.Account.Id && contactTypeRepository.All.Any(ct => ct.ContactId == c.Id && ct.Type == ContactType.ContactTypeA)).ToList().DefaultIfEmpty((contactRepository.All.Where(c => c.AccountId == i.Account.Id && contactTypeRepository.All.Any(ct => ct.ContactId == c.Id && ct.Type == ContactType.ContactTypeB)).ToList()
}
)
Firstly, make absolutely sure your contactRepository.All() method returns IQueryable<Contact> and not IEnumerable<Contact>, IList<Contact> or the like, otherwise you are automatically loading all contacts into memory.
From there, don't be afraid to simplify your query across multiple statements to make it a lot easier to understand. You should also leverage navigation properties rather than relying on completely disconnected entities and generic repositories and manually joining these all up in huge expressions.
Ideally an Account could have a collection of Contacts, but if not, at a minimum a Contact should have ContactType references:
var accountContactsQuery = contactRepository.All
.Where(c => c.AccountId == i.AccountId); //Remains IQueryable
var contacts = accountContactsQuery
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeA)
.ToList(); // Gets List of Contacts where contains at least 1 ContactTypeA type.
// If we have none, replace with results for ContactTypeB
if (!contacts.Any())
contacts = accountContactsQuery
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeB)
.ToList();
This looked a bit odd in that your ContactType appears to have a ContactId, (As opposed to Contact containing a ContactTypeId?) But the above reflects the relationship in your example.
With Account containing Contacts collection:
var accountContactsQuery = accountRepoitory.All
.Where(a => true /* replace with relevant criteria */);
var contacts = accountContactsQuery
.SelectMany(a => a.Contacts)
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeA)
.ToList(); // Gets List of Contacts where contains at least 1 ContactTypeA type.
// If we have none, replace with results for ContactTypeB
if (!contacts.Any())
contacts = accountContactsQuery
.SelectMany(a => a.Contacts)
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeB)
.ToList();
When dealing with conditions in expressions, I can suggest returning all relevant details, then building your final "payload" based on the conditions.
For example, if querying accounts to build transactions but wanting to load ContactAs if available and Bs for each account if not available:
var transactionData = accountRepoitory.All
.Where(a => true /* replace with relevant criteria */);
.Select(a => new
{
a.AccountId,
/* populate account and common details.. */
ContactAs = a.Contacts
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeA).ToList(), // Consider a Select to get relevant details...
ContactBs = a.Contacts
.Where(c => c.ContactTypes.Any(ct => c.Type == ContactType.ContactTypeB).ToList()
}).ToList(); // Executes query against DB to load relevant data...
var transactions = transactionData
.Select( t => new Transaction
{
AccountId = t.AccountId,
/* Other fields */
Contacts = t.ContactAs.Any() ? t.ContactAs : t.ContactBs
}).ToList();
Essentially use the EF Linq expressions to load the possible data, so include results for both ContactA and ContactB, then afterwards build your final projection using that data and conditionally use the ContactA or B as suited. Generally I don't advise passing Entities back (Actual Contact entities) but project into minimally viable view models in the first EF query using Select.

MongoDB .NET Group by with list result of whole objects

I was digging into the documentation of MongoDB http://mongodb.github.io/mongo-csharp-driver/2.7/reference/driver/crud/linq/ and I saw, using .NET drive is possible to make group by on the database.
For 1 ProductId I can have few elements in a database. I want to get whole Last element.
So I am trying to do something like:
var query = _collection.AsQueryable()
.GroupBy(p => p.ProductId, (k, s) =>
new { Name = k, Result = s.First() })
.Select(x => new { Name = x.Name, Result = x.Result }).First();
The problem is that I see an error message like:
System.NotSupportedException: Specified method is not supported.
at
MongoDB.Driver.Linq.Processors.AccumulatorBinder.GetAccumulatorArgument(Expression node)
I know that for now in my example i didnt order by the result.. But this will be my second step. For now i see that i cannot group by. Is it possible to do this kind of group by?
My solution for that is:
var query = _collection.Aggregate(new AggregateOptions { AllowDiskUse = true })
.Match(x => ElementIds.Contains(x.ElementId))
.SortByDescending(x => x.StartDate).ThenByDescending(x => x.CreatedAt)
.Group(x => x.ElementId, x => new
{
StartDate = x.First().StartDate,
Grades = x.First().Grades,
SellingId = x.First().SellingId,
CreatedAt = x.First().CreatedAt,
ModifiedAt = x.First().ModifiedAt,
Id = x.First().Id,
ElementId = x.First().ElementId
})
.ToEnumerable(token);
After that, I parsed it into my model.
AllowDiskUse = true, because in my case MongoDB's memory is not enough to handle this operation.

Evaluate EF query with MAX on the server

Using Entity Framework Core 2.2 I have the following query:
var user = await context.Users.AsNoTracking()
.Include(x => x.Lessons).ThenInclude(x => x.LessonLevel)
.FirstOrDefaultAsync(x => x.Id == userId);
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId < user.Lessons.Max(y => y.LessonLevelId));
Thus query evaluates locally and I get the message:
The LINQ expression 'Max()' could not be translated and will be evaluated locally.'
How can I make this query evaluate on the server?
Update
Based on DavigG answer I made it work using:
var maxLessonLevelId = user.Lessons.Max(y => y.LessonLevelId););
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId < maxLessonLevelId);
I know the following evaluates locally but shouldn't evaluate on the server?
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId <
context.Users.AsNoTracking()
.Where(y => y.Id == userId)
.Select(y => y.Lessons.Max(z => z.LessonLevelId))
.FirstOrDefault());
Is it possible to use a child queries that evaluates on the server?
Get the max value as a separate query, for example:
var maxLessonLevelId = user.Lessons.Max(y => y.LessonLevelId);
Then you can can get the lessons like this:
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonLevelId < maxLessonLevelId);

Linq query using result of another query

I have a query that gets data in the form of an IQueryable
var assys = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == siteAssetId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly)
.Select(x => x.RelatedAsset.CustomAssetAttributes2);
For every 'assy' that is returned, I'd like to get it's AssetId and use this to get a list of 'subassys', see below. For each 'assy' record, the assyId variable should be substituted for its AssetId.
var subassys = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == assyId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.SubAssy)
.Select(x => x.RelatedAsset.CustomAssetAttributes2);
I assume I'll need to use ForEach, does anyone know if what I'm trying to do is possible?
Thanks
Applying ForEach would be extremely bad approach to retrieve your desired result. You could apply join and group for that queries instead.
var assysQuery = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == siteAssetId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly);
Then apply join and group;
var subAssysQuery =
from assy in assysQuery
join subAssy in assetrelationshipRepository.GetAll() on assy.Id equals subAssy.AssetId
where
subAssy.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent &&
subAssy.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly
group assy by assy.Id into g
select new
{
AssyId = g.Key,
SubAssets = g.Select(x => x.RelatedAsset.CustomAssetAttributes2),
};
This should give you CustomAssetAttributes2 value of subassys for per assy record.
Note : Also, I suggest you to use known type for select clause instead
of anonymous type.