Select Filtered with LINQ and EF - c#

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
});
}
}

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.

Check value of same column in all tables [Entity Framework]

I have more than 100 tables in data base in which 60+ table's contain column called ShortCode nvarchar(12) which represent globally unique code of that record.
Now is there any way to find that the ShortCode value eg. AST_SHIP_FIRE present in any of the table in database.
Note:ShortCode is user define.
currently I am try below code,it works but I have to code for all table.
if (entities.Table1.Any(x => x.ShortCode.Trim().ToLower() == a.ShortCode.Trim().ToLower())
{return false;}
else if(entities.Table2.Any(x => x.ShortCode.Trim().ToLower() == a.ShortCode.Trim().ToLower()))
{return false;}
else if( entities.Talble3.Any(x => x.ShortCode.Trim().ToLower() == a.ShortCode.Trim().ToLower()))
{return false;}
.
.
.
else
{
//insert code
}
I think there may be more efficient way.
Ok, maybe not very straightforward but lets do it!
First of all define an interface for ShortCode property and implement it by any entity that has it:
public interface ITableWithShortCode
{
public string ShortCode { get; set; }
}
public class Table1 : ITableWithShortCode
{
public long Id { get; set; }
public string ShortCode { get; set; }
}
public class Table2 : ITableWithShortCode
{
public long Id { get; set; }
public string ShortCode { get; set; }
}
Now using power of Reflection you can write a method like this:
public bool IsExistShortCode(string shortCode)
{
using (var context = new AppDbContext())
{
/*
find all tables that are defined in your DbContext and are implemented ITableWithShortCode like:
public DbSet<Table1> Table1 { get; set; }
public DbSet<Table2> Table2 { get; set; }
...
*/
var properties = typeof(AppDbContext).GetProperties()
.Where(p => p.PropertyType.IsGenericType
&& typeof(ITableWithShortCode).IsAssignableFrom(p.PropertyType.GenericTypeArguments[0]));
foreach (var property in properties)
{
var contextProp = (IQueryable<ITableWithShortCode>)property.GetValue(context);
bool isExist = contextProp.Any(p => p.ShortCode == shortCode);
if (isExist)
return true;
}
return false;
}
}
Note: You can do some optimization on this code, I prefered to keep it in its simplest state to show the idea. But in production, for example you can easily cache DbContext properties on startup and use it afterward

system.InvalidOperationException in entityframework

I have a problem when I update a line with a foreign key. The principle idea is to update a row in the database with a generic method but I have an exception when I save modification in the data base so I try to make the state of entity os modified but not the worker.
else if (ModeButtonVMCaracteristiquesType == ModeButtonVMCaracteristiqueType.EDITIONCaracteristiqueTypeItem)
{
int idCaracteristiqueSelected = Convert.ToInt32(CaracteristiqueSelected.idCharacteristicItem);
var LineModified = (from x in ImItemsModel.imtypeitems select x.imcharacteristicsitems).ToList();
LineModified.ForEach(p => ImItemsModel.Entry(p).State = System.Data.Entity.EntityState.Modified);
var UpdateCaracteristicTypeItem = StaticGenericUpdate.UpadateRowInModel<imcharacteristicsitem>(ImItemsModel, "idCharacteristicItem", idCaracteristiqueSelected, "fk_idTypeItemIMCaracteristicsItems", fk_value, propertiesForModel, propertiesForView, this);
ImItemsModel.SaveChanges();
So I have two models:
public partial class imcharacteristicsitem
{
public imcharacteristicsitem()
{
this.imvaluesofitemscaracteristics = new HashSet<imvaluesofitemscaracteristic>();
}
public int idCharacteristicItem { get; set; }
public string characteristicItem { get; set; }
public string unitCaracteristicItem { get; set; }
public int fk_idTypeItemIMCaracteristicsItems { get; set; }
public byte[] typeValueCaracteristicItem { get; set; }
public virtual ICollection<imvaluesofitemscaracteristic> imvaluesofitemscaracteristics { get; set; }
public virtual imtypeitems imtypeitem { get; set; }
}
and:
public partial class imtypeitems
{
public imtypeitems()
{
this.imcharacteristicsitems = new HashSet<imcharacteristicsitem>();
this.imitems = new HashSet<imitem>();
}
public int idTypeItem { get; set; }
public string DesignationTypeItem { get; set; }
public byte[] SymbolTypeItem { get; set; }
public Nullable<int> MaxNumberConnectionsTypeItem { get; set; }
public virtual ICollection<imcharacteristicsitem> imcharacteristicsitems { get; set; }
public virtual ICollection<imitem> imitems { get; set; }
}
and the generic method is:
public static T UpadateRowInModel<T>(DbContext Model, string NameiD, int IdSelected, string NameFk_key ,int fk_key, IList<PropertyInfo> propertiesModel, IList<PropertyInfo> propertiesView, VMCaracteristicType vm) where T : class
{
T item = Model.Set<T>().Find(IdSelected);
foreach (var property in propertiesModel)
{
if (propertiesView.Count != 0)
{
property.SetValue(item, propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name)) == null ? null :
propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name)).GetValue(vm));
}
if (property.Name == NameiD)
{
property.SetValue(item, IdSelected);
}
if (property.Name == NameFk_key)
{
property.SetValue(item, null);
Model.Entry(item).Property(NameFk_key).IsModified = false;
}
}
return item;
}
EDIT :
so i realise that the probleme is the entity framework can't save because my table imtypeitems have a collection of caractéristique
public virtual ICollection<imcharacteristicsitem> imcharacteristicsitems { get; set; }
so i must delet the row that i wish update it from this table and after that i will save so i can't delet the row of collection i try like this :
var RowBeforupdate = ImItemsModel.imcharacteristicsitems.Include("imtypeitem").Single(row => row.idCharacteristicItem == idCaracteristiqueSelected);
var UpdateCaracteristicTypeItem = StaticGenericUpdate.UpadateRowInModel<imcharacteristicsitem>(ImItemsModel, "idCharacteristicItem", idCaracteristiqueSelected, "fk_idTypeItemIMCaracteristicsItems", fk_value, propertiesForModel, propertiesForView, this);
ImItemsModel.Entry(RowBeforupdate).CurrentValues.SetValues(UpdateCaracteristicTypeItem);
just a ps : i am not a expert in entity framework :(
The most likely cause for the FK null exception is that the FK entity property is processed/set in the foreach loop after the FK_ID property. So even if you change the FK_ID IsModified value to false, you're most likely later setting the imtypeitem property to null.
Also you are changing all model properties based on view properties; so if there's no particular view property you'll be setting the model property to null. This is seldom the thing you want to be doing in this scenario. Usually you want to change just the corresponding view properties and leave the other model properties unchanged.
So the foreach part of your generic method should look like this:
if (propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name)) != null)
{
property.SetValue(item, propertiesView.First(elem => elem.Name.Equals(property.Name)).GetValue(vm));
}
If you have certain properties that are part of view properties but you don't want them to be updated you can just skip them (using your Nameid and NameFk_key):
if(property.Name == NameiD || property.Name == NameFk_key)
continue;
So the final form of the generic method might look something like this:
public static T UpadateRowInModel<T>(DbContext Model, string NameiD, int IdSelected, string NameFk_key ,int fk_key, IList<PropertyInfo> propertiesModel, IList<PropertyInfo> propertiesView, VMCaracteristicType vm) where T : class
{
T item = Model.Set<T>().Find(IdSelected);
foreach (var property in propertiesModel)
{
if(property.Name == NameiD || property.Name == NameFk_key)
continue;
var viewP = propertiesView.FirstOrDefault(elem => elem.Name.Equals(property.Name));
if (viewP != null)
{
property.SetValue(item, viewP.GetValue(vm));
}
}
return item;
}
There's also a variation possible where you do a foreach on the propertiesView; also the parameters for the method can be adjusted.
On a sidenote: I'm not sure if
var LineModified = (from x in ImItemsModel.imtypeitems select x.imcharacteristicsitems).ToList();
LineModified.ForEach(p => ImItemsModel.Entry(p).State = System.Data.Entity.EntityState.Modified);
does something useful, or if it does anything. The p in lambda is an ICollection of enity types and not a separate DB context entity. Not to mention that you want to set Modified state for ALL entities of type imcharacteristicsitem. Maybe you can explain the reason for this.

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

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