Entity Framework querying by related entity - c#

I have 2 entities, Jobs and Appointments:
public class Job
{
public int JobId { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
public class Appointment
{
[Key]
public int AppointmentId { get; set; }
[Required]
public DateTime AppointmentDate { get; set; }
[Required]
[MaxLength(50)]
public string Type { get; set; }
public int Job_JobId { get; set; }
[ForeignKey("Job_JobId")]
public virtual Job Job { get; set; }
}
How do I return only jobs that have an "Initial" appointment type?
Something like the following (which isn't valid syntax):
jobs = jobs.Where(x => x.Appointments.Where(p => p.Type == "Initial"));

I believe you're looking for Any() to introduce the condition that a job must have at least one Appointment of type "Initial", viz:
var initialJobs = jobs
.Where(j => j.Appointments.Any(a => a.Type == "Initial"));
Where requires a predicate argument (Func returning a bool), and Any() returns true if at least one element meets the inner predicate (a.Type == "Initial")

If you only want to load the appointments for jobs that have the type as Initial, you could also do this:
var jobs = appointments.Where(x => x.Type == "Initial").Select(x => x.Job);
This should give you Appointments inside the jobs that only have the Initial type.

If you wanna return list of jobs as long as the list contain at least one Type == "Initial", you should use Any(), Your case seem looks like this
var jobs = jobs.Where(x => x.Appointments.Any(p => p.Type == "Initial"));
Or return job if it contains all appointments with Type == "Initial", you might use Where()
var jobs = jobs.Where(x =>
{
var appointmentCount = x.Appointments.Count();
var validAppointments = x.Appointments.Where(p => p.Type == "Initial");
return appointmentCount == validAppointments.Count();
});

Related

Simplify querying SQL in Entity Framework Core

I have the following code:
public async Task<IEnumerable<Submission>> SelectSubmissionsAsync(string submitterId, IEnumerable<Group> groups)
{
var submissions = new List<Submission>();
var apps = context.Apps
.Select(a => new
{
Id = a.Id,
Member = a.MemberHistories.OrderByDescending(ash => ash.MemberChangeDate).FirstOrDefault().Member,
Owner = a.OwnerHistories.OrderByDescending(oh => oh.OwnerChangeDate).FirstOrDefault().Owner
})
.ToDictionary(x => x.Id, x => x.Member + x.Owner);
var subs = context.Submissions.ToList();
foreach (var sub in subs)
{
if (apps.ContainsKey((Guid)sub.AppId))
{
var value = apps[(Guid)sub.AppId];
var check = value.Contains(submitterId, StringComparison.InvariantCultureIgnoreCase) || groups.Any(g => value.Contains(g.Id, StringComparison.InvariantCultureIgnoreCase));
if (check)
submissions.Add(sub);
}
}
}
public class Submission
{
public Guid Id { get; set; }
public Application App { get; set; }
public Guid? AppId { get; set; }
}
public class App
{
public Guid Id { get; set; }
public string Identifier { get; set; }
public ICollection<MemberHistory> MemberHistories { get; set;}
public ICollection<OwnerHistory> OwnerHistories { get; set;}
}
Is there a way to simplify this code (avoid for loop for example)?
Ideally you should be able to construct a single query looking something like this:
var appInfo = context.Apps
.Select(a => new
{
Id = a.Id,
Member = a.MemberHistories.OrderByDescending(ash => ash.MemberChangeDate).FirstOrDefault().Member,
Owner = a.OwnerHistories.OrderByDescending(oh => oh.OwnerChangeDate).FirstOrDefault().Owner
})
.Where(appCriteria)
;
var submissions = context.Submissions
.Where(s => appInfo.Any(app => s.AppId == app.Id))
.ToList();
That will allow your app to build a single SQL command that filters the apps down to just the ones you want before bringing them back from the database.
Building checkCriteria will be complicated, because that's going to be based on the "OR"/Union of several criteria. You'll probably want to build a collection of those criteria, and then combine them using a strategy similar to what I've defined here. If you start with a collection of values including submitterId and groupIds, each criteria would be something like s => s.Member == val || s.Owner == val.
In order to create these expressions, you'll probably need to declare a class to represent the type that you're currently using an anonymous type for, so you have a name to associate with the generic arguments on your Expression types.

How to create a where condition to an object inside an object in c# linq?

Let's say I have a class like:
public class TrainingPlan
{
public int Id { get; set; }
public int ProjectId { get; set; }
public string TrainingPlanName { get; set; }
public List<Training> TrainingList { get; set; }
public bool IsDeleted { get; set; }
}
And a Training object inside it:
public class TrainingViewModel : AuditViewModel
{
public int Id { get; set; }
public int ProjectId { get; set; }
public int TrainingPlanId { get; set; }
public bool IsDeleted { get; set; }
public ProjectViewModel ProjectObject { get; set; }
public TrainingPlanViewModel TrainingPlanObject { get; set; }
}
I could write something like this in order to retrieve TrainingPlans where IsDeleted = false and also retrieve the Training Objects attached to it.
var result = _trainingPlanRepository.FindBy(t => t.ProjectId == projectId && t.IsDeleted == false).ToList();
But how do I set a condition for the Training objects where IsDeleted = false also?
You could use Any() like below. Assumption Your t has a List<Training> which you want to query as well to get the non-deleted ones.
var result = _trainingPlanRepository.
Where(t => t.ProjectId == projectId && !t.IsDeleted &&
t.TrainingList.Any(x => !x.IsDeleted)).ToList();
var trainingPlans = _trainingPlanRepository
.Where(t => t.ProjectId == projectId && t.IsDeleted == false)
.ToList();
Now, In trainingPlans variable, All related training objects exist in it, so we want to filter it bases on IsDelted Property. so you can use below code:
foreach (var item in trainingPlans)
{
item.trainingList = item.trainingList.Where(t => !t.IsDelete).ToList();
}
good luck.
I guess there is a one-to-many relation (possibly many-to-many) between TrainingPlan and Training: every TrainingPlan has zero or more Tranings, and every Training belongs to exactly one TrainingPlan, namely the Training that the foreign key TrainingId (or something similar) refers to.
So somewhere deep inside your repository, you'll have two IQueryable sequences:
IQueryable<Training> trainings = ...
IQueryable<TrainingPlan> trainingPlans = ...
You want to query all trainingplans that are not deleted and that have a ProjectId equal to projectId, together with all its not deleted TrainingPlans.
Using method syntax this is fairly straightforward:
var result = trainingPlans
// keep only the training plans you want to keep:
.Where(trainingPlan => trainingPlan.ProjectId == projectId && !trainingPlan.IsDeleted)
// GroupJoin each trainingPlan with its trainings:
.GroupJoin(trainings,
trainingPlan => trainingPlan.Id, // from each training plan take the Id
training => training.TrainingPlanId, // from each training take the foreign key
(trainingPlan, matchingTrainings) => new // take the training plan with all its matching trainings
{ // to make a new
// Select the trainingplan properties you plan to use
Id = trainingPlan.Id,
Name = trainingPlan.Name,
...
// Keep only the non-deleted trainings
Trainings = matchingTrainings.Where(training => !training.IsDeleted)
.Select(training => new
{
// Select only the training properties that you plan to use:
Id = training.Id,
Date = training.Date,
...
// not needed, you know the value:
// TrainingPlanId = training.TrainingPlanId,
})
.ToList(),
});
Another method would be to remove the non-deleted trainings before the GroupJoin:
var validTrainingPlans = trainingPlans.Where(...);
var validTrainings = trainings.Where(training => !training.IsDeleted);
var result = validTrainingPlans.GroupJoin(validTrainings,
... etc

Linq Value cannot be null on FK

Using C# MVC5 Visual studio 2015.
I have a method that contains the following code:
public List<OffersOnPropertyViewModel> Build(string buyerId)
{
var filtered = _context.Properties.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
var model = filtered.Select(c =>
{
var item = new OffersOnPropertyViewModel()
{
PropertyType = c.PropertyType,
NumberOfBedrooms = c.NumberOfBedrooms,
StreetName = c.StreetName,
Offers = c.Offers.Where(d => d.BuyerUserId == buyerId).Select(x => new OfferViewModel
{
Id = x.Id,
Amount = x.Amount,
CreatedAt = x.CreatedAt,
IsPending = x.Status == OfferStatus.Pending,
Status = x.Status.ToString(),
BuyerUserId = x.BuyerUserId
}),
};
return item;
}).ToList();
//TODO: refactor, shorten linq, duping where clause
return model;
}
Here is the model:
public class Property
{
[Key]
public int Id { get; set; }
[Required]
public string PropertyType { get; set; }
[Required]
public string StreetName { get; set; }
[Required]
public string Description { get; set; }
[Required]
public int NumberOfBedrooms { get; set; }
[Required]
public string SellerUserId { get; set; }
public bool IsListedForSale { get; set; }
public ICollection<Offer> Offers { get; set; }
}
In the DB Offers table has the property id as its FK.
The method fails at runtime saying the Value cannot be null.
When I step through I notice the filtered results (in the example its 1 result), is saying offers is null. Although the query just filtered the results based on "x.Offers".
I simply need a way to retrieve a list of property's that have offers made by the buyerId provided. Is my approach wrong? or am i missing a one liner?
Thanks
You will need to add Include() to your LINQ query to bring in child objects, as follows:
var filtered = _context.Properties.Include("Offers")
.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
The reason your filter works with the Any() is because when generating the SQL query, this part forms the WHERE clause and is not included in the SELECT.

How to convert this LINQ to Entities query into 'Method Syntax' instead of 'Query Syntax'

I have the following 2 EF models:
public class Rule
{
public int Id { get; set; }
public string RuleValue { get; set; }
public bool IsActive { get; set; }
public List<Exclusion> Exclusions { get; set; }
}
public class Exclusion
{
public int Id { get; set; }
public int ApplicationId { get; set; }
public int SiteId { get; set; }
public int RuleId { get; set; }
[ForeignKey( "RuleId" )]
public Rule Rule { get; set; }
}
I want to query the database to return a List but only where there isn't a related record in the Exclusions table, based on the related RuleId, and for the specified ApplicationId and SiteId. Ultimately, taking into account any Application/Site specific exclusions, so as to not include those Rules in the results that I return.
I've been able to achieve this so far using the following query:
IQueryable<Rule> query =
from r in context.Rule
where r.IsActive && !( from e in context.Exclusion
where e.ApplicationId == applicationId &&
e.SiteId == siteId
select e.RuleId )
.Contains( r.Id )
select r;
I always use Method Syntax everywhere else and for consistency would prefer not to have just this one method that uses Query Syntax, but I've not been able to get the same thing working using Method Syntax.
Why aren't you navigating to the exclusions for this rule (r.Exclusions) instead of all Exclusiong (context.Exclusions) and then filtering on the current rule? This is very backward
If i understood your requirement right you should do:
var query = context.Rule
.Where(r=>r.IsActive)
.Where(r=>!r.Exclusions.Any(e=>e.ApplicationId == applicationId && e.SiteId == siteId);
var query = context.Rule.Where(r => r.IsActive && !context.Exclusion.Any(e => e.ApplicationId == applicationId && e.SiteId==siteId && r.Id==e.RuleId))
Directly translating:
IQueryable<Rule> query = context.Rule
.Where(r => r.IsActive && !context.Exclusion
.Where(e => e.ApplicationId == applicationId && e.SiteId == siteId)
.Select(e => e.RuleId)
.Contains(r.Id)
)
.Select(r => r);
But you can of course omit the select with the identity function (just as the compiler probably would have):
IQueryable<Rule> query = context.Rule
.Where(r => r.IsActive && !context.Exclusion
.Where(e => e.ApplicationId == applicationId && e.SiteId == siteId)
.Select(e => e.RuleId)
.Contains(r.Id)
);

Can I send a property and the total sum of another property to the view at once?

I have a database where I store Orders of which the model looks as follows:
public class Order
{
public int OrderId { get; set; }
public int ProductId { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public int UserId { get; set; }
public DateTime Date { get; set; }
public bool IsConfirmed { get; set; }
public bool IsSettledWithSalary { get; set; }
public virtual Product Product { get; set; }
public virtual User User { get; set; }
}
So the orders are linked to product (for every product ordered there a new order is made) and to the user that did the order. The property IsConfirmed is true if someone's order is confirmed and IsSettledWithSalary is true if the specific order has been deduced from the salary.
Now I want to make a table that shows the balance and name of a user, i.e., display the name of the user with after it the sum of the orders that are Confirmed but not yet deduced from the salary.
To do this I send the following tuple to the view in the controller:
var users = db.Orders.Include(o => o.User)
.Where(o => o.IsConfirmed == true &&
o.IsSettledWithSalary == false)
.GroupBy(order => order.UserId)
.Select(user => user.FirstOrDefault());
var balances = db.Orders
.Where(order => order.IsConfirmed == true &&
o.IsSettledWithSalary == false)
.GroupBy(order => order.UserId)
.Select(group => group.Sum(order => order.UnitPrice
* order.Quantity) );
return View(Tuple.Create(users.ToList(), balances.ToList()));
Then in the view I picked up the tuple by using the model
#model Tuple<List<Models.Order>, List<Decimal>>
and accessing them with Model.Item1 and Model.Item2, and using a foreach loop for setting every user next to the salary in a table.
I find this solution pretty ugly and possibly error prone so that is why I want to create a better query / lambda expression to send to the view.
Is it possible to just send the usernames and balance to the view without making a tuple? And if so, how?
Use a "ViewModel" or you could calculate the property and then stick it on ViewBag/ViewData
Example ViewModel:
class OrderTotalViewModel
{
public string User {get;set;}
public decimal Total {get;set;}
}
Populate:
var users = db.Orders.Include(o => o.User)
.Where(o => o.IsConfirmed == true &&
o.IsSettledWithSalary == false)
.GroupBy(order => order.UserId)
.Select(group => new OrderTotalViewModel
{
User = u.First().Name, // or u.FirstName etc...
Total = u.Sum(order => order.UnitPrice * order.Quantity)
});
In view:
Name: #Model.User
Total: #Model.Total

Categories

Resources