override GetAll to change Sort - example required - aspnetboilerplate

I am trying to override an AsyncCrudAppService call - "GetAll" in order to change the returned sort order.
Firstly, is overriding the method, in fact, the correct way to do this?
If yes, could I have an example as I'm a bit lost on how to do this with a PagedResultDto<T> return type?
If not please let me know where I can find out how.

Technically you can, but really shouldn't do sorting in GetAll() if it can be avoided. CRUD App Services also define virtual method ApplySorting() which is already used by GetAll and can also be overridden.
You can pass the string to dynamically sort by when calling the GetAll() and it will already work.
await _myAppService.GetAll(new PagedAndSortedResultRequestDto() { Sorting = "Name DESC", MaxResultCount = pageSize, SkipCount = skipCount })
If you'd like to have more control over sorting behavior, like use something default or pass in better-formatted query strings, override ApplySorting()
protected override IQueryable<SomeEntity> ApplySorting(IQueryable<SomeEntity> query, PagedAndSortedResultRequestDto input)
{
var sortBy = "LastModificationTime DESC,CreationTime DESC";
switch (input.Sorting?.ToLower())
{
case "name-asc":
sortBy = "Name";
break;
case "name-desc":
sortBy = "Name DESC";
break;
}
input.Sorting = sortBy;
return base.ApplySorting(query, input);
}

Related

How to always filter a collection on specific field value in api-platform GET operations?

In the GET operations, I'd like to exclude from the returning collections my entities that have an " archive" field that equals " true ".
I'd like that to be the default for my endpoints like /users or /companies and i want to avoid to add an URL filter by hand like /users?filter[archive]=true
what would be the best way to do that ?
Thanks for any help :)
I had to do something like that, and I solved it by applying a DoctrineExtension to a Collection that will add the WHERE clause to the QueryBuilder.
How?
Define your Filter, I see you already have that.
Add the Doctrine Extension to by applied only to Collections, or if you need, also to an Item: https://api-platform.com/docs/core/extensions/#custom-doctrine-orm-extension
services:
App\Doctrine\Extension\ArchivedExtension:
tags:
- { name: api_platform.doctrine.orm.query_extension.collection }
Code your Extension.
You could check if you are receiving a certain "filter" (your "filter[archive]=true" query parameter): if YES, dont apply the condition to the QueryBuilder, your Filter will be applied by the filtering mechanism of ApiPlatform.
Your extension class should looked something like this:
class ArchivedExtension implements QueryCollectionExtensionInterface
{
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
$this->addWhere($queryBuilder, $resourceClass, $context);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass, array $context = [])
{
if (MyResource::class !== $resourceClass) {
return;
}
// Search if a "archive" Filter is being requested, if not, apply a restriction to the QueryBuilder
if (array_key_exists('archive', $context['filters'])) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere(sprintf('%s.archive = :archive', $rootAlias));
$queryBuilder->setParameter('archive', true);
}
}

MvcContrib Grid Sorting on complex object

