EF.Functions.Like() for an array of string - entity-framework-core

I want to filter the IQueryable<T> with the help of EF.Functions.Like() method, which accepts a string parameter, to make use of array of strings. Also, I want this filter to be applied on an IQueryable and not on a List.
var configurations = _dbContext.Configurations
.Include(x => x.ChildTable)
.Where(x => x.Id == Id);
if (!string.IsNullOrEmpty(request.Filter))
{
configurations = configurations.Where(x => EF.Functions.Like(string.Join(", ", x.ChildTable.Select(x => x.Name).ToArray()), '%' + request.Filter + '%'));
}
In the above code, Configuration has a one-many relationship with the ChildTable. Meaning, each configuration will have a more than one ChildTable entries related. So, with that given, x.ChildTable.Select(x => x.Name) is of type IEnumerable<string>. And, I want to filter the Configuration records whose ChildTable.Name entries is like the given request.Filter.

Try the following query:
var configurations = _dbContext.Configurations
.Include(x => x.ChildTable)
.Where(x => x.Id == Id);
if (!string.IsNullOrEmpty(request.Filter))
{
configurations = configurations
.Where(x => x.ChildTable.Any(c => EF.Functions.Like(c.Name, '%' + request.Filter + '%')));
}

Related

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.

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.

Linq to Entities using Lambda Expressions and multiple where conditions

I am trying to select a list of objects at the end of a fairly long chain of joins/selects using Linq to Entities written as Lambda Expressions... Here is what I have currently the following two statements.
var formDefId = _unitOfWork.AsQueryableFor<FormTrack>()
.Where(x => x.FormTrackId == formTrackId)
.Select(x => x.FormDefId).First();
var rules = _unitOfWork.AsQueryableFor<FormTrack>()
.Where(x => x.FormTrackId == formTrackId)
.Select(x => x.FormDef)
.SelectMany(x => x.Events
.Where(y => y.EventTypeId == 7))
.Select(x => x.RuleGroup)
.SelectMany(x => x.Rules)
.SelectMany(x => x.RuleFormXmls
.Where(y => y.FormDefId == formDefId));
What I would like to do, is combine the two queries, and use the FormDefId returned by
.Select(x => x.FormDef)
in the final where clause instead of having to use the formDefId from a separate query.
Is this something that is possible?
Thank you in advance for your help
It is much easier to write this using query syntax.. Each from in query syntax corresponds to a SelectMany in lambda syntax. This allows you to have all the variables in scope.
var rules =
from ft in _unitOfWork.AsQueryableFor<FormTrack>()
from e in ft.FormDef.Events
from r in e.RuleGroup.Rules
from x in r.RuleFormXmls
where ft.FormTrackId == formTrackId
where e.EventTypeId == 7
where x.FormDefId == ft.FormDefId
select x

How to include related field in query

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();