web.api only serializing hidden fields - codefluent

I'm experiencing a strange behaviour.
My web.api is returning only hiddenfields from my ObjectCollection on a GET request.
This is my controller:
// GET: api/UserDocuments
[Route("api/UserDocuments/User/{userName}")]
public List<DocIndex> Get(string userName)
{
User usuari = Humanisme.User.LoadByUserName(userName);
List<DocIndex> resposta = DocumentCollection.LoadIndexPerUsuari(usuari);
return resposta;
}
And this is the object as it gets generated from the BOM:
namespace Humanisme
{
using CodeFluent.Runtime;
using CodeFluent.Runtime.Utilities;
// CodeFluent Entities generated (http://www.softfluent.com). Date: Tuesday, 01 March 2016 11:52.
// Build:1.0.61214.0820
[System.CodeDom.Compiler.GeneratedCodeAttribute("CodeFluent Entities", "1.0.61214.0820")]
[System.SerializableAttribute()]
[System.ComponentModel.DataObjectAttribute()]
public partial class DocIndex : CodeFluent.Runtime.ICodeFluentLightEntity
{
private int _id = -1;
[System.NonSerializedAttribute()]
private Humanisme.User _user = ((Humanisme.User)(null));
private string _lat = default(string);
private string _lon = default(string);
private string _etapaVital = default(string);
private string _solvencia = default(string);
private int _valoracio = CodeFluentPersistence.DefaultInt32Value;
private System.DateTime _data = CodeFluentPersistence.DefaultDateTimeValue;
private string _nom = default(string);
public DocIndex()
{
}
[System.ComponentModel.DefaultValueAttribute(((int)(-1)))]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=false, Type=typeof(int))]
[System.ComponentModel.DataObjectFieldAttribute(true)]
public int Id
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
[System.Xml.Serialization.XmlIgnoreAttribute()]
public Humanisme.User User
{
get
{
return this._user;
}
set
{
this._user = value;
}
}
[System.ComponentModel.DefaultValueAttribute(default(string))]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Type=typeof(string))]
public string Lat
{
get
{
return this._lat;
}
set
{
this._lat = value;
}
}
[System.ComponentModel.DefaultValueAttribute(default(string))]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Type=typeof(string))]
public string Lon
{
get
{
return this._lon;
}
set
{
this._lon = value;
}
}
[System.ComponentModel.DefaultValueAttribute(default(string))]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Type=typeof(string))]
public string EtapaVital
{
get
{
return this._etapaVital;
}
set
{
this._etapaVital = value;
}
}
[System.ComponentModel.DefaultValueAttribute(default(string))]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Type=typeof(string))]
public string Solvencia
{
get
{
return this._solvencia;
}
set
{
this._solvencia = value;
}
}
[System.ComponentModel.DefaultValueAttribute(CodeFluentPersistence.DefaultInt32Value)]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=false, Type=typeof(int))]
public int Valoracio
{
get
{
return this._valoracio;
}
set
{
this._valoracio = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(IsNullable=false, Type=typeof(System.DateTime))]
public System.DateTime Data
{
get
{
return this._data;
}
set
{
this._data = value;
}
}
[System.ComponentModel.DefaultValueAttribute(default(string))]
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true, Type=typeof(string))]
public string Nom
{
get
{
return this._nom;
}
set
{
this._nom = value;
}
}
protected virtual void ReadRecord(System.Data.IDataReader reader, CodeFluent.Runtime.CodeFluentReloadOptions options)
{
if ((reader == null))
{
throw new System.ArgumentNullException("reader");
}
if ((((options & CodeFluent.Runtime.CodeFluentReloadOptions.Properties)
== 0)
== false))
{
this._id = CodeFluentPersistence.GetReaderValue(reader, "Id", ((int)(-1)));
this._user = new Humanisme.User();
CodeFluent.Runtime.CodeFluentLightWeightPersistence.ReadRecord(reader, this._user, null, new CodeFluent.Runtime.Utilities.Pair<string, string>("Id", "User_Id"));
this._lat = CodeFluentPersistence.GetReaderValue(reader, "Lat", ((string)(default(string))));
this._lon = CodeFluentPersistence.GetReaderValue(reader, "Lon", ((string)(default(string))));
this._etapaVital = CodeFluentPersistence.GetReaderValue(reader, "EtapaVital", ((string)(default(string))));
this._solvencia = CodeFluentPersistence.GetReaderValue(reader, "Solvencia", ((string)(default(string))));
this._valoracio = CodeFluentPersistence.GetReaderValue(reader, "Valoracio", ((int)(CodeFluentPersistence.DefaultInt32Value)));
this._data = CodeFluentPersistence.GetReaderValue(reader, "Data", ((System.DateTime)(CodeFluentPersistence.DefaultDateTimeValue)));
this._nom = CodeFluentPersistence.GetReaderValue(reader, "Nom", ((string)(default(string))));
}
}
void CodeFluent.Runtime.ICodeFluentLightEntity.ReadRecord(System.Data.IDataReader reader)
{
this.ReadRecord(reader, CodeFluent.Runtime.CodeFluentReloadOptions.Default);
}
}
}
Calling the web.api get method returns this JSON:
[
{
"_id": 1,
"_lat": null,
"_lon": null,
"_etapaVital": null,
"_solvencia": null,
"_valoracio": 0,
"_data": "0001-01-01T00:00:00",
"_nom": null
}
]
Serializer (from WebApiConfig.cs)
JsonMediaTypeFormatter jsonFormatter = (JsonMediaTypeFormatter)config.Formatters.FirstOrDefault(f => f is JsonMediaTypeFormatter);
if (jsonFormatter != null)
{
// jsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Include;
jsonFormatter.UseDataContractJsonSerializer = true;
}