I am trying to work with MvcContrib Grid control. But I cannot seem to get the sorting to work on complex objects that hold other objects.
I have setup my controller/classes/Views similar to the OP in this question.
Sorting with MVCContrib
I have tried to use the SortColumnName to my childobject.property but it gives me an error saying My main object does not have this property. This is my code snippet
//POCO class
class Issue {
public int ID {get; get; }
.....
public int priorityId {get; set;}
public virtual Priority priority {get; set;}
}
//Controller code
public ViewResult Index(int? pageNo, GridSortOptions sort)
{
var issues = db.issues.Include(i => i.priority);
ViewBag.sort = sort;
if (!string.IsNullOrEmpty(sort.Column))
{
issues = issues.OrderBy(sort.Column, sort.Direction);
}
return View(issues.ToList().AsPagination(pageNo ?? 1, 10));
}
//View code for the Grid
#Html.Grid(Model).Sort(ViewBag.sort as GridSortOptions).Columns(column => {
column.For(issue => Html.ActionLink(" ", "Edit", new { id = issue.ID, areas = "Issues", controller = "Main"}, new { #id="editBtn"})).Named("Edit");
column.For(issue => Html.ActionLink(issue.ID.ToString(), "Edit", new {id = issue.ID, areas = "Issues", controller = "Main"})).Named("ID").Sortable(true);
column.For(issue => issue.priority.codeDesc).Named("Priority").SortColumnName("priority.codeDesc").Sortable(true);
}).Empty("No data found")
When I try to sort on the priority string, it gives me an error saying 'priority.codeDesc is not a property of Issue'.
TIA
The issue here isn't actually related to the grid, but rather to the .OrderBy extension method provided as part of the MvcContrib sorting extensions. This extension is fairly simplistic and I only wrote it to cover simple cases where you want to sort on a direct property of the object, however in your case you're trying to order on a nested property ("priority.codeDesc") which isn't supported - you can't use dot notation with this extension.
You'd either need to switch to using a different mechanism to perform the actual sorting, or if this is a one-off situation then you could hard-code the sorting logic for this particular column (not ideal, but if it's a one off then it's simpler than writing a new sorting mechanism), eg:
if (!string.IsNullOrEmpty(sort.Column))
{
if(sort.Column == "priority.codeDesc")
{
issues = issues.OrderBy(x => x.priority.codeDesc);
}
else
{
issues = issues.OrderBy(sort.Column, sort.Direction);
}
}
OMG! Dots!
I was in the same boat but thanks God I found a brilliant solution posted by our fellow developer Jarrett Meyer. I found it after maybe 3 hours Googling in the past and just now when I decided to boost my pagination and sorting with MvcContrib Grid.
You can find the full post here:
Server-Side Sorting With Dynamic LINQ
His code saved me... :D The use of LINQ's Aggregate function was AWESOME! Kudozzz to him.
I had to change Jarretts' original code a little bit to fit it to my needs. Here's the code after I modified it:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, GridSortOptions sortOptions)
{
if (string.IsNullOrEmpty(sortOptions.Column))
{
return collection;
}
Type collectionType = typeof(T);
ParameterExpression parameterExpression = Expression.Parameter(collectionType, "p");
Expression seedExpression = parameterExpression;
Expression aggregateExpression = sortOptions.Column.Split('.').Aggregate(seedExpression, Expression.Property);
MemberExpression memberExpression = aggregateExpression as MemberExpression;
if (memberExpression == null)
{
throw new NullReferenceException(string.Format("Unable to cast Member Expression for given path: {0}.", sortOptions.Column));
}
LambdaExpression orderByExp = Expression.Lambda(memberExpression, parameterExpression);
const string orderBy = "OrderBy";
const string orderByDesc = "OrderByDescending";
Type childPropertyType = ((PropertyInfo)(memberExpression.Member)).PropertyType;
string methodToInvoke = sortOptions.Direction == MvcContrib.Sorting.SortDirection.Ascending ? orderBy : orderByDesc;
var orderByCall = Expression.Call(typeof(Queryable), methodToInvoke, new[] { collectionType, childPropertyType }, collection.Expression, Expression.Quote(orderByExp));
return collection.Provider.CreateQuery<T>(orderByCall);
}
Now you can call this extension method like this in your controller method:
var users = Database.Memberships.OrderBy(sort);
where sort is GridSortOptions that lives in MvcContrib.UI.Grid.
sort.ColumnName can contain strings like these ones now:
User.UserName
User.MyRelatedEntity.RelatedEntityProperty
User.MyRelatedEntity.RelatedEntityProperty.AndSoON
Note that when you create your Grid columns you can specify
.SortColumnName("User.UserName")

How can I create an Expression within another Expression?

