Use PredicateBuilder in SelectMany LINQ to Entity Framework - c#

It's the first time I write here on stackoverflow.
I would like to use PredicateBuilder in a LINQ query with SelectMany.
I enter my code
public async Task<List<Articolo>> OttieniElencoArticoliFiltratoComplessoAsync
(ExpressionStarter<Articolo> predicateArticolo,
ExpressionStarter<ArticoloFornitore> predicateFornitore,
ExpressionStarter<Categoria> predicateCategoria,
ExpressionStarter<Magazzino> predicateMagazzino,
ExpressionStarter<PrezzoVendita> predicatePrezzoVendita)
{
using (var database = DatabaseContext.NuovoDatabase())
{
var query = database.Articoli.AsExpandable();
if (predicateArticolo != null)
{
query = query.Where(predicateArticolo);
}
if (predicateFornitore != null)
{
query = query.AsExpandable().SelectMany(a => a.ElencoArticoliFornitore.Where(predicateFornitore)).Select(fornitore => fornitore.Articolo);
}
if (predicateMagazzino != null)
{
query = query.AsExpandable()
.SelectMany(articolo => articolo.ElencoMagazzino.Where(predicateMagazzino))
.Select(magazzino => magazzino.Articolo);
}
if (predicatePrezzoVendita != null)
{
query = query.AsExpandable().SelectMany(articolo =>
articolo.ElencoPrezzoVendita.Where(predicatePrezzoVendita).Select(vendita => vendita.Articolo));
}
if (predicateCategoria != null)
{
query = query.AsExpandable()
.SelectMany(articolo => articolo.ElencoCategorie.Where(predicateCategoria))
.Select(categoria => categoria.Articolo);
}
return await query.ToListAsync();
}
}
I create the predicate like this
private ExpressionStarter<ArticoloFornitore> PredicateFornitore()
{
var ritornaNull = true;
var predicate = PredicateBuilder.New<ArticoloFornitore>();
if (IsEnabledFiltroFornitore && FornitoreSelezionato != null)
{
ritornaNull = false;
predicate = predicate.And(fornitore => fornitore.IdFornitore == FornitoreSelezionato.IdFornitore);
}
return ritornaNull ? null : predicate;
}
private ExpressionStarter<Categoria> PredicateCategoria()
{
var ritornaNull = true;
var predicate = PredicateBuilder.New<Categoria>();
if (IsEnabledCategoriaLivello1 && CategoriaLivello1Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv1 == CategoriaLivello1Selezionata.IdCategoriaLv1);
}
if (IsEnabledCategoriaLivello2 && CategoriaLivello2Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv2 == CategoriaLivello2Selezionata.IdCategoriaLv2);
}
if (IsEnabledCategoriaLivello3 && CategoriaLivello3Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv3 == CategoriaLivello3Selezionata.IdCategoriaLv3);
}
if (IsEnabledCategoriaLivello4 && CategoriaLivello4Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv4 == CategoriaLivello4Selezionata.IdCategoriaLv4);
}
if (IsEnabledCategoriaLivello5 && CategoriaLivello5Selezionata != null)
{
ritornaNull = false;
predicate = predicate.And(categoria =>
categoria.IdCategoriaLv5 == CategoriaLivello5Selezionata.IdCategoriaLv5);
}
return ritornaNull ? null : predicate;
}
I tried this method on LINQPAD with fixed data instead of the PredicateBuilder and the query works.
But with the PredicateBuilder I get the .NET Framework Data Provider error 1025.
How can I solve? I would like to be able to create a dynamic query that takes the parameters from the interface and returns the results.
I hope you can help me.

EntityFramework requires your predicates to be Expression<Func<T, bool>> rather than Func<T, bool>. This is what IQueryable.ToExpandable() does. However, your Entity objects such as articolo.ElencoPrezzoVendita are virtual ICollection objects, so the predicate is reduced to Func<T, bool> which is incompatible with EF.
It doesn't exactly roll off the tongue but you could try something like this (I didn't know the structure of your objects) with database to ensure the Expression is used.
if (predicatePrezzoVendita != null)
{
query = query.AsExpandable()
.SelectMany(articolo =>
database.ElencoPrezzoVendita
.Where(x => articolo.ForeignKeyID == x.ID)
.AsExpandable()
.Where(predicatePrezzoVendita)
.Select(vendita => vendita.Articolo));
}