The classes generated by CodeFluent Entities are decorated by SerializableAttribute. This attribute changes the way Json.NET serialize or deserialize the object. You can configure Json.NET to ignore this attribute:
JsonMediaTypeFormatter jsonFormatter = (JsonMediaTypeFormatter)config.Formatters.FirstOrDefault(f => f is JsonMediaTypeFormatter);
if (jsonFormatter != null)
{
jsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
IgnoreSerializableAttribute = true
};
}
http://james.newtonking.com/archive/2012/04/11/json-net-4-5-release-2-serializable-support-and-bug-fixes
Json.NET now detects types that have the SerializableAttribute and serializes all the fields on that type, both public and private, and ignores the propertie
So you can use the service producer which will add the DataMemberAttribute or you can use the Json.NET Aspect to automatically add specific Json.NET attribute: Newtonsoft.Json.JsonObjectAttribute and Newtonsoft.Json.JsonPropertyAttribute.

Finally found!
When dealing with web.api never, never, never forget to add the "Service Producer" subproducer attached to the standard BOM Producer in your model project.
You'll never notice any problem but at serializing when no attributes will be processed and only hidden properties (object fields) will be serialized at output.
Sorry for the nerd mistake, happy for the lesson learned.
Again, thanks Meziantou. You would never figured where the issue was originated, mainly because I didn't carried all the project details to the question.

Related

swashbuckle openapi 3 write example and description for the dynamically generated model classes