Forgive me if this has been asked already. I've only just started using LINQ. I have the following Expression:
public static Expression<Func<TblCustomer, CustomerSummary>> SelectToSummary()
{
return m => (new CustomerSummary()
{
ID = m.ID,
CustomerName = m.CustomerName,
LastSalesContact = // This is a Person entity, no idea how to create it
});
}
I want to be able to populate LastSalesContact, which is a Person entity.
The details that I wish to populate come from m.LatestPerson, so how can I map over the fields from m.LatestPerson to LastSalesContact. I want the mapping to be re-useable, i.e. I do not want to do this:
LastSalesContact = new Person()
{
// Etc
}
Can I use a static Expression, such as this:
public static Expression<Func<TblUser, User>> SelectToUser()
{
return x => (new User()
{
// Populate
});
}
UPDATE:
This is what I need to do:
return m => (new CustomerSummary()
{
ID = m.ID,
CustomerName = m.CustomerName,
LastSalesContact = new Person()
{
PersonId = m.LatestPerson.PersonId,
PersonName = m.LatestPerson.PersonName,
Company = new Company()
{
CompanyId = m.LatestPerson.Company.CompanyId,
etc
}
}
});
But I will be re-using the Person() creation in about 10-15 different classes, so I don't want exactly the same code duplicated X amount of times. I'd probably also want to do the same for Company.
Can't you just use automapper for that?
public static Expression<Func<TblCustomer, CustomerSummary>> SelectToSummary()
{
return m => Mapper.Map<TblCustomer, CustommerSummary>(m);
}
You'd have to do some bootstrapping, but then it's very reusable.
UPDATE:
I may not be getting something, but what it the purpose of this function? If you just want to map one or collection of Tbl object to other objects, why have the expression?
You could just have something like this:
var customers = _customerRepository.GetAll(); // returns IEnumerable<TblCustomer>
var summaries = Mapper.Map<IEnumerable<TblCustomer>, IEnumerable<CustomerSummary>>(customers);
Or is there something I missed?
I don't think you'll be able to use a lambda expression to do this... you'll need to build up the expression tree by hand using the factory methods in Expression. It's unlikely to be pleasant, to be honest.
My generally preferred way of working out how to build up expression trees is to start with a simple example of what you want to do written as a lambda expression, and then decompile it. That should show you how the expression tree is built - although the C# compiler gets to use the metadata associated with properties more easily than we can (we have to use Type.GetProperty).
This is always assuming I've understood you correctly... it's quite possible that I haven't.
How about this:
public static Person CreatePerson(TblPerson data)
{
// ...
}
public static Expression<Func<TblPerson, Person>> CreatePersonExpression()
{
return d => CreatePerson(d);
}
return m => (new CustomerSummary()
{
ID = m.ID,
CustomerName = m.CustomerName,
LastSalesContact = CreatePerson(m.LatestPerson)
});

How to access data into IQueryable?

