build linq query based off parameters - c#

I'm trying to create a query based on parameters from my datatable. I'm trying to perform something like the following except I keep getting errors and having to basically recreate the entire linq query in each case statement with one parameter different ie. OrderBy or OrderByDecending.
Is there a way to build the query based on the parameters?
public JsonResult IndexData(DTParameterViewModel param)
{
IEnumerable<DataTableRowViewModel> rows = (from j in jobs select j)
.Skip(param.Start)
.Take(param.Length)
.AsEnumerable()
.Select(j => new DataTableRowViewModel
{
Id = j.Id,
ArrivalDate = j.ArrivalDate.ToString(Constants.DATE_FORMAT),
DueDate = j.DueDate?.ToString(Constants.DATE_TIME_FORMAT),
Contact = j.Contact,
Priority = j.Priority.GetDisplayName(),
JobType = j.JobType.GetDisplayName(),
SapDate = j.SapDate.ToString()
});
foreach(DTOrder order in param.Order)
{
ascDesc = order.Dir;
switch (order.Column)
{
case 0:
orderCol = 0;
colName = "Id";
if (ascDesc == "desc")
{
rows = (from j in jobs select j)
.OrderByDescending(j => j.Id);
}
else
{
rows = (from j in jobs select j)
.OrderBy(j => j.Id)
}
break;
case 1:
orderCol = 1;
colName = "ArrivalDate";
if (ascDesc == "desc")
{
rows = (from j in jobs select j)
.OrderByDescending(j => j.ArrivalDate)
}
else
{
rows = (from j in jobs select j)
.OrderBy(j => j.ArrivalDate)
}
break;
}

There are more complex ways of doing it, but I find this the easiest to read, especially for LINQ beginners:
var first=true;
foreach(DTOrder order in param.Order)
{
switch(order.Column)
{
case 0:
if (first)
{
rows=(order.Dir=="asc")?rows.OrderBy(r=>r.Id):rows.OrderByDescending(r=>r.Id);
} else {
rows=(order.Dir=="asc")?rows.ThenBy(r=>r.Id):rows.ThenByDescending(r=>r.Id);
}
break;
case 1:
...
}
first=false;
}
Normally however, you would want to do your Take and Skip after the order, so you would do something like this:
public JsonResult IndexData(DTParameterViewModel param)
{
// Start with the base query
var rows = jobs.AsQueryable();
// Order the query
var first=true;
foreach(DTOrder order in param.Order)
{
switch(order.Column)
{
case 0:
if (first)
{
rows=(order.Dir=="asc")?rows.OrderBy(r=>r.Id):rows.OrderByDescending(r=>r.Id);
} else {
rows=(order.Dir=="asc")?rows.ThenBy(r=>r.Id):rows.ThenByDescending(r=>r.Id);
}
break;
case 1:
...
}
first=false;
}
// Partition the query
rows=rows
.Skip(param.Start)
.Take(param.Length)
.AsEnumerable(); // Or place the AsEnumerable in the projection
// Project the query
var result=rows.Select(j => new DataTableRowViewModel
{
Id = j.Id,
ArrivalDate = j.ArrivalDate.ToString(Constants.DATE_FORMAT),
DueDate = j.DueDate?.ToString(Constants.DATE_TIME_FORMAT),
Contact = j.Contact,
Priority = j.Priority.GetDisplayName(),
JobType = j.JobType.GetDisplayName(),
SapDate = j.SapDate.ToString()
});
return result;
}
Further optimizing (assuming that jobs comes from a database), then you should do an intermediate projection to limit the columns that come back, changing:
// Partition the query
rows=rows
.Skip(param.Start)
.Take(param.Length)
.AsEnumerable(); // Or place the AsEnumerable in the projection
// Project the query
var result=rows.Select(j => new DataTableRowViewModel
to this:
// Partition the query
var result1=rows
.Skip(param.Start)
.Take(param.Length)
/* Selecting only the fields we need */
.Select(j=> new {
j.Id,
j.ArrivalDate,
j.DueDate,
j.Contact,
j.Priority,
j.JobType,
j.SapDate
})
.AsEnumerable(); // Or place the AsEnumerable in the projection
// Project the query
var result=result1.Select(j => new DataTableRowViewModel

Related

loop through sql results and delete the single rows

Im trying to delete persons from a list through a for loop. My problem is now that im trying to find each id to delete the specific row of my database but i dont know how i can do it with a for loop.
My Code right now:
[HttpPost("delUebertrag/")]
public async Task<ActionResult<ProzessPersonenzuordnungen>> delRecUebertrag([FromBody] recUebertragModel user)
{
ProzessPersonenzuordnungen ppz = new();
for (var i = 0; i <= user.personList.Length; i++)
{
Guid personId = new Guid(user.personList[i]);
ppz.ProzessId = new Guid(user.prozessId);
var prozessPersonenzuordnungen = _context.ProzessPersonenzuordnungens.Where(p => p.ProzessId == ppz.ProzessId && p.PersonId == personId);
if (prozessPersonenzuordnungen == null)
{
return NotFound();
}
//Everythings works fine above, prozessPersonenzuordnungen haves for Example 2 results
for(var j = 0; j < prozessPersonenzuordnungen.Count(); j++) // i dont know if Counts is fine, looking for something like length of the results
{
var toDeletingRow = await _context.ProzessPersonenzuordnungens.FindAsync(prozessPersonenzuordnungen.Select(p => p.ProzessPersonenzuordnungId)); // Here i need to go through every singleId of my results, something like p.ProzessPersonenzuordnungId[j](does not work)
_context.ProzessPersonenzuordnungens.Remove(toDeletingRow);
await _context.SaveChangesAsync();
}
}
return Ok();
}
The FindAsync can be only used for single item. Instead do
var rowsToDelete = _context.ProzessPersonenzuordnungens.Where(x=> prozessPersonenzuordnungen.Select(p => p.ProzessPersonenzuordnungId).Contains(x.Id));
And instead of Remove, use
_context.ProzessPersonenzuordnungens.RemoveRange(rowsToDelete);
And one important thing, accessing database in a loop is a very bad practice.
Try to do it without the loop.
Edit:
Try this
ProzessPersonenzuordnungen ppz = new ProzessPersonenzuordnungen();
for (var i = 0; i <= user.personList.Length; i++)
{
Guid personId = new Guid(user.personList[i]);
ppz.ProzessId = new Guid(user.prozessId);
var prozessPersonenzuordnungen = _context.ProzessPersonenzuordnungens.Where(p => p.ProzessId == ppz.ProzessId && p.PersonId == personId)
.Select((x)=> x.ProzessPersonenzuordnungId);
if (prozessPersonenzuordnungen == null)
{
return NotFound();
}
var rowsToDelete = _context.ProzessPersonenzuordnungens.Where(x => prozessPersonenzuordnungen.Contains(x.Id));
_context.ProzessPersonenzuordnungens.RemoveRange(rowsToDelete);
await _context.SaveChangesAsync();
return Ok();
}

how to sort on a field not stored within the db

I have an asp.net mvc site and I'm unable to sort on a field that is calculated when needed in the model.
private decimal _total = -1;
public decimal Total
{
get
{
if (_total < 0)
{
_total = get_total(TableId);
}
return _total;
}
}
private decimal get_total(int id)
{
....Many Calcs
}
I'm trying to sort on Total, but I get the error:
Additional information: The specified type member 'Total' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Here is my actionlink:
#Html.ActionLink("By Total", "Index", new { sortOrder = ViewBag.Total, currentFilter = ViewBag.CurrentFilter }, new { #class = "btn btn-danger" })
I have found some similar issues, but I just can't figure out what how to sort by this.
And my controller. I tried to edit this down for clarity.
public ActionResult Index(string sortOrder)
{
ViewBag.CurrentSort = sortOrder;
ViewBag.Total = sortOrder == "total" ? "total_desc" : "total";
var records = from u in db.Records.Include(t => t.User).Where(t => t.Active == true)
select u;
switch (sortOrder)
{
case "total":
records = db.Records.OrderBy(u => u.Total).Where(t => t.Active == true);
break;
case "rating_desc":
records = db.Records.OrderByDescending(u => u.Total).Where(t => t.Active == true);
break;
default:
records = db.Records.OrderBy(u => u.Title).Where(t => t.Active == true);
break;
}
return View(records.ToList());
}
Try to call ToList() method before trying to order by this property as this cannot be translated to an SQL statement.
// I assume currently your query is something like this
DbContext.SomeEntity.Where(...).OrderBy(e => e.Total);
// After calling .ToList() you can sort your data in the memory (instead of in db)
DbContext.SomeEntity.Where(...).ToList().OrderBy(e => e.Total);
UPDATE:
The problem is that first you declare the records variable with this line:
var records = from u in db.Records.Include(t => t.User).Where(t => t.Active == true) select u;
Because of this the type of the records variable will be System.Linq.IQueryable<Project.Models.Record> and that's why in the switch case you "needed" to cast with .AsQueryable().
Additionally the initial value will be always overridden in the switch statement therefore it is totally unnecessary to initialize it as you do it currently.
What you should do:
public ActionResult Index(string sortOrder)
{
/* ViewBag things */
IEnumerable<Record> records =
db
.Records
.Include(record => record.User)
.Where(record => record.Active)
.ToList(); // At this point read data from db into memory
// Total property cannot be translated into an SQL statement.
// That's why we call it on memory objects instead of DB entities.
switch (sortOrder)
{
case "total":
records = records.OrderBy(record => record.Total);
break;
case "rating_desc":
records = records.OrderByDescending(record => record.Total);
break;
default:
records = records.OrderBy(record => record.Title);
break;
}
return View(records.ToList());
}
I needed to cast my query as IQueryable, so here is the updated switch:
switch (sortOrder)
{
case "total":
records = db.Records.Where(t => t.Active == true).AsQueryable().OrderBy(u => u.Total));
break;
case "rating_desc":
records = db.Records.Where(t => t.Active == true).AsQueryable.OrderByDescending(u => u.Total).;
break;
default:
records = db.Records.Where(t => t.Active == true).AsQueryable.OrderBy(u => u.Title).;
break;
}

jquery datatables server side filtering causes EF to timeout?

I have the following method which filters 2 million records but most of the times if i want to get the last page it causes entity framework to timeout is there any way I could improve the following code so that it can run faster.
public virtual ActionResult GetData(DataTablesParamsModel param)
{
try
{
int totalRowCount = 0;
// Generate Data
var allRecords = _echoMediaRepository.GetMediaList();
//Apply search criteria to data
var predicate = PredicateBuilder.True<MediaChannelModel>();
if (!String.IsNullOrEmpty(param.sSearch))
{
var wherePredicate = PredicateBuilder.False<MediaChannelModel>();
int i;
if (int.TryParse(param.sSearch, out i))
{
wherePredicate = wherePredicate.Or(m => m.ID == i);
}
wherePredicate = wherePredicate.Or(m => m.Name.Contains(param.sSearch));
predicate = predicate.And(wherePredicate);
}
if (param.iMediaGroupID > 0)
{
var wherePredicate = PredicateBuilder.False<MediaChannelModel>();
var mediaTypes = new NeptuneRepository<Lookup_MediaTypes>();
var mediaGroups = mediaTypes.FindWhere(m => m.MediaGroupID == param.iMediaGroupID)
.Select(m => m.Name)
.ToArray();
wherePredicate = wherePredicate.Or(m => mediaGroups.Contains(m.NeptuneMediaType) || mediaGroups.Contains(m.MediaType));
predicate = predicate.And(wherePredicate);
}
var filteredRecord = allRecords.Where(predicate);
var columnCriteria = param.sColumns.Split(',').ToList();
if (!String.IsNullOrEmpty(columnCriteria[param.iSortCol_0]))
{
filteredRecord = filteredRecord.ApplyOrder(
columnCriteria[param.iSortCol_0],
param.sSortDir_0 == "asc" ? QuerySortOrder.OrderBy : QuerySortOrder.OrderByDescending);
}
totalRowCount = filteredRecord.Count();
var finalQuery = filteredRecord.Skip(param.iDisplayStart).Take(param.iDisplayLength).ToList();
// Create response
return Json(new
{
sEcho = param.sEcho,
aaData = finalQuery,
iTotalRecords = allRecords.Count(),
iTotalDisplayRecords = totalRowCount
}, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Logger.Error(ex);
throw;
}
}
Your code and queries look optimized, so the problem should be the lack of indexes in the database that degrade the performance of your orderby (used by the skip).
Using a test code very similar to yours, I've done some tests in a local test DB with a table with 5 Million rows (with XML Type columns all filled) and, as expected, using queries ordered by indexes was really fast but, by unindexed columns, they could take very, very, long time.
I recommend you to analyse the most common used columns for the dynamic Where and Order functions and do some performance tests by creating the corresponding indexes.

MVCCrud Using LinqToEntities

There is a sample application called MVCCrud. This example is quite good and I would like to use it as the framework on a project that I am working on.
The problem is that MVCCrud uses LingToSQL and I would like to use LinqToEntities. I got most everything to work correctly once I converted over to LinqToEntities except one place.
In the following code on the lines i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
cell = getCells(p)
it gives a Linq to Entities does not recognize GetValue.
Can someone help me refactor the following code?
items = items.OrderBy(string.Format("{0} {1}", sidx, sord)).Skip(pageIndex * pageSize).Take(pageSize).AsQueryable();
// Generate JSON
var jsonData =
new
{
total = totalPages,
page,
records = totalRecords,
rows = items.Select(
p => new
{
// id column from repository
i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
cell = getCells(p)
}).ToArray()
};
return Json(jsonData);
and here is the getCell method:
private string[] getCells(TModel p)
{
List<string> result = new List<string>();
string a = actionCell(p);
if (a != null)
{
result.Add(a);
}
foreach (string column in data_rows.Select(r => r.value))
{
try
{
// hack for tblcategory.name
string[] parts = column.Split('.');
// Set first part
PropertyInfo c = typeof(TModel).GetProperty(parts[0]);
object tmp = c.GetValue(p, null);
// loop through if there is more than one depth to the . eg tblCategory.name
for (int j = 1; j < parts.Length; j++)
{
c = tmp.GetType().GetProperty(parts[j]);
tmp = c.GetValue(tmp, null);
}
if (tmp.GetType() == typeof(DateTime))
{
result.Add(((DateTime)tmp).ToString(dateTimeFormat));
}
else if (tmp.GetType() == typeof(float))
{
result.Add(((float)tmp).ToString(decimalFormat));
}
else if (tmp.GetType() == typeof(double))
{
result.Add(((double)tmp).ToString(decimalFormat));
}
else if (tmp.GetType() == typeof(decimal))
{
result.Add(((decimal)tmp).ToString(decimalFormat));
}
else
{
result.Add(tmp.ToString());
}
}
catch (Exception)
{
result.Add(string.Empty);
}
}
return result.ToArray();
}
Do this ToList() instead of AsQueryable():
items = items.OrderBy(string.Format("{0} {1}", sidx, sord)).Skip(pageIndex * pageSize).Take(pageSize).ToList();
You can't execute any external method "within" linq query.
And may you say that was working in Linq2Sql then you should know when you call any external method "Like ToString()" Linq2Sql will fetch all data from database then handle your query in the memory and that maybe a serious harming if you have a lot of records.
For more information look at this

Total results of paged projected nhibernate query with aggregates

I'm dynamically building a nhibernate projected query that needs to implement paging. Something like...
var projections = Projections.ProjectionList();
foreach (var p in projection.Projections)
{
IProjection newProjection = null;
switch (p.AggregateFunc)
{
case AggregateFuncTypeEnum.GroupProperty:
newProjection = Projections.GroupProperty(p.Path);
break;
case AggregateFuncTypeEnum.Sum:
newProjection = Projections.Sum(p.Path);
break;
default:
newProjection = Projections.Property(p.Path);
break;
}
projections.Add(newProjection, p.Name);
}
criteria.SetProjection(projections).SetResultTransformer(new AliasToBeanResultTransformer(projectionType));
I can get the first 15 results like so
criteria.SetFirstResult(0);
criteria.SetMaxResults(15);
var results = criteria.List();
But I also need to send another query to get the total number of records but so far I've failed to figure this out. The projection still needs to be applied i.e. if the results are grouped by 'code' with a sum of 'cost' then 100 records might return 20 rows, and it's the 20 I'm interested in.
How do I get the total number of records that will be returned? Thanks
maybe this:
var rowcount = CriteriaTransformer.Clone(criteria);
var goupprojections = Projections.ProjectionList();
var projections = Projections.ProjectionList();
foreach (var p in projection.Projections)
{
IProjection newProjection = null;
switch (p.AggregateFunc)
{
case AggregateFuncTypeEnum.GroupProperty:
newProjection = Projections.GroupProperty(p.Path);
goupprojections.Add(Projections.GroupProperty(p.Path), p.Name);
break;
case AggregateFuncTypeEnum.Sum:
newProjection = Projections.Sum(p.Path);
break;
default:
newProjection = Projections.Property(p.Path);
break;
}
projections.Add(newProjection, p.Name);
}
criteria.SetProjection(projections).SetResultTransformer(new AliasToBeanResultTransformer(projectionType));
if (goupprojections.Aliases.Length == 0)
{
rowcount.SetProjection(Projections.RowCount())
}
else
{
rowcount.SetProjection(Projections.Count(goupprojections))
}
var results = criteria.Future();
var count = rowcount.FutureValue<int>();

Categories