My model properties definition is coming from a json file so using reflection to write the classes to be shown under schema on resulting swagger page.
foreach (var model in Models)
{
if (!ModelTypes.ContainsKey(model.Key))
{
anyNonCompiledModel = true;
BuildModelCodeClass(modelComponentBuilder, model.Value);//Build model classes
}
}
BuildModelCodeEnd(modelComponentBuilder);
if (anyNonCompiledModel)
{
CSharpCompiler compiler = new CSharpCompiler();
compiler.AddReference(typeof(object));
compiler.AddReference(typeof(ResourceFactory));
compiler.AddReference(typeof(System.Runtime.Serialization.DataContractResolver));
compiler.AddReference(typeof(System.Runtime.Serialization.DataContractAttribute));
var types = compiler.Compiler(modelComponentBuilder.ToString()); //write model classes
foreach (var type in types)
{
ModelTypes.Add(type.Name, type);
}
}
public void BuildModelCodeClass(StringBuilder modelComponentBuilder, MetadataModelEntity model)
{
modelComponentBuilder.AppendLine($"public class {model.Name} {{");
foreach (var p in model.Data.Properties)
{
if (p.Obsoleted) continue;
if (p.Type.Type == "array")
{
modelComponentBuilder.AppendLine($" public {p.Type.ArrayType.ObjectName}[] {p.Name} {{get;set;}}");
}
else
{
//primitive types
modelComponentBuilder.AppendLine($" public {p.Type.ObjectName} {p.Name} {{get;set;}}");
}
}
modelComponentBuilder.AppendLine(
#"}
");
}
If i provide the description and example like following (in BuildModelCodeClass, inside the loop) then the example and description displays for me.
if (!string.IsNullOrWhiteSpace((string)p.Example))
{
modelComponentBuilder.AppendLine($" ///<example>{p.Example}</example>");
}
if (!string.IsNullOrWhiteSpace((string)p.Description))
{
modelComponentBuilder.AppendLine($" ///<description>{p.Description}</description>");
}
However, i dont want to do above.
I want to write my models via the open api and not via the C# Compiler, is it possible?
I want to show example and description via schema (may be under paths some where). How can i do this? Context has my models info available that i can interact with here.
public class SwaggerDocumentFilter : IDocumentFilter
{
SwaggerDocument _swaggerDocument;
public SwaggerDocumentFilter(object apiConfigure)
{
_swaggerDocument = ((ApiGatewayConfiguration)apiConfigure).SwaggerDocument;
}
public void Apply(OpenApiDocument document, DocumentFilterContext context)
{
if (document.Info.Extensions == null || !document.Info.Extensions.ContainsKey(SwaggerEndpoint.ExtensionDocName)) return;
var openIdString = document.Info.Extensions[SwaggerEndpoint.ExtensionDocName] as OpenApiString;
if (openIdString == null) return;
var docName = openIdString.Value;
SwaggerEndpoint endpoint = _swaggerDocument.SwaggerEndpoints.SingleOrDefault(x => x.Name == docName);
if (endpoint == null) return;
//Add server objects
document.Servers = endpoint.ServerObjects;
//Add Tags objects
document.Tags = endpoint.Tags;
//Set swagger paths objects
var pathsObjects = _swaggerDocument.GetPathsObject(docName, context);
if (pathsObjects.IsValid())
{
pathsObjects.ToList().ForEach(
item => document.Paths.Add(item.Key, item.Value)
);
}
//Add Schema components
//Add Example/Examples
}
}
Following helped
https://github.com/domaindrivendev/Swashbuckle.WebApi/issues/162
AddSchemaExamples.cs
public class AddSchemaExamples : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (type == typeof(Product))
{
schema.example = new Product
{
Id = 123,
Type = ProductType.Book,
Description = "Treasure Island",
UnitPrice = 10.0M
};
}
}
}
SwaggerConfig.cs
httpConfig
.EnableSwagger(c =>
{
c.SchemaFilter<AddSchemaExamples>()
});
My implementation for the Apply since model is dynamic
if (model != null)
{
schema.Description = model.Description;
foreach (var p in schema.Properties)
{
var mp = model.Data.Properties.SingleOrDefault(x => x.Name == p.Key);
if (mp != null)
{
if (!string.IsNullOrWhiteSpace(mp.Description))
{
p.Value.Description = mp.Description;
}
if(!string.IsNullOrWhiteSpace(mp.Example))
{
p.Value.Example =
new Microsoft.OpenApi.Any.OpenApiString(mp.Example.ToString());
}
}
}
}

moq mongodb InsertOneAsync method

