How to do a search with EF CodeFirst - entity-framework

Currently, to do a search using EF CodeFirst and a repository pattern, based on user input to multiple text boxes on an mvc search view/page, I do something like the following:
public PagedList<Entity1> PlayerUserSearch(Entity1SearchParameters searchParameters, int? pageSize, int? startEntity, Func<Entity1, object> sortOrder, bool sortDesc)
{
IQueryable<Entity1> query = from entities in this.DataContext.Entity1s.Include("Entity2List")
where entities.Entity2List.Any()
select entities;
if (searchParameters.Entity2PrimaryKeyId.HasValue)
query = query.Where(e => e.Id == searchParameters.Entity2PrimaryKeyId.Value);
if (searchParameters.HasStats.HasValue)
{
if (searchParameters.HasStats.Value)
query = query.Where(u => u.Entity2List.Any(e => e.Stat != null));
else
query = query.Where(u => u.Entity2List.Any(e => e.Stat == null));
}
if (searchParameters.Entity2OtherField.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField == searchParameters.Entity2OtherField));
if (searchParameters.Entity2OtherField2.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2));
if (searchParameters.Active.HasValue)
query = query.Where(e => e.Active == searchParameters.Active.Value);
return this.GetPageByStartEntity(pageSize.Value, startEntity.Value, query, sortOrder, sortDesc);
}
The problem with this is that for every time I add on another where that checks the child of Entity1 (Entity2) for a certain field, it takes on a new " AND EXISTS" clause to the sql statement generated, so that it is doing an exists and checking table Entity2 all over again for every different field checked, rather than doing a single EXISTS on Entity in the query, and checking all fields I tacked on to the query (i.e. EntityOtherField1 and EntityOtherField2). I haven't been able to find a better way to do a search based on user inputs than constantly checking for the input being there (add to the search parameters)) and then tacking on a new where to the current query. Can anyone tell me if there is a better way to do this? Thanks!

I think what you're looking for is using Specification Pattern.
var spec = new specification<Entity2>(s => true);
if (searchParameters.HasStats.Value)
{
spec = spec.And(e => e.Stat != null);
}
if (searchParameters.Entity2OtherField2.HasValue)
{
spec = spec.And(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2);
}
query = query.Where(u => u.Entity2List.Any(spec));
Or I believe you can make it more standard by separating the filter logic and using spec.IsStatisfiedBy method.
A good framework for repository/specification implemetation on Entity Framework can be found here.

Related

Entity Framework translate method call

