I wrote two Specifications which return null if their parameter is null.
public static Specification<Prodotto> getProdottoByLineaSpec (String linea) {
if (linea != null) {
return (root, query, criteriaBuilder) -> {
return criteriaBuilder.like((root.join("linea")).get("nome"), "%"+linea+"%");
};
}
else return null;
}
public static Specification<Prodotto> getProdottoByIngSpec (String ing) {
if (ing != null) {
return (root, query, criteriaBuilder) -> {
return criteriaBuilder.like(((root.join("listaQuoteIng")).join("ing")).get("nome"), "%"+ing+"%");
};
}
else return null;
}
Then I created a third one that combines the previous ones with an and operator inside a where clause:
public static Specification<Prodotto> getProdottoByMainTraits (String linea, String ing) {
return Specification.where(getProdottoByLineaSpec(linea).and(getProdottoByIngSpec(ing)));
}
Now, that's the funny part:
If ByLinea returns null, i get a nullPointerException from checkPackageAccess when resolving the where clause.
If ByIng returns null, it just gets ignored (like it should be) and the query matches just the other predicate.
If I switch the two predicates, putting ByIng as the first one and then ByLinea inside the where clause, everything works in every combination.
It is a good practice to avoid returning null from methods.
You can use criteriaBuilder.conjunction() to ignore null parameter Specification. It generates always true Predicate. There is an opposite method criteriaBuilder.disjunction()
public static Specification<Prodotto> getProdottoByLineaSpec (String linea) {
return (root, query, criteriaBuilder) -> {
if (linea == null) {
return criteriaBuilder.conjunction();
}
return criteriaBuilder.like(
(root.join("linea")).get("nome"),
"%" + linea + "%"
);
}
}
P.S. You get NullPointerException if first Specification is null trying to access a method and.
To be clear it looks like this
Specification.where(null.and(getProdottoByIngSpec(ing)));
But if only second Specification is null this one works
Specification.where(getProdottoByLineaSpec(linea).and(null));
because and method parameter can be null
Related
I am currently implementing a RESTFUL API that provides endpoints to interface with a database .
I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.
I've found some patterns such as :
GET /api/ressource?param1=value1,param2=value2...paramN=valueN
param1,param2...param N being my table columns and the values.
I've also found another pattern that consists of send a JSON object that represents the query .
To filter on a field, simply add that field and its value to the query :
GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}
I'm looking for the best practice to achieve this .
I'm using EF Core with ASP.NET Core for implementing this.
Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.
That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:
I.e.
{
p1: "Jake",
p2: "8"
}
The query string looks like:
.../api/customer/search?filters=XHgde0023GRw....
On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:
// this is the search filter keys, these (int) values are passed to the search client for each filter field.
public enum FilterKeys
{
None = 0,
Name,
Age,
ParentName
}
public JsonResult Search(string filters)
{
string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);
using (var context = new TestDbContext())
{
var query = context.Children.AsQueryable();
foreach (var filter in filterData)
query = filterChildren(query, filter.Key, filter.Value);
var results = query.ToList(); //example fetch.
// TODO: Get the results, package up view models, and return...
}
}
private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
{
var filterKey = parseFilterKey(key);
if (filterKey == FilterKeys.None)
return query;
switch (filterKey)
{
case FilterKeys.Name:
query = query.Where(x => x.Name == value);
break;
case FilterKeys.Age:
DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
DateTime birthDateEnd = birthDateStart.AddYears(1);
query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
break;
}
return query;
}
private FilterKeys parseFilterKey(string key)
{
FilterKeys filterKey = FilterKeys.None;
Enum.TryParse(key.Substring(1), out filterKey);
return filterKey;
}
You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.
I have invented and found it useful to combine a few filters into one type for example CommonFilters and make this type parseable from string:
[TypeConverter(typeof(CommonFiltersTypeConverter))]
public class CommonFilters
{
public PageOptions PageOptions { get; set; }
public Range<decimal> Amount { get; set; }
//... other filters
[JsonIgnore]
public bool HasAny => Amount.HasValue || PageOptions!=null;
public static bool TryParse(string str, out CommonFilters result)
{
result = new CommonFilters();
if (string.IsNullOrEmpty(str))
return false;
var parts = str.Split(new[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
if (part.StartsWith("amount:") && Range<decimal>.TryParse(part.Substring(7), out Range<decimal> amount))
{
result.Amount = amount;
continue;
}
if (part.StartsWith("page-options:") && PageOptions.TryParse(part.Substring(13), out PageOptions pageOptions))
{
result.PageOptions = pageOptions;
continue;
}
//etc.
}
return result.HasAny;
}
public static implicit operator CommonFilters(string str)
{
if (TryParse(str, out CommonFilters res))
return res;
return null;
}
}
public class CommonFiltersTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string str)
{
if (CommonFilters.TryParse(str, out CommonFilters obj))
{
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
}
the request looks like this:
public class GetOrdersRequest
{
[DefaultValue("page-options:50;amount:0.001-1000;min-qty:10")]
public CommonFilters Filters { get; set; }
//...other stuff
}
In this way you reduce the number of input request parameters, especially when some queries don't care about all filters
If you use swagger map this type as string:
c.MapTypeAsString<CommonFilters>();
public static void MapTypeAsString<T>(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.MapType(typeof(T), () => new OpenApiSchema(){Type = "string"});
}
I have 3 methods these are same methods only some parameters will be change I want to write one method how can i write
public string method1(int id)
{
var getAllStudents = rep.Students.Where(e => e.StudentId == id).ToList();
foreach (var item in getAllStudents)
{
if (item.isActive != true)
return "Error";
}
return "OK";
}
public string method2(int id)
{
var getAllTeachers = rep.Teachers.Where(e => e.TeacherId == id).ToList();
foreach (var item in getAllTeachers)
{
if (item.isActive != true)
return "Error";
}
return "OK";
}
public string method3(int id)
{
var getAllClasses = rep.Classes.Where(e => e.ClassId == id).ToList();
foreach (var item in getAllClasses)
{
if (item.isActive != true)
return "Error";
}
return "OK";
}
I think there is very easy way to write 1 method. the think is where parameter has different id..
Thanks.
Avoid conditional logic based on arguments. This leads to fragile code because every parameter combination has to be tested to be considered reliable. This leads to complex code that is easily prone to bugs. Having simpler single-purpose methods are typically much more reliable and easier to understand and maintain.
For instance given your example and assuming that "rep" was your instance's DbContext...
public bool IsActiveStudent(int id)
{
bool result = rep.Students.Any(x => x.StudentId == id && x.IsActive);
return result;
}
public bool IsActiveTeacher(int id)
{
bool result = rep.Teachers.Any(x => x.TeacherId == id && x.IsActive);
return result;
}
public bool IsActiveClass(int id)
{
bool result = rep.Classes.Any(x => x.ClassId == id && x.IsActive);
return result;
}
These can be essentially one-liners by simply returning the .Any() result. I tend to favour selecting the result into a variable first and returning it on a separate line since it makes it easier to breakpoint and inspect.
If you need to return a string for "Ok" vs. "Error" then:
return result ? "OK" : "Error";
Methods should strive to do one thing, and do it well. Easy to understand and troubleshoot if need be. Adding parameters and conditional code inside the method merely makes the code more volatile and leaves openings for bugs. In the end it doesn't make the code much shorter when the initial method could be simplified.
You can not overload methods if they signatures are the same.
You have two methods with the same signature:
public string checkexist(int id)
What you can do is to rename your methods, like this:
public interface WriteSomethingHere {
public boolean isStudentExist(int id);
public boolean isTeacherExist(int id);
public boolean isClassExist(int id);
}
I just found answer using generic repo
public T GetEntity<T>(int Id)
where T : class
{
using (MyEntities rpContext = new MyEntities())
{
return rpContext.Set<T>().Find(e => e.Id == Id);
}
}
after calling
var entityStudent = GetEntity<Student>(1);
var entityTeacher = GetEntity<Teacher>(1);
var entityClasses = GetEntity<Classes>(1);
You have Create Enumeration
Public Enum ParameterStaus:short
{
Student=1,
Teacher=2,
Classess=3
}
public string method2(int id.ParameterStatus status)
{
if(status==ParameterStatus.Teacher)
{
var getAllTeachers = rep.Teachers.Where(e => e.TeacherId == id).ToList();
foreach (var item in getAllTeachers )
{
if (item.isActive != true)
return "Error";
}
return "OK";
}
}
Else if(status==ParameterStatus.Student)
{
var getAllStudents = rep.Students.Where(e => e.StudentId == id).ToList();
foreach (var item in getAllStudents)
{
if (item.isActive != true)
return "Error";
}
return "OK";
}
Else
{
var getAllClasses = rep.Classes.Where(e => e.ClassId == id).ToList();
foreach (var item in getAllClasses)
{
if (item.isActive != true)
return "Error";
}
return "OK";
}
}
This method is supposed to make a list from my database (Godkjenning), and I want it to return true if ive written ("Godkjent") in my database and false if not.
public boolean GodkjenningT(String godkjentIkkegodkjent) {
List<Tilbakemelding> tilbakemeldingListe = em.createQuery(
"SELECT t FROM Tilbakemelding t WHERE t.Godkjenning LIKE :godkjenning" )
.setParameter("godkjenning", godkjentIkkegodkjent )
.setMaxResults(1)
.getResultList();
System.out.println("godkjenning" + godkjentIkkegodkjent);
if(tilbakemeldingListe.equals("Godkjent")){
return true;
}
else {
return false;
}
}
}
I will edit the code like this:
public boolean GodkjenningT(String godkjentIkkegodkjent) {
/*now the query matches cases and the 'LIKE' statement will work correctly
having the '%' char before and after the string */
List<Tilbakemelding> tilbakemeldingListe = em.createQuery(
"SELECT t FROM Tilbakemelding t WHERE UPPER(t.Godkjenning) LIKE UPPER(?)")
.setString(0, "%"+godkjentIkkegodkjent+"%")
.getResultList();
System.out.println("godkjenning" + godkjentIkkegodkjent);
/*here you have to iterate the list to find if there is the string inside*/
for(Tilbakemelding element : tilbakemeldingListe){
/*if you want not to consider the case, you have to use the equalsIgnoreCase statement*/
if(element.getGodkjenning().equals("Godkjent")){
return true;
} else {
return false;
}
}
}
But, can you please post the implementation of the class: Tilbakemelding? I've supposed your Tilbakemelding entity (is that an entity? :)) has a getGodkjenning() method to complete my code.
In Entity framework 6, when running the overload of the Include method that uses lambda expression to the context:
Context.SomeEntity.Include(x => x.MyOtherEntity))
it returns an IQueryable, whereas when we use the one that uses string:
Context.SomeEntity.Include("MyOtherEntity")
it returns a DbQuery.
I need to return a DbQuery and don't want to use the string overload so that I can get inclusion errors at compile time.
How can I return a DbQuery after using the include with the lambda?
I believe you can't convert an IQueryable to a DbQuery. However, you can use this code to pass in an expression and get the required string.
Be sure to write some unit tests for this and adapt the method for your needs. I have not tested it properly yet.
public static string GetMemberName<T>(this Expression<Func<T>> expression)
{
MemberExpression memberExp;
if (!TryFindMemberExpression(expression.Body, out memberExp))
return string.Empty;
var memberNames = new Stack<string>();
do
{
memberNames.Push(memberExp.Member.Name);
}
while (TryFindMemberExpression(memberExp.Expression, out memberExp));
return string.Join(".", memberNames.ToArray());
}
private static bool TryFindMemberExpression(Expression exp, out MemberExpression memberExp)
{
memberExp = exp as MemberExpression;
if (memberExp != null)
{
return true;
}
if (IsConversion(exp) && exp is UnaryExpression)
{
memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
if (memberExp != null)
{
return true;
}
}
return false;
}
private static bool IsConversion(Expression exp)
{
return (exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked);
}
I build a Repository just like Codefirst demo.
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
But this search has a problem.
Sometimes other person use 'get' function to search value and this value will be used as param inside other function.
Just like this:
UserInfoBll userInfoBll = new UserInfoBll();
UserInfo userInfo = userInfoBll.Get(p => p.UserCode == "8001", null, "CorpShop").First();
ConsumeProcess.process(userInfo);
If userInfo value change in function ConsumeProcess.process when I process savechange.
It will update something I don't want to update, so I want to find a way. The search value is just for search, when value change it, not to update the value.
For this way I wrote this:
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
List<TEntity> tEntityList = Get(filter, orderBy, includeProperties).ToList();
SetEntityStates(EntityState.Detached, tEntityList);
return tEntityList;
}
protected void SetEntityStates(EntityState entityState, IEnumerable<TEntity> tEntity_params)
{
foreach (TEntity tEntity in tEntity_params)
{
if (tEntity != null)
{
context.Entry(tEntity).State = entityState;
}
}
}
Now it won't update, if somebody changed search value. But there is another problem.
If I use code like this, the property which is included can't get
UserInfoBll userInfoBll = new UserInfoBll();
userInfo = userInfoBll.GetList(p => p.UserCode == "8001", null, "CorpShop").First(); // CorpShop is include property
corpShop = userInfo.CorpShop; //userInfo.CorpShop is null
If you have an IQueryable, the easiest way to get entities disconnected from db is to use the AsNoTracking method.
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
return = Get(filter, orderBy, includeProperties).AsNoTracking().ToList();
}