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
Related
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 + '%')));
}
I have following SQL Query , due to some limitation because there are many more conditions in this query I have to convert this query to LINQ.
SELECT
sh.BarCode
FROM
Bars AS sh
INNER JOIN BarDetail AS detail ON
detail.BarCode = sh.BarCode
AND
detail.IsActive = 1
INNER JOIN BarStatus AS st ON
st.BarCode = sh.BarCode
AND
st.IsActive = 1
So I have done this so far at LINQ
var queryAble = _context.BarDetail
.Include(x => x.Bar)
.Include(x => x.Bar)
.ThenInclude(y => y.BarStatus)
.Where(x => x.IsActive == true)
.AsQueryable();
I want to apply condition on barstatus as well; condition is barstatus with IsActive == true. I am unable to do it .
I don't want to do it as raw SQL with DbCommand, I'd like to do it entirely using only Linq-to-Entities.
How would I do this maybe this way but its not working
var queryAble = _context.BarDetail
.Include(x => x.Bar)
.Include(x => x.Bar)
.ThenInclude(y => y.BarStatus)
.Where(x => x.IsActive == true && x.Bar.BarStatus[SOMETHING HERE])
.AsQueryable();
This is direct translation of your SQL to LINQ. Note that Include intorduced not for building query but for loading related data.
var query =
from bar in _context.Bar
from detail in bar.Details
where detail.IsActive && bar.IsActive && bar.BarStatus.IsActive
select bar.BarCode;
As I remarked in my comment, while your original SQL query works, it's best for JOIN clauses to use only key (or tuple) equality, while other predicates should be in the WHERE clause. Following that pattern shouldn't cause any changes to your runtime query execution plan, but I feel it's keeping with the relational-calculus that SQL is based on - and it also means you can instantly check if a JOIN is correct or not because you'll always be using only primary-key and foreign-key columns (which are presumably already indexed... right?).
So your query becomes:
SELECT
b.BarCode
FROM
Bars AS b
INNER JOIN BarDetail AS d ON d.BarCode = b.BarCode
INNER JOIN BarStatus AS s ON s.BarCode = b.BarCode
WHERE
d.IsActive = 1
AND
s.IsActive = 1
...which is easier to translate into Linq-to-Entities:
Also:
You don't need the .AsQueryable() call: all non-materialized queries created from DbContext's DbSet<T> will be IQueryable<T> already.
As you have navigation-properties you don't need to do a manual Join.
IQueryable<String> q = _context.BarCode
// .Include( b => b.BarDetail )
// .Include( b => b.BarStatus )
.Where( b =>
b.BarDetail.IsActive == true
&&
b.BarStatus.IsActive == true
)
.Select( b => b.BarCode );
List<String> list = await q.ToListAsync( cancellationToken ).ConfigureAwait(false);
Update: Without b.BarDetail.IsActive == true
I can not directly add Include( b => b.BarStatus ) because BarStatus doesn't have direct relationship with BarCode, its linked with BarDetail. So first we go into BarDetail and then after that we go in BarStatus using BarDetail
You can still do a manual JOIN:
IQueryable<String> q = _context.BarCode
// .Include( b => b.BarDetail )
// .Include( b => b.BarStatus )
.Join( _context.BarStatus, s => s.BarCode, b => b.BarCode, ( s, b )
=> new { BarStatus = s, BarCode = b, BarDetail = b.BarDetail } )
.Where( t =>
t.BarDetail.IsActive == true
&&
t.BarStatus.IsActive == true
)
.Select( b => b.BarCode );
List<String> list = await q.ToListAsync( cancellationToken ).ConfigureAwait(false);
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.
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);
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.