C# Refactor code to make re-usable with generics - c#

This is a very loaded question but I am new to using generics and want to become proficient using them and taking my C#/.NET skills to the next level. I created a data filter using multiple dropdowns. Before I break the below method into smaller encapsulated, single responsibility methods, I was hoping I could get some help.
My goal is to re-use this filter in many parts of our application. In order to do so I will have to add the "filter" methods to our common base EntityController and EntityRespository classes. Obviously I am going to have to make use of generics to accomplish this task. Can anyone give me some advice after looking at the below (ridiculously long) method?
[HttpPost("FilteredResults")]
[ProducesResponseType(typeof(PaginatedList<ProcessCard>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesDefaultResponseType]
[Produces("application/json")]
public async Task<IActionResult> GetFilteredBusinessProcesses([FromBody] List<FieldFilterCriteria> filterCriteriaList = null)
{
try
{
List<BusinessProcess> listOfCommonItems = new List<BusinessProcess>();
PaginatedList<BusinessProcess> paginatedListOfCommonItems = new PaginatedList<BusinessProcess>();
var orgIdGuid = filterCriteriaList[0]?.OrganizationId;
if (filterCriteriaList != null)
{
var results = repository.Find(null, a => a.OrganizationId == orgIdGuid);
foreach (FieldFilterCriteria filter in filterCriteriaList)
{
foreach (string value in filter?.FilterValues)
{
switch (filter.Field)
{
case "Department":
var dList = results?.Items.Where(x => x.OrganizationUnitId == Guid.Parse(value)).ToList();
listOfCommonItems.AddRange(dList);
break;
case "Status":
var sList = results?.Items.Where(x => x.Status == value).ToList();
listOfCommonItems.AddRange(sList);
break;
case "Priority":
var pList = results?.Items.Where(x => x.Priority == value).ToList();
listOfCommonItems.AddRange(pList);
break;
case "Owner":
var oList = results?.Items.Where(x => x.OwnerName == value).ToList();
listOfCommonItems.AddRange(oList);
break;
}
}
}
if (filterCriteriaList.Count == 1)
{
paginatedListOfCommonItems.Items = listOfCommonItems;
}
if (filterCriteriaList.Count > 1)
{
listOfCommonItems = listOfCommonItems.GroupBy(x => x).Where(x => x.Count() > 1).Select(x => x.Key).ToList();
paginatedListOfCommonItems.Items = listOfCommonItems;
}
var businessProcessCard = processCatalogManager.GetBusinessProcessCard(paginatedListOfCommonItems);
return Ok(businessProcessCard);
}
else
{
return Ok();
}
}
catch (Exception ex)
{
return ex.GetActionResult();
}
}

Related

Efficiently select a set of objects that have multi value keys

I have a ProductVersions model which has a multi part key, a int ProductId and a int VersionNum field.
The function below takes a list of simple Dto classes that are just these 2 fields with goal of returning a set full ProductVersion objects from the database that match.
Below is a less than efficient solution. I am expecting the incoming list to only between 2 to 4 items so its not too bad but I'd like to do better.
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
List<ProductVersion>? productVersions = null;
if (productVersionDtos != null)
{
foreach (ProductVersionDto dto in productVersionDtos)
{
ProductVersion? productVersion = await myPortalDBContext.ProductVersions
.Where(pv => pv.ProductId == dto.ProductId && pv.VersionNum == dto.VersionNum)
.FirstOrDefaultAsync();
if (productVersion != null)
{
if (productVersions == null) productVersions = new List<ProductVersion>();
productVersions.Add(productVersion);
}
}
}
return productVersions;
}
I had consider something like this:
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
List<ProductVersion>? productVersions = null;
if (productVersionDtos != null)
{
productVersions = await myPortalDBContext.ProductVersions
.Join(productVersionDtos,
pv => new { pv.ProductId, pv.VersionNum },
pvd => new { pvd.ProductId, pvd.VersionNum },
(pv, pvd) => pv)
.ToListAsync();
}
return productVersions;
}
but at runtime fails because the Join doesn't make sense. Anyone know of way to do this more efficiently with just a single round-trip into the dbContext?
Use FilterByItems extension and then you can generate desired query:
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
if (productVersionDtos == null)
return null;
var productVersions = await myPortalDBContext.ProductVersions
.FilterByItems(productVersionDtos,
(pv, dto) => pv.ProductId == dto.ProductId && pv.VersionNum == dto.VersionNum, true)
.ToListAsync();
return productVersions;
}