I am using Mongodb database with .net core. I just want to moq insert method that using mongodbContext. Here is what I am trying to do but it's not working:
public void InsertEventAsync_Test()
{
//Arrange
var eventRepository = EventRepository();
var pEvent = new PlanEvent
{
ID = "testEvent",
WorkOrderID = "WorkOrderID",
IsDeleted = false,
IsActive = true,
EquipmentID = "EquipmentID"
};
////Act
//mockEventContext.Setup(mr => mr.PlanEvent.InsertOne(It.IsAny<PlanEvent>(), It.IsAny<InsertOneOptions>()))
mockEventContext.Setup(s => s.PlanEvent.InsertOneAsync(It.IsAny<PlanEvent>(), It.IsAny<InsertOneOptions>())).Returns("sdad");
var result = eventRepository.InsertEventAsync(pEvent);
////Assert
result.Should().NotBeNull();
}
Below is the method that I need to Moq:
public EventRepository(IFMPContext eventContext)
{
_eventContext = eventContext;
}
public async Task<string> InsertEventAsync(Model.EventDataModel.PlanEvent eventobj)
{
eventobj._id = ObjectId.GenerateNewId();
eventobj.CreatedDateTime = DateTime.UtcNow.ToString();
try
{
_eventContext.PlanEvent.InsertOne(eventobj);
return eventobj.ID;
}
catch (Exception ex)
{
string x = ex.Message;
}
return "";
}
Assuming
public class EventRepository {
private readonly IFMPContext eventContext;
public EventRepository(IFMPContext eventContext) {
this.eventContext = eventContext;
}
public async Task<string> InsertEventAsync(Model.EventDataModel.PlanEvent eventobj) {
eventobj._id = ObjectId.GenerateNewId();
eventobj.CreatedDateTime = DateTime.UtcNow.ToString();
try {
await eventContext.PlanEvent.InsertOneAsync(eventobj);
return eventobj.ID;
} catch (Exception ex) {
string x = ex.Message;
}
return "";
}
}
You need to configure the test to support the async nature of the method under test
public async Task InsertEventAsync_Test()
{
//Arrange
var expected = "testEvent";
var pEvent = new PlanEvent {
ID = expected,
WorkOrderID = "WorkOrderID",
IsDeleted = false,
IsActive = true,
EquipmentID = "EquipmentID"
};
var mockEventContext = new Mock<IFMPContext>();
mockEventContext
.Setup(_ => _.PlanEvent.InsertOneAsync(It.IsAny<PlanEvent>(), It.IsAny<InsertOneOptions>()))
.ReturnsAsync(Task.FromResult((object)null));
var eventRepository = new EventRepository(mockEventContext.Object);
//Act
var actual = await eventRepository.InsertEventAsync(pEvent);
//Assert
actual.Should().NotBeNull()
actual.Should().Be(expected);
}
The test method definition needed to be updated to be asynchronous to allow the method under test to be awaited. The mock dependency also needed to be setup in such a way to allow the async flow to continue as expected when invoked.
#Nkosi Thanks a lot for your help. Finally i found the way. i was missing extra moq param It.IsAny<System.Threading.CancellationToken>() below is the working test
public void InsertEventAsync_Test()
{
//Arrange
var eventRepository = EventRepository();
var pEvent = new PlanEvent
{
ID = "testEvent",
WorkOrderID = "WorkOrderID",
IsDeleted = false,
IsActive = true,
EquipmentID = "EquipmentID"
};
////Act
mockEventContext.Setup(s => s.PlanEvent.InsertOne(It.IsAny<PlanEvent>(), It.IsAny<InsertOneOptions>(),It.IsAny<System.Threading.CancellationToken>()));
var result = eventRepository.InsertEventAsync(pEvent);
////Assert
result.Should().NotBeNull();
Assert.AreEqual(pEvent.ID, result);
}

_Context.SaveChangesAsync() is not executing in command line