Related

How can I filter a SQL query in Entity Framework in C#?

I'm trying to convert this into an async method, but I don't think I'm querying the data properly to do it. What am I missing to:
make this cleaner
make it async
All the InventorySelection is, is a class with the various filter items.
private IEnumerable<T_IFS_Inventory> Getquery(T_IFS_InventorySelectDTO InventorySelection)
{
IEnumerable<T_IFS_Inventory> query = _db.T_IFS_Inventory.AsQueryable();
if (InventorySelection.SerialNumber != "" && InventorySelection.SerialNumber != null)
{
query = query.Where(q => q.SerialNumber == InventorySelection.SerialNumber);
}
else
{
if (InventorySelection.ScanDateStart != null)
{
query = query.Where(q => q.ScanDate >= InventorySelection.ScanDateStart);
if (InventorySelection.ScanDateEnd != null)
query = query.Where(q => q.ScanDate <= InventorySelection.ScanDateEnd);
}
if (InventorySelection.StatusDateStart != null)
{
query = query.Where(q => q.DateStatus >= InventorySelection.StatusDateStart);
if (InventorySelection.StatusDateEnd != null)
query = query.Where(q => q.DateStatus <= InventorySelection.StatusDateEnd);
}
if (InventorySelection.CategoryID != 0)
query = query.Where(q => q.CategoryID == InventorySelection.CategoryID);
if (InventorySelection.BrandID != 0)
query = query.Where(q => q.BrandID == InventorySelection.BrandID);
if (InventorySelection.ModelID != 0)
query = query.Where(q => q.ModelID == InventorySelection.ModelID);
if (InventorySelection.EmployeeID != 0)
query = query.Where(q => q.EmployeeID == InventorySelection.EmployeeID);
if (InventorySelection.StatusID != 0)
query = query.Where(q => q.StatusID == InventorySelection.StatusID);
if (InventorySelection.EmployeeStatusID != 0)
query = query.Where(q => q.EmployeeStatusID == InventorySelection.EmployeeStatusID);
if (InventorySelection.CurrentLocationID != 0)
query = query.Where(q => q.CurrentLocationID == InventorySelection.CurrentLocationID);
}
return query;
}
Your query is being executed at the first line, I hope this is a typo, but IEnumerable is wrong as the variable type. It explicitly forces ALL of the records to be loaded into memory before any of your filters are applied. What you want is to defer the execution until all of the criteria have been defined.
IQueryable<T_IFS_Inventory> query = _db.T_IFS_Inventory.AsQueryable();
With only this change the query will be evaluated and the data will be returned as part of the return process. Because the method return type is IEnumerable the change from IQueryable to IEnumerable would force the evaluation as part of the return process.
To make this async, deliberately resolve the query:
return query.ToListAsync();
Or you can await it if you need this method's line info in the stack trace:
return await query.ToListAsync();
Both of these changes will require the method prototype to be made async too:
private async Task<IEnumerable<T_IFS_Inventory>> Getquery(T_IFS_InventorySelectDTO InventorySelection)
However now the method isn't getting a query it is now executing the entire query and bringing all of the data into memory.
A better approach that will allow you to compose on top of this query would be to change the method prototype to return IQueryable
private async IQueryable<T_IFS_Inventory> Getquery(T_IFS_InventorySelectDTO InventorySelection)
Ideally, your method should look like this:
private IQueryable<T_IFS_Inventory> Getquery(T_IFS_InventorySelectDTO InventorySelection)
{
IQueryable<T_IFS_Inventory> query = _db.T_IFS_Inventory.AsQueryable();
if (!String.IsNullOrEmpty(InventorySelection.SerialNumber))
{
query = query.Where(q => q.SerialNumber == InventorySelection.SerialNumber);
}
else
{
if (InventorySelection.ScanDateStart != null)
{
query = query.Where(q => q.ScanDate >= InventorySelection.ScanDateStart);
if (InventorySelection.ScanDateEnd != null)
query = query.Where(q => q.ScanDate <= InventorySelection.ScanDateEnd);
}
if (InventorySelection.StatusDateStart != null)
{
query = query.Where(q => q.DateStatus >= InventorySelection.StatusDateStart);
if (InventorySelection.StatusDateEnd != null)
query = query.Where(q => q.DateStatus <= InventorySelection.StatusDateEnd);
}
if (InventorySelection.CategoryID != 0)
query = query.Where(q => q.CategoryID == InventorySelection.CategoryID);
if (InventorySelection.BrandID != 0)
query = query.Where(q => q.BrandID == InventorySelection.BrandID);
if (InventorySelection.ModelID != 0)
query = query.Where(q => q.ModelID == InventorySelection.ModelID);
if (InventorySelection.EmployeeID != 0)
query = query.Where(q => q.EmployeeID == InventorySelection.EmployeeID);
if (InventorySelection.StatusID != 0)
query = query.Where(q => q.StatusID == InventorySelection.StatusID);
if (InventorySelection.EmployeeStatusID != 0)
query = query.Where(q => q.EmployeeStatusID == InventorySelection.EmployeeStatusID);
if (InventorySelection.CurrentLocationID != 0)
query = query.Where(q => q.CurrentLocationID == InventorySelection.CurrentLocationID);
}
return query;
}
It is important when we use this query builder pattern that you keep the query as an IQueryable until all of the criteria have been added.
In terms of code style, I'd call this very clean, anything else is going to affect the readability, right now it's very easy to follow.

