How can I share object level logic across Entity Framework queries? - c#

I am looking to share some very simple logic across multiple queries in Entity Framework. Say I have the following models
public class User
{
// user properties
public ICollection<UserRole> Roles { get; set; }
}
public class UserRole : IDateRestricted
{
public RoleType Type { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
public interface IDateRestricted
{
DateTime StartDate { get; set; }
DateTime? EndDate { get; set; }
}
If I was using normal LINQ I could create a simple extension method that would allow me to determine if the role was currently active.
public static class DateRestrictedExtensions
{
public static Boolean IsActive(this IDateRestricted entity)
{
return entity.StartDate <= DateTime.Now && (entity.EndDate == null || entity.EndDate > DateTime.Now);
}
}
and I could use it like so.
var admins = users.Where(x => x.Roles.Any(role => role.Type == RoleType.Admin && role.IsActive()));
With entity framework this fails with the dreaded:
LINQ to Entities does not recognize the method Boolean IsActive(Domain.Interfaces.IDateRestricted) method, and this method cannot be translated into a store expression.
Is there a way that I can share this logic and have LINQ to Entities support it when querying?

You should use Expression<Func<IDateRestricted, bool>> instead of Func<IDateRestricted, bool> - presented exception's description exactly points to it:
public static class DateRestrictedExtensions
{
public static Expression<Func<IDateRestricted, bool>> IsActive()
{
return entity => entity.StartDate <= DateTime.Now
&& (entity.EndDate == null || entity.EndDate > DateTime.Now);
}
}
var admins = users.Where(x => x.Roles.AsQueryable().Where(role.Type == RoleType.Admin)
.Any(DateRestrictedExtensions.IsActive()));

Related

How to make Entity Framework only filter data when query fields are not null?

I have an mvc .net core application where the user is displayed some data, and can filter that data based on some input that he/her gives.
If no filters/constraints are given, then the while set of entities should jsut be returned.
I found this example, and found the second answer to be quite neat, with regards to what I want to do.
So I added this thing at the bottom of my controller:
public static class QueryableEx
{
public static IQueryable<T> Where<T>(
this IQueryable<T> #this,
bool condition,
Expression<Func<T, bool>> #where)
{
return condition ? #this.Where(#where) : #this;
}
}
And then made this controller action which filters by one of three possible inputs:
[HttpPost]
public IActionResult query(QueryModel query)
{
List<CustomerModel> customers = new List<CustomerModel>();
var db = new DemoContext();
customers = db.Customers
.Where(query.Name != null, x => x.Name == query.Name)
.Where(query.Surname != null, x => x.Surname == query.Surname)
.Where(query.Age != null, x => x.Age == query.Age)
.ToList();
return View("Index", customers);
}
This works like a charm, If I input a certain name, then I only get the results with that name and vice versa.
There is an issue though. If all of the input fields are null, then everything is filtered out. I want the opposite to happen, if no filters have been entered, then just return everything.
How do I do this? Making sure that no filtering happens if all the input fields are empty?
EDIT
By request, I here is the model I use for queries
public class QueryModel
{
public string Name {get;set; }
public string Surname { get; set; }
public uint Age { get; set; }
}
And here is the customer one:
public class CustomerModel
{
public int Id{get;set; }
[Required]
public string Name {get;set; }
[Required]
public string Surname { get; set; }
[Required]
[Range(18,110)]
public uint Age { get; set; }
[Required]
public virtual AdressModel Adress { get; set; }
[Required]
public DateTime Created { get; set; }
[Required]
public virtual List<PurchaseModel> purchases { get; set; }
}
Your model parameters are not nullable, so I suspect that you'll end up looking for customers with Age equal Zero, hence no results.
Try:
customers = db.Customers
.Where(query.Name != default, x => x.Name == query.Name)
.Where(query.Surname != default, x => x.Surname == query.Surname)
.Where(query.Age != default, x => x.Age == query.Age)
.ToList();
Change 'null' to 'default' in each case.

Filtering Child Object with Multiple Criteria

I'm having an issue filtering records returned using linq. The objects involved look like this:
Appointment
public partial class Appointment
{
public Appointment()
{
Callbacks = new HashSet<Callback>();
}
[Key()]
public int AppointmentId { get; set; }
public DateTime Start { get; set; }
public DateTime? Deleted { get; set;}
public virtual ICollection<Callback> Callbacks { get; set; }
}
Callback
public partial class Callback
{
[Key()]
public int CallbackId { get; set; }
public DateTime? Deleted { get; set; }
public virtual Appointment Appointment { get; set; }
public virtual User AssignedTo { get; set; }
}
User
public partial class User
{
public User()
{
Callbacks = new HashSet<Callback>();
}
[Key()]
public int UserId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Ref { get; set; }
public virtual ICollection<Callback> Callbacks { get; set; }
}
I'm trying to return records that meet the following criteria:
The appointment start date must equal searchDate
The appointment is not deleted
The appointment start date must not clash with any appointments that the user already has
I've tried using the following query, but no results are returned (there are appointments available for the date 01/03/2016 (dd/mm/yyyy).
public List<AppointmentSearchResultsViewModel> SearchByDate(DateTime searchDate, string userName)
{
return _context.Appointments
.Where(a =>
a.Start.Date == searchDate
&& a.Deleted == null
&& a.Callbacks.Any(c =>
!(c.Appointment.Start != a.Start
&& c.AssignedTo.Ref == userName
&& c.Deleted == null)
))
.OrderBy(a => a.Start)
.Select(a)
.ToList();
}
Could anyone help me with how to filter correctly based on the criteria above?
Edit
To try and clarify the model:
A user has callbacks
A callback has an appointment
The aim of this query is to search for all appointments on the searchDate where the user does not already have a callback scheduled for the appointment time.
I think you need a negative comparison for your Any-statement:
!a.Callbacks.Any(c =>
(c.Appointment.Start == a.Start
&& c.AssignedTo.Ref == userName
&& c.Deleted == null)
Thus you only got those Callbacks from a.Callbacks which have a different Start-date.
Furtheremore you can ommit the Select(a)-statement at the end and immediately call ToList.
The model and what you are trying to achieve is not really clear to me, but I will try my chances anyway on the part O could understand:
return _context.Appointments
.Where(a =>
a.Start.Date == searchDate
&& a.Deleted == null
&& !a.Callbacks.Any(c =>
(c.Appointment.Start == a.Start
&& c.AssignedTo.Ref == userName
&& c.Deleted == null)
))
.OrderBy(a => a.Start)
.ToList();
Try this and let me know what you get in return...
return context.Users.Where(user => user.Ref = userName)
.SelectMany(user => user.Callbacks)
.Where(cb => cb.Appointment.Deleted == null)
.Where(cb => cb.Appointment.Start == searchDate)
.Select(cb => cb.Appointment)
.ToList();
This should return any appointments that clash with the searchDate parameter

Select Filtered with LINQ and EF

I have a class like this, which is derived from a database with EF (my database contains all records from this class):
public class Products
{
public string color { get; set; }
public string size { get; set; }
public string comment { get; set; }
public string owner { get; set; }
public string buyer { get; set; }
public Nullable<DateTime> After { get; set; }
public Nullable<DateTime> Before { get; set; }
}
Now, on my web form users can specify each property in free text boxes and I want to search in the database entity, based on these properties. The user can decide to fill all properties, or may be just two of them. How do I create the .select in EF?
Any help welcome!
BR,
Ronald
Products.GetAllProducts().Where(x=>(string.IsNullOrEmpty(txtColor.Text) || x.color ==txtColor.Text) &&
(string.IsNullOrEmpty(txtSize) || x.size == txtSize.Text) &&
--- Same as other fields
).AsEnumerable();
Db.Products
.Where(p=>string.IsNullOrEmty(colorTexBox.Text) || p.color==colorTexBox.Text)
.Where(p=>check other property...)
.
.
.AsEnumerable()
Use .Contains method:
private bool IsMatch(string value, string searchCriteria)
{
if(searchCriteria == null || value == null) return true;
return value.ToUpper().Contains(searchCriteria.ToUpper());
}
public Products FindProducts(string color, string size, string comment, string owner, string buyer, datetime? after, datetime? before)
{
using(MyDbContext cont = new MyDbContext())
{
return cont.Products.Where((p) =>
{
return IsMatch(p. color, color) && IsMatch(p.size, size) &&
IsMatch(p.comment, comment) && IsMatch(p.owner, owner) &&
IsMatch(p.buter, buyer); // add your logic for dates here
});
}
}

Fluent Nhibernate QueryOver fetch record only by Date without time

I have following object:
public class DomainMenu:EntityBase
{
public virtual DomainName DomainName { get; set; }
public virtual DateTime PlannedDate { get; set; }
public virtual DateTime CreationDate { get; set; }
public virtual string Notes { get; set; }
}
And mapping:
public class DomainMenuMap : ClassMap<DomainMenu>
{
public DomainMenuMap()
{
Id(c => c.Id).GeneratedBy.Identity();
Map(c => c.PlannedDate);
Map(c => c.CreationDate);
Map(c => c.Notes);
References(c => c.DomainName);
}
}
I have following method :
public IList<DomainMenu> GetDomainMenuesPlannedForNextDays(int domainId)
{
using (_unitOfWorkFactory.Create())
{
var todayDate = DateTime.Now.Date;
var list = _domainMenuRepository.QueryOver()
.Where(c=>c.PlannedDate.Date >= todayDate)
.Where(c => c.DomainName.Id == domainId)
.Future().ToList();
return list;
}
}
In this method I want to get rows that have PlannedDate bigger or equal with today date.I want to compare only date value, without time, but I am getting following error:
could not resolve property: PlannedDate.Date of: DomainMenu
Is it possible to it using QueryOver in Fluent Nhibernate or not?
Note: I am interested only in using this solution , I do not want different methods as I already know them, I just want to know if it's possible with QueryOver.
Thanks.
NHibernate doesn't know what to do with the Date property, it's .Net property and the QueryOver API can't handle it.
Have a look at this blog on how to extend queryover with custom methods and properties
http://blog.andrewawhitaker.com/blog/2015/01/29/queryover-series-part-9-extending-queryover-using-custom-methods-and-properties/

Entity Framework Virtual ICollection How to Query

First of all, I am new to Entity Framework etc and trying to figure some things out. I have a model like this:
public class EventInstance {
[Column("EVENT_INSTANCE_ID")]
public int EventInstanceID { get; set; }
[Column("CUSTOMER_ID")]
public int CustomerID { get; set; }
[Column("EVENT_ID")]
public int EventID { get; set; }
[Column("START_DATE_TIME")]
public System.DateTime StartDateTime { get; set; }
public virtual Event Event { get; set; }
}
I need to access a property in a table called EventTimeEventInstances but this table is not included in the model. I have two questions.
If I add:
public virtual ICollection<EventTimeEventInstance> EventTimeInstances { get; set; }
Will that effect other areas of our application?
Secondly, how do I access the property from the ICollection in a query like this:
public IQueryable<EventInstance> GetInstances(int scheduleID) {
// only returning instances that are 3 months back
DateTime dateRange = DateTime.Now.AddDays(-180);
return EventDBContext.EventInstances.Where
(x => x.CustomerID == MultiTenantID && x.StartDateTime >= dateRange)
.OrderBy(x => x.StartDateTime).AsQueryable();
}
I need to be able to add EventTimeInstances.EventTimeID == scheudleID to this query. How can I do this?
You can use it like that in your query:
public IQueryable<EventInstance> GetInstances(int scheduleID)
{
// only returning instances that are 3 months back
DateTime dateRange = DateTime.Now.AddDays(-180);
return EventDBContext.EventInstances.Where(x =>
x.CustomerID == MultiTenantID &&
x.StartDateTime >= dateRange &&
x.EventTimeInstances.Any(a => a.EventTimeID == scheudleID) ).OrderBy(x => x.StartDateTime).AsQueryable();
}

Categories