Combination of two conditions in .cs code

Desc
I have a problem with the "where" instruction from the .cs code, I don't know how to use "and" there?
I tried
Code dont run, "and" dont exist
[HttpGet]
public ActionResult GetNumberDays(string mieciacValue, int rokValue)
{
var liczbaDni = _ecpContext.Miesiace.Where(x => x.Rok == rokValue and x => x.Miesiac == miesiacValue).Select(i => new
{
ObecnaIloscDni = i.IloscDni
});
return null;
}
Replace the and by &&. See the documentation for more Details.
eg:
[HttpGet]
public ActionResult GetNumberDays(string mieciacValue, int rokValue)
{
var liczbaDni = _ecpContext.Miesiace.Where(x => x.Rok == rokValue && x.Miesiac == miesiacValue).Select(i => new
{
ObecnaIloscDni = i.IloscDni
});
return null;
}

How to initialize var when it is used for different things in a method

I have a method in my controller that is performing some logic based on data submitted and my varitem should be assigned to different results?
For example my controller's method is something like this:
public ActionResult Index(SearchCriteria criteria)
{
var db = new EbuyDataEntities();
//criteria.SearchKeyword = "movie";
if (!string.IsNullOrEmpty(criteria.SearchKeyword))
{
var auctionData = db.Auctions.First(q => q.Description.Contains(criteria.SearchKeyword));
}
else
{
var auctionData = db.Auctions.OrderBy(item => item.EndTime);
}
switch (criteria.GetSortByField())
{
case SearchCriteria.SearchFieldType.Price:
auctionData = auctionData.OrderBy(q => q.CurrentPrice.Value);
break;
case SearchCriteria.SearchFieldType.RemainingTime:
auctionData = auctionData.OrderBy(q => q.EndTime);
break;
case SearchCriteria.SearchFieldType.Keyword:
default:
auctionData = auctionData.OrderBy(q => q.Title);
break;
}
auctionData = SomeMethod();
var viewModel = new SearchViewModel();
return View("Search",viewModel);
}
What is the right way to do something like this.
Well, two options:
Move the declaration to before the if statement, and give it an explicit type:
IQueryable<Auction> auctionData;
if (...)
{
...
}
else
{
...
}
Change the structure to only have a single declaration, e.g. using the conditional operator:
var auctionData = !string.IsNullOrEmpty(criteria.SearchKeyword)
? db.Auctions.Where(q => q.Description.Contains(criteria.SearchKeyword))
: db.Auctions.OrderBy(item => item.EndTime);
Note that I've changed First to Where here - otherwise you would only be matching a single entry, which doesn't sound like much of a search to me, and would make of the rest of the method very odd.
That in itself suggests a third option:
var auctionData = db.Auctions.OrderBy(item => item.EndTime);
var keyword = criteria.SearchKeyword;
if (!string.IsNullOrEmpty(keyword))
{
auctionData = auctionData.Where((q => q.Description.Contains(keyword));
}

Search by multiple fields

I'm working on a project and implemented a search by multiple fields in MVC, using LINQ like so:
public ActionResult SearchResult(SearchViewModel model)
{
List<Requisition> emptyList = new List<Requisition>();
if (model.RequisitionID > 0
|| model.Department > 0
|| model.Status > 0
|| model.RequisitionedBy != null)
{
var results = db.Requisitions.Where(x => x.RequisitionId > 0);
results = ProcessSearchInput(model, results);
return PartialView(results.ToList());
}
return PartialView(emptyList);
}
Helper:
private static IQueryable<Requisition> ProcessSearchInput(SearchViewModel model, IQueryable<Requisition> results)
{
if (model.Department > 0)
results = results.Where(x => x.Department == model.Department);
if (model.RequisitionedBy != null)
results = results.Where(x => x.Requisitioned_By.Contains(model.RequisitionedBy));
if (model.Status > 0)
results = results.Where(x => x.Status.Contains(model.Status.ToString()));
return results;
}
This code works fine.
However, if I add an extra search field to the form, I would also need to add a separate if statement in the controller.
With the current approach, the ProcessSearchInput method will contain too many if statements.
Is there a better way to handle a search with multiple fields?
Your current approach violates the open closed principle. The solution is to create a dynamic filter like in this example. However that is a complicated solution that is worth only if you are going to add more and more filters along the way. If not then don't bother.
I agree with previous comments: your current solution is probably the way to go.
In the real world, you'll soon have to implement filters like 'all customers having either a billing- or a shipping-address in New York', and more complicated stuff. By then, all clever generic stuff will just be in the way.
However, if you promise never to use this in production code:
you can save a lot of typing by using a query by example, where you specify the filter as an instance of the type your source contains:
var example = new Requisition { Department = 8, Requisitioned_By ="john" };
var result = db.Requisitions.FilterByExample(example);
This is a simple implementation:
public static class FilterByExampleHelper
{
public static IQueryable<T> FilterByExample<T>(this IQueryable<T> source, T example) where T : class
{
foreach (var property in typeof(T).GetProperties(BindingFlags.Public|BindingFlags.Instance).Where(p => p.CanRead))
{
ConstantExpression valueEx = null;
var propertyType = property.PropertyType;
if (propertyType.IsValueType)
{
var value = property.GetValue(example);
if (value != null &&
!value.Equals(Activator.CreateInstance(propertyType)))
{
valueEx = Expression.Constant(value, propertyType);
}
}
if (propertyType == typeof(string))
{
var value = property.GetValue(example) as string;
if (!string.IsNullOrEmpty(value))
{
valueEx = Expression.Constant(value);
}
}
if (valueEx == null)
{
continue;
}
var parameterEx = Expression.Parameter(typeof(T));
var propertyEx = Expression.Property(parameterEx, property);
var equalsEx = Expression.Equal(propertyEx, valueEx);
var lambdaEx = Expression.Lambda(equalsEx, parameterEx) as Expression<Func<T, bool>>;
source = source.Where(lambdaEx);
}
return source;
}
}

DataTables multisort columns .net mvc hint

i'm using Datatables Jquery Extension and i've made multicolum sorting reading those posts:
http://www.codeproject.com/Articles/155422/jQuery-DataTables-and-ASP-NET-MVC-Integration-Part#Sorting
http://farm-fresh-code.blogspot.it/2012/02/mvc-jquery-ui-and-datatable-pluginajax.html
My question is, those two methods are dependent to The model that i'm using in my Controller, in this case
If i need to use similar code in other controllers i need to replicate that and changing model fields and the Type with others, for example .
This seems to me not very DRY.
How to Proceed? Is controller the good position for those two methods?
private IOrderedQueryable<User> CreateSortedQuery(DataTableParameterModel parameterModel, IQueryable<User> baseQuery)
{
var orderedQuery = (IOrderedQueryable<User>)baseQuery;
for (int i = 0; i < parameterModel.iSortingCols; ++i)
{
var ascending = string.Equals("asc", parameterModel.sSortDir[i], StringComparison.OrdinalIgnoreCase);
int sortCol = parameterModel.iSortCol[i];
Expression<Func<User, string>> orderByExpression = GetOrderingFunc(sortCol);
if (orderByExpression != null)
{
if (ascending)
{
orderedQuery = (i == 0)
? orderedQuery.OrderBy(orderByExpression)
: orderedQuery.ThenBy(orderByExpression);
}
else
{
orderedQuery = (i == 0)
? orderedQuery.OrderByDescending(orderByExpression)
: orderedQuery.ThenByDescending(orderByExpression);
}
}
else
{
if (ascending)
{
orderedQuery = (i == 0)
? orderedQuery.OrderBy(c => c.Id)
: orderedQuery.ThenBy(c => c.Id);
}
else
{
orderedQuery = (i == 0)
? orderedQuery.OrderByDescending(c => c.Id)
: orderedQuery.ThenByDescending(orderByExpression);
}
}
}
return orderedQuery;
}
private Expression<Func<User, string>> GetOrderingFunc(int ColumnIndex)
{
Expression<Func<User, string>> InitialorderingFunction;
switch (ColumnIndex)
{
case 1:
InitialorderingFunction = c => c.FirstName;
break;
case 2:
InitialorderingFunction = c => c.LastName;
break;
case 3:
InitialorderingFunction = c => c.UserName;
break;
case 4:
InitialorderingFunction = c => c.Email;
break;
case 5:
InitialorderingFunction = c => c.BusinessName;
break;
default:
InitialorderingFunction = null;
break;
}
return InitialorderingFunction;
}
I guess, your question is pretty close to these two answers:
Property name evaluating from expression:
public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
var expression = (MemberExpression)action.Body;
string fieldName = expression.Member.Name;
and
Applying linq sorting passing string values with LINQ Dynamic Query Library:
var result = data
.Where(/* ... */)
.Select(/* ... */)
.OrderBy(fieldName + " asc");

Categories