EF Core - use projection and include a collection of an entity

I have a base repository:
public async Task<TEntity> GetByCondition(Expression<Func<TEntity, bool>> predicate, Func<DbSet<TEntity>, IQueryable<TEntity>> baseQuery = null, Expression<Func<TEntity, TEntity>> projection = null)
{
IQueryable<TEntity> q = _context.Set<TEntity>();
if (baseQuery != null)
{
q = baseQuery(_context.Set<TEntity>());
}
q = q.Where(predicate);
if (projection != null)
{
q = q.Select(projection);
}
return await q.FirstOrDefaultAsync();
}
I try to get the business entity and also include the image.inverseParents but with no success:
Expression<Func<Businesses, bool>> predicate = x => x.Id == id;
Expression<Func<Businesses, Businesses>> projection = x => new Businesses
{
BusinessImages = x.BusinessImages
.Where(bi => bi.Status == (int)EnumGringo.LU_Status.active && bi.Image.Status == (int)EnumGringo.LU_Status.active)
.Select(bi => new BusinessImages
{
//Won't complie
Image = bi.Image.Include(i=>i.InverseParent)
}).ToList()
};
Businesses business = await _repository.GetByCondition(predicate, projection: projection);
How can I do something like this to work?
Image = bi.Image.Include(i=>i.InverseParent)
Things I tried:
Image = bi.Image.Select(i=>i.InverseParent.Parent).FirstOrDefault()
Adding
IQueryable<Businesses> baseQuery(DbSet<Businesses> x) => x
.Include(c => c.BusinessImages)
.ThenInclude(c => c.Image)
.ThenInclude(c => c.InverseParent);
Businesses business = await _repository.GetByCondition(predicate, projection: projection);
The base repository ignores the basequery.
Projection queries ignores Include, once you go with Select you need to continue with select all the way.
This works for me:
BusinessImages = x.BusinessImages
.Where(bi => bi.Status == (int)EnumGringo.LU_Status.active && bi.Image.Status == (int)EnumGringo.LU_Status.active)
.Select(bi => new BusinessImages
{
Image = new UserImages
{
InverseParent = bi.Image.InverseParent
}
})

Getting error in LINQ query to sqlite

I'm trying to make login query in sqlite but can't convert AsyncTableQuery in boolean
public bool queryLogIn(string userNameLogIn,string passwordLogIn)
{
var query=database.Table<Users>().Where(i => i.UserName == userNameLogIn && i.Password == passwordLogIn);
if (query == true)//There is error
{
return true;
}
else
{
return false;
}
}
You can use FirstOrDefault function for login is success.
public bool queryLogIn(string userNameLogIn,string passwordLogIn)
{
var query=database.Table<Users>().FirstOrDefault(i => i.UserName == userNameLogIn && i.Password == passwordLogIn);
if (query == null)//There is error
{
return false;
}
else
{
return true;
}
}
Where returns an iqueryable of Users. You need Any instead which Determines whether any element of a sequence satisfies a condition and since it's return type is bool you can use it in your if statement:
var query=database.Table<Users>().Any(i => i.UserName == userNameLogIn
&& i.Password == passwordLogIn);
EDIT: Or use Any in your if:
var query=database.Table<Users>().Where(i => i.UserName == userNameLogIn
&& i.Password == passwordLogIn);
if (query.Any())
{
return true;
}