I'm using Entity Framework core 6 with SQL Server. I have the following query:
dbContext.Table1
.Where(t1 => some_condition(t1.some_property))
.Select(t1 => new
{
Field1 = t1.some_property,
Field2 = dbContext.Table2.Where(t2 => some_condition(t1.some_property, t2.some_property)).First(), // <-- this is my question
});
The thing is that this subquery dbContext.Table2.Where(t2 => some_condition(t1, t2)).First(), is used in many other places. I need to extract it somehow and call it instead of copy-pasting it everywhere.
I tried to extract it in a function like this:
public string Get(string some_property) => context.Table2
.Where(t => some_condition(some_property, t.some_property))
.First();
dbContext.Table1
.Where(t1 => some_condition(t1.some_property))
.Select(t1 => new
{
Field1 = t1.some_property,
Field2 = Get(t1.some_property)
});
But with that I get the subquery to execute separately and for every element in the collection (N+1 problem. I was expecting it though).
Is there a way to achieve that ? maybe using expression trees (which I'm not yet very familiar with).

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.

EF Core: Get entities matching a filter, but only those that have the latest datetime property in their grouping by a composite key

I have a table of entities of class Pricing.
Previously the Pricing table had a unique index based on 3 foreign keys: InstitutionId, SubmissionTypeId and FeeTypeId.
Now it needs to have multiple possible Pricing rows per that same index, but I should only use the one with the latest value of a DateTime column ActiveFrom, i.e. I should only use the latest active Pricing for the given composite index.
My repository method needs to return all currently active Pricings matching two foreign key values (InstitutionId and SubmissionTypeId).
I tried doing it like this:
DateTime today = DateTime.Today;
IQueryable<Pricing> filteredActivePricings = Context.Pricings
.Where(pricing =>
pricing.InstitutionId == institutionId
&& pricing.SubmissionTypeId == submissionTypeId
&& pricing.ActiveFrom <= today
)
.GroupBy(pricing => new { pricing.InstitutionId, pricing.SubmissionTypeId, pricing.FeeTypeId })
.Select(pricingGroup => pricingGroup
.OrderByDescending(pricing => pricing.ActiveFrom)
.First()
);
IList<Pricing> result = await filteredActivePricings.ToListAsync();
But EF Core says it can't convert that syntax into a valid SQL query.
I've found a solution using pure SQL code, but I'd like to do it in the EF Core's LINQ method syntax, since the rest of my codebase is written in it.
So, I figured out my problem and a solution.
The problem was, as #ivan-stoev pointed out, I wrongly thought that IGrouping<K, T> (the result of IQueryable<T>.GroupBy) contained a collection of T entities grouped by the key K, and that I had to somehow process that collection to get my single entity T.
So here's my solution which utilizes the IQueryable<T>.GroupBy method properly, to get a collection of key values and dates which are then used in a Join back to the table, to get the full data of the entities I need.
DateTime today = DateTime.Today;
IQueryable<ActivePricingKey> filteredActivePricingKeyQuery = Context.Pricings
.Where(pricing =>
pricing.InstitutionId == institutionId
&& pricing.SubmissionTypeId == submissionTypeId
&& pricing.ActiveFrom <= today
)
.GroupBy<Pricing, PricingKey, ActivePricingKey>(
(Pricing pricing)
=> new PricingKey
{
InstitutionId = pricing.InstitutionId,
SubmissionTypeId = pricing.SubmissionTypeId,
FeeTypeId = pricing.FeeTypeId
},
(PricingKey key, IEnumerable<Pricing> pricings)
=> new ActivePricingKey
{
InstitutionId = key.InstitutionId,
SubmissionTypeId = key.SubmissionTypeId,
FeeTypeId = key.FeeTypeId,
ActiveFrom = pricings.Max(pricing => pricing.ActiveFrom)
}
);
IQueryable<Pricing> filteredActivePricingQuery = filteredActivePricingKeyQuery
.Join(
Context.Pricings,
(ActivePricingKey activePricingKey)
=> new
{
activePricingKey.InstitutionId,
activePricingKey.SubmissionTypeId,
activePricingKey.FeeTypeId,
activePricingKey.ActiveFrom
},
(Pricing pricing)
=> new
{
pricing.InstitutionId,
pricing.SubmissionTypeId,
pricing.FeeTypeId,
pricing.ActiveFrom
},
(ActivePricingKey activePricingKey, Pricing pricing) => pricing
);
ICollection<Pricing> pricings = await filteredActivePricingQuery.ToListAsync();

Linq to Entities Select clause with lambda

I am working on a new project and we are using Entity Framework and the dev lead would like to use lambda queries whenever possible. One thing we are having a hard time figuring out is how to select two columns specifically. Also how to select distinct. We have a table that has multiple entries for a vendor but we want to just get a list of vendors and load to a dictionary object. It fails because as written it is trying to add a key value that has already been added. Take the following query.
Dictionary<int, string> dict = new Dictionary<int, string>();
dict = GetWamVendorInfo().AsEnumerable()
.Where(x => x.vendor_name != null && x.vendor_id != null)
//.Select(x => x.vendor_id).Distinct()
.Take(2)
.ToDictionary(o => int.Parse(o.vendor_id.ToString()), o => o.vendor_name);
What I would like to do is select just vendor_id and vendor_name so we can get just the distinct records.
Any help would be greatly appreciated.
Thanks,
Rhonda
Use an anonymous type:
// earlier bit of query
.Select(x => new { VendorId = x.vendor_id, VendorName = x.vendor_name } )
.Distinct()
.ToDictionary(o => o.VendorId, o => o.VendorName);
I've removed the call to Take(2) as it wasn't clear why you'd want it - and also removed the parsing of VendorId, which I would have expected to already be an integer type.
Note that you should almost certainly remove the AsEnumerable call from your query - currently you'll be fetching all the vendors and filtering with LINQ to Objects. There's also no point creating an empty dictionary and then ignoring it entirely. I suspect your complete query should be:
var vendors = GetWamVendorInfo()
.Select(x => new { VendorId = x.vendor_id,
VendorName = x.vendor_name } )
.Distinct()
.ToDictionary(o => o.VendorId,
o => o.VendorName);
As an aside, you should ask your dev lead why he wants to use lambda expressions (presumably as opposed to query expressions) everywhere. Different situations end up with more readable code using different syntax options - it's worth being flexible on this front.
Just use an anonymous object:
var vendors = GetWamVendorInfo().AsEnumerable()
.Where(x => x.vendor_name != null && x.vendor_id != null)
.Select(new {x.vendor_id, x.vendor_name})
.Take(2)
That's it. You can now work with vendors[0].vendor_id, vendors[0].vendor_name, and so on.

Entity Framework Include with condition

I need to filter a dealer based on id and the uncomplete checkins
Initially, it returned the dealer based only on id:
// TODO: limit checkins to those that are not complete
return this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(d => d.DealerId == id)
.FirstOrDefault();
As you can see the requirement is to limit the checkins.
Here's what I did:
var query = from d in this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(d => d.DealerId == id)
select new
{
Dealer = d,
Groups = from g in d.Groups
select new
{
Items = from i in g.Items
select new
{
Group = i.Group,
Observations = i.Observations,
Recommendations = i.Recommendations
}
},
Checkins = from c in d.Checkins
where c.Complete == true
select new
{
Inspections = from i in c.Inspections
select new
{
InspectionItems = i.InspectionItems
}
}
};
var dealer = query.ToArray().Select(o => o.Dealer).First();
return dealer;
It works.
However, I am not convinced I am doing the right thing.
What is the best way to accomplish what I did? A stored procedure maybe?
I am not sure I even have to use Include clause anymore
Thank you.
If you want to load filtered relations with single query you indeed have to execute such projection but you don't need those calls to Include. Once you are building projections includes are not use - you have returned data under your control.
Stored procedure will help you only if you fall back to plain ADO.NET because stored procedures executed through Entity framework are not able to fill related entities (only flattened structures).
Automatic fixupu mentioned by #Andreas requires multiple database queries and as I know it works only if lazy loading is disabled because proxied object somehow doesn't have information about fixup and it still has its internal flags for each relation as not loaded so when you access them for the first time they still execute additional query.
Maybe you can make use of the relation fixup mechanism in the EF ObjectContexts. When you do multiple queries in the same context for entities, that are related by associations, these are resolved.
Assuming your association between Dealers and Checkins is 1:n with navigation properties on each side, you could do like:
var dealer = yourContext.Dealers
.Where(p => p.DealerId == id)
.FirstOrDefault();
if(dealer != null)
{
yourContext.Checkins
.Where(c => c.Complete && c.DealerId == dealer.DealerId)
.ToList();
I have not tested this by now, but since EF recognises that the Checkins, it inserts into the context by the second query belong to the dealer from the first query, corresponding references are created.
#Andreas H:
Awesome, thank you a lot.
I had to adjust your suggestion like this and it worked:
var dealer = this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Where(p => p.DealerId == id).
FirstOrDefault();
if (dealer != null)
{
this.ObjectContext.Checkins
.Include("Inspections")
.Include("Inspections.InspectionItems")
.Where(c => !c.Complete && c.Dealer.DealerId == dealer.DealerId)
.ToList();
}
return dealer;
I still have to use the Include otherwise it won't return the referenced entities.
Note also that Dealer.Groups are unrelated to the Dealer.Checkins.
So if there's no checkins satisfying the condition, Groups still need to be returned.
It's interesting to note that at first, I put the two include for checkins to the dealer
var dealer = this.ObjectContext.Dealers
.Include("Groups")
.Include("Groups.Items")
.Include("Groups.Items.Observations")
.Include("Groups.Items.Recommendations")
.Include("Checkins.Inspections")
.Include("Checkins.Inspections.InspectionItems")
.Where(p => p.DealerId == id).
FirstOrDefault();
if (dealer != null)
{
this.ObjectContext.Checkins
.Where(c => c.Complete && c.DealerId == id)
.ToList();
}
return dealer;
but it returned all the Checkins including those which are not complete.
I don't understand exactly why the latter doesn't work but the former does, how are the entities are resolved. I somehow can intuit that the former returns all data.
Your accepted solution will generate multiple database queries. As Ladislav Mrnka said a projection is the only way to pull your result with one query. The maintance of your code indeed hard. Maybe you could use an IQueryable-Extension that builds the projection dynamically and keep your code clean:
var query = this.ObjectContext.Dealers.SelectIncluding( new List<Expression<Func<T,object>>>>(){
x => x.Groups,
x => x.Groups.Select(y => y.Items),
x => x.Groups.Select(y => y.Items.Select(z => z.Observations)),
x => x.Groups.Select(y => y.Items.Select(z => z.Recommendations)),
x => x.Checkins.Where(y => y.Complete==true),
x => x.Checkins.Select(y => y.Inspections),
x => x.Checkins.Select(y => y.Inspections.Select(z => z.InspectionItems))
});
var dealer = query.First();
return dealer;
You can find the extension at thiscode/DynamicSelectExtensions on github