I have IQueryable object and I need to take the data inside the IQueryable to put it into Textboxs controls. Is this possible?
I try something like:
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.????
}
Update:
I'm doing this:
public IQueryable getData(String tableName, Hashtable myparams)
{
decimal id = 0;
if (myparams.ContainsKey("id") == true)
id = (decimal)myparams["id"];
Type myType= Type.GetType("ORM_Linq." + tableName + ", ORM_Linq");
return this.GetTable(tableName , "select * from Articu where id_tipo_p = '" + id + "'");
}
public IQueryable<T> GetTable<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class
{
return _datacontext.GetTable<T>().Where(predicate);
}
This returns a {System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable1[ORM_Linq.Articu]}`
I don't see any method like you tell me. I see Cast<>, Expression, ToString...
EDIT: Updated based on additional info from your other posts...
Your getData method is returning IQueryable instead of a strongly typed result, which is why you end up casting it. Try changing it to:
public IQueryable<ORM_Linq.Articu> getData(...)
Are you trying to query for "Articu" from different tables?
With the above change in place, your code can be rewritten as follows:
ORM_Linq.Articu result = mydata.SingleOrDefault();
if (result != null)
{
TextBoxCode.Text = result.id.ToString();
TextBoxName.Text = result.descrip;
}
If you have a single result use SingleOrDefault which will return a default value if no results are returned:
var result = mydata.SingleOrDefault();
if (result != null)
{
textbox1.text = result.ProductName; // use the column name
}
else
{
// do something
}
If you have multiple results then loop over them:
foreach (var item in mydata)
{
string name = item.ProductName;
int id = item.ProductId;
// etc..
}
First, you should be using a strongly-typed version of IQueryable. Say that your objects are of type MyObject and that MyObject has a property called Name of type string. Then, first change the parameter mydata to be of type IQueryable<MyObject>:
public void setdata (IQueryable<MyObject> mydata)
Then we can write a body like so to actually get some data out of. Let's say that we just want the first result from the query:
public void setdata (IQueryable<MyObject> mydata) {
MyObject first = mydata.FirstOrDefault();
if(first != null) {
textbox1.Text = first.Name;
}
}
Or, if you want to concatenate all the names:
public void setdata(IQueryable<MyObject> mydata) {
string text = String.Join(", ", mydata.Select(x => x.Name).ToArray());
textbo1.Text = text;
}
Well, as the name suggests, an object implementing IQueryable is... Queryable! You'll need to write a linq query to get at the internal details of your IQueryable object. In your linq query you'll be able to pull out its data and assign bits of it where ever you'd like - like your text box.
Here's a great starting place for learning Linq.
I think you find the same mental struggle when coming from FoxPro and from DataSet. Really nice, powerful string-based capabilities(sql for query, access to tables and columns name) in these worlds are not available, but replaced with a compiled, strongly-typed set of capabilities.
This is very nice if you are statically defining the UI for search and results display against a data source known at compile time. Not so nice if you are trying to build a system which attaches to existing data sources known only at runtime and defined by configuration data.
If you expect only one value just call FirstOrDefault() method.
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.FirstOrDefault().PropertyName;
}

linq and object initialisation

If I have something like:
var query = from children in _data.Children
where children.ChildId == childId
select new CustomModel.MyChild
{
ChildId = children.ChildId,
Name = children.ChildName
};
return query.FirstOrDefault();
Where I want the resultant object to be my custom model.
Can I handle the custom model instantiation in a different method, which could be reused if I had multiple linq queries that all generated a custom child model?
For example,
var query = from children in _data.Children
where children.ChildId == childId
select CreateMyCustomChild([param ??]);
return query.FirstOrDefault();
This may well be impossible, I don't know, but what would the method signature be like if it is possible?
I'm only thinking reuse for when multiple linq queries contain duplicate object initialisation code.
Thanks
It really depends on what version of LINQ you're using. If you're using LINQ to SQL, I don't believe you can call arbitrary methods in the query. The query translator wouldn't know what to do with the method call
If you're using LINQ to Objects, you're absolutely fine to do it, like this:
var query = from children in _data.Children
where children.ChildId == childId
select CreateMyCustomChild(children)
return query.FirstOrDefault();
// Elsewhere
public CustomModel.MyChild CreateMyCustomChild(OtherChild child)
{
return new CustomModel.MyChild
{
ChildId = child.ChildId,
Name = child.ChildName
};
}
(Side note: I'd call the range variable in the query "child" rather than "children" as at any one time it only represents a single child.)
If you wanted you could write "select 1" or in your case "CreateMyCustomChild(children)" since "children" is containing all your info. In your case you aren't adding a lot of info to "children", so why not "select children"?
In other words, just try it out. The return type of your value will determine over which type your LINQ enumerates.
Suppose you had a method that did the transform for you.
public static class Conversions
{
public static CustomModel.MyChild ToCustomModel(this DataModel.MyChild source)
{
return new CustomModel.MyChild()
{
ChildId = source.ChildId,
Name = source.ChildName
}
}
}
You can use such a method to do the conversion of a single item.
DataModel.MyChild myResult = getResult();
CustomModel.MyChild myConvertedResult = myResult.ToCustomModel()
Such a method can also be used in a Enumerable.Select method call.
IEnumerable<DataModel.MyChild> myQueriedResults = getResult();
IEnumerable<CustomModel.MyChild> myConvertedResults =
myQueryiedResults.Select(c => c.ToCustomModel());
While you can do with expressions, I don't think it is worth the hassle. Instead I suggest you define an extension method like:
IQueryable<CustomModel.MyChild> ToModel(this IQueryable<Child> childs)
{
return childs.Select( c=>
select new CustomModel.MyChild
{
ChildId = children.ChildId,
Name = children.ChildName
}
);
}
You can then call:
return _data.Children
.Where(c=>c.ChildId == childId)
.ToModel()
.FirstOrDefault();

Resources