Dynamic Linq Contains to filter a List

I have this Method
public static IEnumerable<T> Filter<T>(IEnumerable<T> source, string searchStr)
{
var propsToCheck = typeof(T).GetProperties().Where(a => a.PropertyType == typeof(string));
var filter = propsToCheck.Aggregate(string.Empty, (s, p) => (s == string.Empty ? string.Empty : string.Format("{0} OR ", s)) + string.Format("{0} == #0", p.Name).ToLower());
var filtered = source.AsQueryable().Where(filter, searchStr);
return filtered;
}
Which takes List and a search string and finds any matches in the list. However this only works for 100% matches, how can I make this case insensitive and use contains instead of a 100% match ?
Constructing a dynamic LINQ query doesn't look like the best option here. Filtering with a delegate would do better:
public static IEnumerable<T> Filter<T>(IEnumerable<T> source, string searchStr)
{
var searchStrLower = searchStr.ToLower();
var propsToCheck = typeof(T).GetProperties().Where(a => a.PropertyType == typeof(string) && a.CanRead);
return source.Where(obj => {
foreach (PropertyInfo prop in propsToCheck)
{
string value = (string)prop.GetValue(obj);
if (value != null && value.ToLower().Contains(searchStrLower)) return true;
}
return false;
});
}

Dynamic EF Where Clause raising ArgumentNullException

I'm trying to code a method that, in it's class given the values of some of the attributes, returns a filtered DbSet. The code, so far, is:
public IEnumerable<Pesquisa> Pesquisas {
get {
PrometheusDBContext db = new PrometheusDBContext();
var temp = db.Pesquisas;
if ((this.Filtro.Nome != null) && (this.Filtro.Nome.Trim() != ""))
{
temp = (temp.Where(p => SqlFunctions.PatIndex(this.Filtro.Nome, p.Nome) > 0) as DbSet<Pesquisa>);
}
if ((this.Filtro.CodTipoPesquisa != null) && (this.Filtro.CodTipoPesquisa.Trim() != ""))
{
temp = (temp.Where(p => p.CodTipoPesquisa == this.Filtro.CodTipoPesquisa.Trim()) as DbSet<Pesquisa>);
}
if ((this.Filtro.IDStatusPesquisa != null) && (this.Filtro.IDStatusPesquisa > 0))
{
temp = (temp.Where(p => p.IDStatusPesquisa == this.Filtro.IDStatusPesquisa) as DbSet<Pesquisa>);
}
if ((this.Filtro.DataCriacao_Inicial != null) && (this.Filtro.DataCriacao_Final != null))
{
temp = (temp.Where(p => (p.DataCriacao >= this.Filtro.DataCriacao_Inicial) && (p.DataCriacao <= this.Filtro.DataCriacao_Final)) as DbSet<Pesquisa>);
}
else
{
if (this.Filtro.DataCriacao_Inicial != null)
{
temp = (temp.Where(p => p.DataCriacao >= this.Filtro.DataCriacao_Inicial) as DbSet<Pesquisa>);
}
if (this.Filtro.DataCriacao_Final != null)
{
temp = (temp.Where(p => p.DataCriacao <= this.Filtro.DataCriacao_Final) as DbSet<Pesquisa>);
}
}
return temp
.Include(p => p.Usuario)
.Include(p => p.StatusPesquisa)
.Include(p => p.TipoPesquisa)
.Include(p => p.ModeloTermoAdesao)
.Include(p => p.Pacientes)
.ToList();
}
Problem is: everytime one of the attributes is filled with some value (i.e.: this.Filtro.Nome = "test" ), the ToList() raises an ArgumentNullExcpetion. Any ideas?
You shouldn't cast to DbSet at the end of each line.
Also, declare
IQueryable<Pesquisa> temp = db.Pesuisas;
// your code follows.
The reason behind it is that although you start with a DbSet, applying operators changes its type. Your dynamic cast returns null then.

Categories

Resources