Our system is receiving input from two external sources (phone call/web submission).
// Table-Per-Type Hierarchy
public class Submission
{
public int SubmissionId { get; set; } // Primary Key
public int? PersonId { get; set; }
public int? CompanyId { get; set; }
public long? EmployeeId { get; set; }
public bool? Completed { get; set; }
public string AbsenceReason { get; set; }
public string AbsenceType { get; set; }
public DateTime? AbsenceDate { get; set; }
}
public class CallSubmission : Submission
{
public string CallerId { get; set; }
public string PhoneNumber { get; set; }
public DateTime? HangUp { get; set; }
public DateTime? PickUp { get; set; }
}
public class WebSubmission : Submission
{
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
public DateTime SubmissionDate { get; set; }
}
My goal is to retrieve all submissions within the past seven days using PickUp/SubmissionDate depending on the type of submission we're dealing with. Is it possible to achieve this with a single LINQ statement? Ideally, I'd like to avoid having to load two different data sets in-memory.
Statements I'm hoping to integrate
Users.Where(user => user.UserName == name)
.SelectMany(user => user.Submissions)
.OfType<CallSubmission)()
.Where(call => call.PickUp >= startDate)
Users.Where(user => user.UserName == name)
.SelectMany(user => user.Submissions)
.OfType<WebSubmission>()
.Where(web => web.SubmissionDate >= startDate)
Actually (surprisingly for me) what are you asking is possible (at least in the latest EF6.1.3) since the C# is and as operators are supported (they are basically used by OfType method).
var query = db.Users
.Where(user => user.UserName == name)
.SelectMany(user => user.Submissions)
.Where(subm => (subm as CallSubmission).PickUp >= startDate
|| (subm as WebSubmission).SubmissionDate >= startDate);
The important part is to use as operator and not cast which generates unsupported exception. There is no need to check for null because the generated SQL query handles NULLs naturally.
Related
I'm using EF Core and ASP.NET Core API, and I have entities that are more or less defined as follows:
public class MyUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Circle
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyUser> Members { get; set; } = new List<MyUser>();
}
public class Tender
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public TenderVisibility Visibility { get; set; }
public ICollection<TenderCircle> TenderCircles { get; set; } = new List<TenderCircle>();
}
public enum TenderVisibility
{
Public,
Circles,
}
public class TenderCircle
{
public int TenderId { get; set; }
[ForeignKey("TenderId")]
public Tender Tender { get; set; }
public int CircleId { get; set; }
[ForeignKey("CircleId")]
public Circle Circle { get; set; }
}
Then, I want to get all my Tenders where a certain user is member of the Circle if the visibility is set to Circles, so I have something like this:
public IEnumerable<Tender> GetTenders(string userId)
{
var user = _context.Users.Where(u => u.Id == userId);
return _context.Tenders
.Include("TenderCircles.Circle.Members")
.Where(t => t.Visibility == TenderVisibility.Public ||
(t.Visibility == TenderVisibility.Circles && t.TenderCircles.Select(tc => tc.Circle.Members).ToList().Contains(user.ToList()))).ToList()
.ToList();
}
But that seems to me way too complicated, and it actually doesn't work as it throws error saying it is not allowed for the second part where I select circle members and check whether user is contained. Any idea how to achieve what I want more efficiently?
You can't call ToList in Where condition. You can use Any instead;
public IEnumerable<Tender> GetTenders(string userId)
{
var user = _context.Users.Where(u => u.Id == userId);
return _context.Tenders
.Include("TenderCircles.Circle.Members")
.Where(t => t.Visibility == TenderVisibility.Public ||
(t.Visibility == TenderVisibility.Circles && t.TenderCircles.Any(tc => tc.Circle.Members.Any(m=>m.Id == userId)))
.ToList();
}
I couldn't try it because it is a bit complicated to reproduce. But please let me know if there is any syntax error
I have tables with created using below models in Entity Framework
public class User
{
public int Id{ get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public ICollection<AssigneeMonth> AssigneeMonths { get; set; }
}
public class AssigneeMonth
{
public int Id { get; set; }
public int AssigneeId { get; set; }
public Month Month { get; set; }
public User Assignee { get; set; }
}
public class ProjectAssignee
{
public int Id { get; set; }
public int ProjectId { get; set; }
public int AssigneeId { get; set; }
public bool IsActive { get; set; }
public AutomationProject Project { get; set; }
[ForeignKey("AssigneeId")]
public User User { get; set; }
}
I am trying to get data into the collection AssigneeMonths from AssigneeMonth using this code:
var assn = dataContext.ProjectAssignees
.Where(r => r.Project.Name == project.Name && r.IsActive)
.Include(u => u.User)
.ToList();
But AssigneeMonths collection in the above assn is always null even if I have data in AssigneeMonth for the user
May I know what's wrong with the above code?
Since you're using eager loading, you're only loading the information for user, not its navigational properties.
You can use the code in this answer, which applied to your case will look like this:
var assn = dataContext.ProjectAssignees
.Where(r => r.Project.Name == project.Name && r.IsActive)
.Include(u => u.User.SelectMany(u => u.AssigneeMonths))
.ToList();
Since AssigneeMonths is a collection, you need to use SelectMany instead of Select.
Other option would be to do it like this (as posted in other answers in that link):
var assn = dataContext.ProjectAssignees
.Where(r => r.Project.Name == project.Name && r.IsActive)
.Include(u => u.User)
.Include("User.AssigneeMonths")
.ToList();
But I personally don't like the second method because its easy to make mistakes with the string and it will hide the errors until runtime.
On my previous work EF keeps query as IQueryable until I used some materialized methods, for example, ToList() or FirstOrDefault(), and I could create big, flexible and fast queries. But on my new work I noted, that sequences inside IQueryable methods have ICollection type and, of course, they have IEnumerable methods (not IQueriable). I can't understand what wrong and how I can change it. I haven't find solution in google. The version of EF is the same as on my previous work (6.1.3).
For Example, I have 2 entity classes and my own class:
public class Client // Entity
{
public int ID { get; set; }
public string FullName { get; set; }
public string Address { get; set; }
...
public virtual ICollection<Parcel> ParcelsSender { get; set; }
public virtual ICollection<Parcel> ParcelsReceiver { get; set; }
}
public class Parcel // Entity
{
public int ID { get; set; }
public string ParcelNumber { get; set; }
...
public int ClientSenderID { get; set; }
public int ClientReceiverID { get; set; }
public virtual Client ClientSender { get; set; }
public virtual Client ClientReceiver { get; set; }
}
public class ClientCustom // My class
{
public int ID { get; set; }
public string FullName { get; set; }
public bool IsInexperienced { get; set; }
}
and I created this EF query:
var clients = context.Clients
.OrderBy(x => x.FullName)
.Select(x => new ClientCustom()
{
ID = x.ID,
FullName = x.FullName,
IsInexperienced = x.ParcelsSender.Select(y => y.ID).FirstOrDefault() == 0
&& x.ParcelsReceiver.Select(y => y.ID).FirstOrDefault() == 0
})
.ToList();
In this case, the problem is that x.ParcelsSender and x.ParcelsReceiver in query are ICollection<Parcel> type; In turn, x.ParcelsSender.Select() and x.ParcelsReceiver.Select() methods returns IEnumerable<Parcel> instead IQueriable<Parcel>. As I know, that means, queries become very slowly with big amount of data.
I have a context called companyContext. There are Three tables Reports,Logs and Employees.I am given a case id and I need to get a list of all the employees who belong to a certain case (and a log if there is one, but i dont need to worry about that yet). So I made a query get all the employees where EmployeeID is equal to Employee.ID, where the Reports CaseID is eaual to case.id. However, its not reutning a list, its returning a Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable}]
Am I making the query correctly ? Thanks guys.
var employees = await context.Reports.Include(s => s.Employee)
.ThenInclude(e => e.ID)
.AsNoTracking()
.Where(r => r.CaseID == Case.Id);
Models
public class Log
{
public int ID { get; set; }
public string Input { get; set; }
public string Tag { get; set; }
public DateTime LogDate { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Report
{
public int ID { get; set; }
public string CaseID { get; set; }
public int EmployeeD { get; set; }
public int? LogID { get; set; }
public Employee Employee { get; set; }
public Log Log { get; set; }
}
SingleOrDefaultAsync
throws an exception if there is more than one element in the sequence.
So you should not be using SingleOrDefaultAsync if you expect multiple records.
If I'm interpreting your question properly and you want employees themselves, you could start with them and then narrow via the Employee class's .Reports navigation property.
var employees = await context.Employees.AsNoTracking()
.Where(e => e.Reports.Any(r => r.CaseID == case.Id))
.ToListAsync();
So entity is actually like using a real database. I had to do a where and select clause, but also a toList to convert it into something readable.
var employees = await context.Reports.Where(r => r.CaseID == Case.Id);
.Select(r => r.Employee)
.ToList();
I am currently working on a cinema booking system for a school project and have run into a problem.
I have a Movie model which contains a list of show models(date and time for the viewing of the movie). I need to get all the movie objects that are shown on a specific date with a list containing only the Show objects which date is equal to the specific date. I have tried various ways to do it in entity but cant seem to get it to work.
Here is the Movie class:
[DataContract]
public class Movie
{
[Key, Required, DataMember]
public Guid Id { get; set; }
[Required, DataMember]
public string Name { get; set; }
[Required, DataMember]
public string Info { get; set; }
[Required, DataMember]
public DateTime Premiere { get; set; }
[Required, DataMember]
public MovieType Type { get; set; }
[DataMember]
public ICollection<Show> Shows { get; set; }
[DataMember]
public string IMDBID { get; set; }
}
public enum MovieType
{
Movie2D = 0,
Movie3D = 1,
KidsMovie = 2
}
Here is the Show class:
[DataContract]
public class Show
{
[Key, Required, DataMember]
public Guid Id { get; set; }
[Required, DataMember]
public Guid MovieId { get; set; }
[Required, DataMember]
public Guid ScreenId { get; set; }
[Required, DataMember]
public DateTime DateTime { get; set; }
[Required, DataMember]
public ShowType Type { get; set; }
[DataMember]
public Screen Screen { get; set; }
[DataMember]
public Movie Movie { get; set; }
}
public enum ShowType
{
Standard = 0,
Premiere = 1,
}
Here is the GetMovies(DateTime date) method:
public List<Movie> GetMovies(DateTime date)
{
using (EntityContext db = new EntityContext())
{
List<Movie> movieList = db.Movies
.Include("Show")
.Where(x => x.Shows.Where(x => x.DateTime.Date == date.Date)).ToList();
return movieList;
}
}
I know that this function isn't working but hope it would show what I am trying to do.
I think DateTime.Date is not supported in Linq to Entities. You could use DbFunctions.TruncateTime static method:
var justDate= date.Date;
var movieList=db.Movies.Include(x=>x.Shows)
.Where(x => x.Shows.Any(x => DbFunctions.TruncateTime(x.DateTime) == justDate))
.ToList();
Update:
After read #Jonathan's comment I did a little research and it's true using DbFunctions.TruncateTime could affect the performance. You can find a detailed explanation in this post.
Following the same idea of #JonathanAllen and #MattJohnson, you can avoid to use that function if you do a range query instead, truncating first the Time from date parameter:
var startDate= date.Date;
var endDate= startDate.AddDays(1);
var movieList=db.Movies.Include(x=>x.Shows)
.Where(x => x.Shows.Any(x =>x.DateTime >= startDate && x.DateTime < endDate)
.ToList();
You should be able to use Any
public List<Movie> GetMovies(DateTime date)
{
using (EntityContext db = new EntityContext())
{
List<Movie> movieList = db.Movies.Include("Show")
.Where(x => x.Shows.Any(x => x.DateTime.Date == date.Date));
return movieList;
}
}
My request good working with All() method.
model.ReferenceList = db.JournalCardReference.OrderBy(a => a.orderF)
.Include(x => x.JournalCardField)
.Where(x => x.JournalCardField
.All(f => f.deleted == null || f.deleted != true)).ToList();