I have used _Context.SaveChangesAsync() in a lot of places in my project and it work fine, except here
//hash the password change the bool to false set the salt save
await TryUpdateModelAsync<User>(user);
Hash hash = new Hash();
string salt = hash.CreateSalt();
user.Salt = salt;
user.Password = hash.HashPassword(NewPassword, salt);
user.ChangePassword = false;
await _Context.SaveChangesAsync();
The Model state is true, and it should be working fine but even in the command line (Microsoft.EntityFrameworkCore.Database.Command) it doesn't show at all and the database is not updating, no error or exception is being thrown
any idea ?
this is the whole class
public class ChangePasswordModel : PageModel
{
public string CurrentPassword { set; get; }
public string NewPassword { set; get; }
public User user { set; get; }
private readonly PROJECTDATABASE_MDFContext _Context;
public ChangePasswordModel(PROJECTDATABASE_MDFContext databasecontext)
{
_Context = databasecontext;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
int TempId = 0;
try
{
TempId = Convert.ToInt32(User.FindFirst(claim => claim.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value);
}
catch { }
if (TempId > 0)
{
user = new User();
user = await _Context.User.AsNoTracking().Include(m => m.Role).FirstOrDefaultAsync(m => m.UserId == TempId);
if (user != null)
{
CurrentPassword = Request.Form["CurrentPassword"];
NewPassword = Request.Form["NewPassword"];
//if the password is hashed
if (user.ChangePassword == false)
{
Hash hash = new Hash();
CurrentPassword = hash.HashPassword(CurrentPassword, user.Salt);
}
if (user.Password == CurrentPassword)
{
if(NewPassword.Length >= 8)
{
//hash the password change the bool to false set the salt save
await TryUpdateModelAsync<User>(user);
Hash hash = new Hash();
string salt = hash.CreateSalt();
user.Salt = salt;
user.Password = hash.HashPassword(NewPassword, salt);
user.ChangePassword = false;
await _Context.SaveChangesAsync();
if (user.Role.RoleName == "Student")
{
return RedirectToPage("Users/StudentProfile", new { id = user.UserId });
}
else
{
return RedirectToPage("Users/StaffMemberProfile", new { id = user.UserId });
}
}
else
{
ModelState.AddModelError("NewPassword", "Password Must be at least 8 characters!");
return Page();
}
}
else
{
ModelState.AddModelError("CurrentPassword", "Password is not Correct!");
return Page();
}
}
}
return Page();
}
}
before the save the user is populated correctly with the updated values so the problem is not in the hash method
The problem is in the line where you load User entity:
user = await _Context.User.AsNoTracking().Include(m => m.Role).FirstOrDefaultAsync(m => m.UserId == TempId);
You use AsNoTracking() extension call which tells Entity Framework not to track any changes for returned entity. So to fix the problem just remove unnecessary AsNoTracking() call:
user = await _Context.User.Include(m => m.Role).FirstOrDefaultAsync(m => m.UserId == TempId);

How to construct a dynamic where filter in EF.Core to handle equals, LIKE, gt, lt, etc

Please how do we construct a dynamic where filter in EF.Core to handle:
Query.Where(fieldName, compareMode, value)
I basically Expect to use it like below:
[HttpGet(Name = nameof(GetStaff))]
public IActionResult GetStaffAsync([FromQuery] QueryParams p)
{
var s = db.Staff.AsNoTracking()
.Where(p.filter_field, p.filter_mode, p.filter_value)
.OrderByMember(p.sortBy, p.descending);
var l = new Pager<Staff>(s, p.page, p.rowsPerPage);
return Ok(l);
}
//Helpers
public class QueryParams
{
public bool descending { get; set; }
public int page { get; set; } = 1;
public int rowsPerPage { get; set; } = 5;
public string sortBy { get; set; }
public onject filter_value { get; set; }
public string filter_field { get; set; }
public string filter_mode { get; set; }
}
public class Pager<T>
{
public int pages { get; set; }
public int total { get; set; }
public IEnumerable<T> Items { get; set; }
public Pager(IEnumerable<T> items, int offset, int limit)
{
Items = items.Skip((offset - 1) * limit).Take(limit).ToList<T>();
total = items.Count();
pages = (int)Math.Ceiling((double)total / limit);
}
}
Assuming all you have is the entity type and strings representing the property, comparison operator and the value, building dynamic predicate can be done with something like this:
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, string value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.Property);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
private static Expression MakeComparison(Expression left, string comparison, string value)
{
switch (comparison)
{
case "==":
return MakeBinary(ExpressionType.Equal, left, value);
case "!=":
return MakeBinary(ExpressionType.NotEqual, left, value);
case ">":
return MakeBinary(ExpressionType.GreaterThan, left, value);
case ">=":
return MakeBinary(ExpressionType.GreaterThanOrEqual, left, value);
case "<":
return MakeBinary(ExpressionType.LessThan, left, value);
case "<=":
return MakeBinary(ExpressionType.LessThanOrEqual, left, value);
case "Contains":
case "StartsWith":
case "EndsWith":
return Expression.Call(MakeString(left), comparison, Type.EmptyTypes, Expression.Constant(value, typeof(string)));
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
private static Expression MakeString(Expression source)
{
return source.Type == typeof(string) ? source : Expression.Call(source, "ToString", Type.EmptyTypes);
}
private static Expression MakeBinary(ExpressionType type, Expression left, string value)
{
object typedValue = value;
if (left.Type != typeof(string))
{
if (string.IsNullOrEmpty(value))
{
typedValue = null;
if (Nullable.GetUnderlyingType(left.Type) == null)
left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
}
else
{
var valueType = Nullable.GetUnderlyingType(left.Type) ?? left.Type;
typedValue = valueType.IsEnum ? Enum.Parse(valueType, value) :
valueType == typeof(Guid) ? Guid.Parse(value) :
Convert.ChangeType(value, valueType);
}
}
var right = Expression.Constant(typedValue, left.Type);
return Expression.MakeBinary(type, left, right);
}
}
Basically building property accessor (with nested property support), parsing the comparison operator and calling the corresponding operator/method, dealing with from/to string and from/to nullable type conversions. It can be extended to handle EF Core specific functions like EF.Functions.Like by adding the corresponding branch.
It can be used directly (in case you need to combine it with other predicates) or via custom extension method like this:
public static partial class QueryableExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, string comparison, string value)
{
return source.Where(ExpressionUtils.BuildPredicate<T>(propertyName, comparison, value));
}
}
based on Ivans answer this is what i came up with
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, object value)
{
var parameter = Expression.Parameter(typeof(T));
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.PropertyOrField);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
static Expression MakeComparison(Expression left, string comparison, object value)
{
var constant = Expression.Constant(value, left.Type);
switch (comparison)
{
case "==":
return Expression.MakeBinary(ExpressionType.Equal, left, constant);
case "!=":
return Expression.MakeBinary(ExpressionType.NotEqual, left, constant);
case ">":
return Expression.MakeBinary(ExpressionType.GreaterThan, left, constant);
case ">=":
return Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, constant);
case "<":
return Expression.MakeBinary(ExpressionType.LessThan, left, constant);
case "<=":
return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, constant);
case "Contains":
case "StartsWith":
case "EndsWith":
if (value is string)
{
return Expression.Call(left, comparison, Type.EmptyTypes, constant);
}
throw new NotSupportedException($"Comparison operator '{comparison}' only supported on string.");
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
}
and some tests
public class Tests
{
[Fact]
public void Nested()
{
var list = new List<Target>
{
new Target
{
Member = "a"
},
new Target
{
Member = "bb"
}
};
var result = list.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<Target>("Member.Length", "==", 2))
.Single();
Assert.Equal("bb", result.Member);
}
[Fact]
public void Field()
{
var list = new List<TargetWithField>
{
new TargetWithField
{
Field = "Target1"
},
new TargetWithField
{
Field = "Target2"
}
};
var result = list.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<TargetWithField>("Field", "==", "Target2"))
.Single();
Assert.Equal("Target2", result.Field);
}
[Theory]
[InlineData("Name", "==", "Person 1", "Person 1")]
[InlineData("Name", "!=", "Person 2", "Person 1")]
[InlineData("Name", "Contains", "son 2", "Person 2")]
[InlineData("Name", "StartsWith", "Person 2", "Person 2")]
[InlineData("Name", "EndsWith", "son 2", "Person 2")]
[InlineData("Age", "==", 13, "Person 2")]
[InlineData("Age", ">", 12, "Person 2")]
[InlineData("Age", "!=", 12, "Person 2")]
[InlineData("Age", ">=", 13, "Person 2")]
[InlineData("Age", "<", 13, "Person 1")]
[InlineData("Age", "<=", 12, "Person 1")]
public void Combos(string name, string expression, object value, string expectedName)
{
var people = new List<Person>
{
new Person
{
Name = "Person 1",
Age = 12
},
new Person
{
Name = "Person 2",
Age = 13
}
};
var result = people.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<Person>(name, expression, value))
.Single();
Assert.Equal(expectedName, result.Name);
}
}
I modified the answer I found here: Linq WHERE EF.Functions.Like - Why direct properties work and reflection does not?
I chucked together a version for those using NpgSQL as their EF Core provider as you will need to use the ILike function instead if you want case-insensitivity, also added a second version which combines a bunch of properties into a single Where() clause:
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string searchTerm)
{
// Check property name
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
// Check the search term
if(string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var property = typeof(T).GetProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"The property {typeof(T)}.{propertyName} was not found.", nameof(propertyName));
}
// Check the property type
if(property.PropertyType != typeof(string))
{
throw new ArgumentException($"The specified property must be of type {typeof(string)}.", nameof(propertyName));
}
// Get expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the property expression and return it
Expression selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, IEnumerable<string> propertyNames, string searchTerm)
{
// Check property name
if (!(propertyNames?.Any() ?? false))
{
throw new ArgumentNullException(nameof(propertyNames));
}
// Check the search term
if (string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var properties = propertyNames.Select(p => typeof(T).GetProperty(p)).AsEnumerable();
if (properties.Any(p => p == null))
{
throw new ArgumentException($"One or more specified properties was not found on type {typeof(T)}: {string.Join(",", properties.Where(p => p == null).Select((p, i) => propertyNames.ElementAt(i)))}.", nameof(propertyNames));
}
// Check the property type
if (properties.Any(p => p.PropertyType != typeof(string)))
{
throw new ArgumentException($"The specified properties must be of type {typeof(string)}: {string.Join(",", properties.Where(p => p.PropertyType != typeof(string)).Select(p => p.Name))}.", nameof(propertyNames));
}
// Get the expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the expression and return it
Expression selectorExpression = null;
foreach (var property in properties)
{
var previousSelectorExpression = selectorExpression;
selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
if(previousSelectorExpression != null)
{
selectorExpression = Expression.Or(previousSelectorExpression, selectorExpression);
}
}
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}

How to store user sql where clause, and how to apply it on a select?

I am using JPA / Eclipselink / PostgreSQL within my application.
I have a model that list some data, and I would like to let the user of the application to create his own where clause parameters.
How can I store theses parameters ? as plain sql string ?
Then how can I apply the where clause ? as a simple string concatenation ? (I don't like this idea at all).
Bests regards.
Ok, so I solved my problem.
For information : I have created a recursive JSON representation of every where clause parameters possibility.
And I have created a query using criteria api by decoding the pojo structure from json.
The json class look like that :
public class JSonSearchCriteria
{
public static enum CriteriaType
{
asc,
desc,
count,
countDistinct,
and,
or,
not,
equal,
notEqual,
between,
gt,
ge,
lt,
le,
like,
notLike;
}
#Expose
public CriteriaType type;
#Expose
public List<JSonSearchCriteria> sub;
#Expose
public String what = null;
#Expose
public List<Integer> integerValue = null;
#Expose
public List<Long> longValue = null;
#Expose
public List<Boolean> booleanValue = null;
#Expose
public List<String> stringValue = null;
#Expose
public List<DateTime> datetimeValue = null;
public JSonSearchCriteria()
{
}
public JSonSearchCriteria(final CriteriaType type)
{
this.type = type;
}
public JSonSearchCriteria(final CriteriaType type, final String what)
{
this(type);
this.what = what;
}
public JSonSearchCriteria(final CriteriaType type, final String what, final String... values)
{
this(type, what);
for(final String value : values)
{
value(value);
}
}
public JSonSearchCriteria(final CriteriaType type, final String what, final Long... values)
{
this(type, what);
for(final Long value : values)
{
value(value);
}
}
public JSonSearchCriteria(final CriteriaType type, final String what, final Integer... values)
{
this(type, what);
for(final Integer value : values)
{
value(value);
}
}
public JSonSearchCriteria(final CriteriaType type, final String what, final DateTime... values)
{
this(type, what);
for(final DateTime value : values)
{
value(value);
}
}
public void add(final JSonSearchCriteria subCriteria)
{
if(sub == null)
{
sub = new ArrayList<>();
}
sub.add(subCriteria);
}
public void value(final String value)
{
if(stringValue == null)
{
stringValue = new ArrayList<>();
}
stringValue.add(value);
}
public void value(final Long value)
{
if(longValue == null)
{
longValue = new ArrayList<>();
}
longValue.add(value);
}
public void value(final Integer value)
{
if(integerValue == null)
{
integerValue = new ArrayList<>();
}
integerValue.add(value);
}
public void value(final DateTime value)
{
if(datetimeValue == null)
{
datetimeValue = new ArrayList<>();
}
datetimeValue.add(value);
}
#SuppressWarnings(
{
"unchecked", "rawtypes"
})
#Transient
public Predicate buildPredicate(final CriteriaBuilder builder, final Root<Record> root, Join<Record, RecordInfo> infos)
{
switch(type)
{
case and:
case or:
final Predicate[] preds = new Predicate[sub.size()];
int cpt = 0;
for(final JSonSearchCriteria s : sub)
{
preds[cpt] = s.buildPredicate(builder, root, infos);
cpt++;
}
if(type == CriteriaType.and)
{
return builder.and(preds);
}
else if(type == CriteriaType.or)
{
return builder.or(preds);
}
break;
case equal:
case lt:
case gt:
case between:
final Path p;
if(what.startsWith("infos."))
{
p = infos.get(what.substring(6));
}
else
{
p = root.get(what);
}
if(stringValue != null && !stringValue.isEmpty())
{
if(type == CriteriaType.equal)
{
return builder.equal(p, stringValue.get(0));
}
}
else if(longValue != null && !longValue.isEmpty())
{
if(type == CriteriaType.equal)
{
return builder.equal(p, longValue.get(0));
}
else if(type == CriteriaType.lt)
{
return builder.lt(p, longValue.get(0));
}
else if(type == CriteriaType.gt)
{
return builder.gt(p, longValue.get(0));
}
}
else if(integerValue != null && !integerValue.isEmpty())
{
if(type == CriteriaType.equal)
{
return builder.equal(p, integerValue.get(0));
}
else if(type == CriteriaType.lt)
{
return builder.lt(p, integerValue.get(0));
}
else if(type == CriteriaType.gt)
{
return builder.gt(p, integerValue.get(0));
}
}
else if(booleanValue != null && !booleanValue.isEmpty())
{
return builder.equal(p, booleanValue.get(0));
}
else if(datetimeValue != null && !datetimeValue.isEmpty())
{
if(type == CriteriaType.equal)
{
return builder.equal(p, datetimeValue.get(0));
}
else if(type == CriteriaType.between && datetimeValue.size() > 1)
{
return builder.between(p, datetimeValue.get(0), datetimeValue.get(1));
}
}
break;
}
System.err.println(type + " - not implemented");
return null;
}
}
And it is used like that :
final SearchTemplate templ = DBHelper.get(SearchTemplate.class, 100);
final Gson gson = new GsonBuilder().registerTypeAdapter(DateTime.class, new DateTimeJsonAdapter()).create();
final JSonSearchCriteria crits = gson.fromJson(templ.getTemplate(), JSonSearchCriteria.class);
final CriteriaBuilder critBuilder = DBHelper.getInstance().em().getCriteriaBuilder();
final CriteriaQuery<Record> critQuery = critBuilder.createQuery(Record.class);
final Root<Record> root = critQuery.from(Record.class);
final Join<Record, RecordInfo> infos = root.join("infos");
critQuery.where(crits.buildPredicate(critBuilder, root, infos));
final TypedQuery<Record> query = DBHelper.getInstance().em().createQuery(critQuery);
final List<Record> result = query.getResultList();
for(final Record rec : result)
{
System.err.println(rec.toString());
}