How to build a query with Panache which depends on optional fields? - quarkus-panache

Let's say I have the following database entity:
Customer
- firstname
- lastname
- status (active | inactive)
- email
And the following (simplified) Java method:
getCustomers (String firstname, String lastname, String status, String email) {...}
If all strings are empty I want to retrieve all records from the database. If lastname and status have values I want to retrieve the relevant records. And so on. So when creating the query I need to check if the fields have values and then add these fields to the database query. How would I best implement this with Quarkus Panache?

I don't think there is a specific method to do it but if you pass the parameters as Map<String, Object>:
public List<Customer> getCustomers(Map<String, Object> parameters)
if ( parameters == null ) {
return Customer.listAll();
}
Map<String, Object> nonNullParams = parameters.entrySet().stream()
.filter( entry -> entry.getValue() != null )
.collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
if ( nonNullParams.isEmtpty() ) {
return Customer.listAll();
}
String query = nonNullParams.entrySet().stream()
.map( entry -> entry.getKey() + "=:" + entry.getKey() )
.collect( Collectors.joining(" and ") );
return Customer.find(query, nonNullParams);
}
or
public List<Customer> getCustomers (String firstname, String lastname, String status, String email) {
Map<String, Object> parameters = new HashMap<>();
addIfNotNull(parameters, "firstName", firstName );
addIfNotNull(parameters, "lastName", lastName );
addIfNotNull(parameters, "status", status );
addIfNotNull(parameters, "email", email );
if ( parameters.isEmtpty() ) {
return Customer.listAll();
}
String query = parameters.entrySet().stream()
.map( entry -> entry.getKey() + "=:" + entry.getKey() )
.collect( Collectors.joining(" and ") );
return Customer.find(query, parameters)
}
private static void addIfNotNull(Map<String, Object> map, String key, String value) {
if (value != null) {
map.put(key, value);
}
}

You can use filters for this, which is a Hibernate feature supported by Panache. It's not very well documented, so here is a working example.
Your database entity:
#Entity
#Table(name = "book")
#FilterDef(name = "Books.byISBN",
defaultCondition = "isbn = :isbn",
parameters = #ParamDef(isbn = "isbn", type = "string"))
#Filter(name = "Books.byISBN")
public class Book extends PanacheBaseEntity {}
Your web resource
#Path("book")
public class BookResource {
#Inject
BookRepository bookRepository;
#GET
#Produces(MediaType.TEXT_PLAIN)
public String index(#QueryParam("isbn") Optional<String> isbnFilter) {
final PanacheQuery<Book> bookQuery = bookRepository.findAll();
isbnFilter.ifPresent(isbn -> bookQuery.filter("Book.byISBN", Parameters.with("isbn", isbn)));
return bookQuery.list().stream()
.map(Book::name)
.collect(Collectors.joining(","));
}
}

Related

AutoMapper 10.0.0 with F# - How to fix it without additional mapping?

I have 3 types :
type [<CLIMutable>] status = { id : Guid; name : string }
type [<CLIMutable>] container = { id : Guid; name : string; status : status}
type [<CLIMutable>] scontainer = { id : Guid; name : string; status : string}
and next configuration
let c =
MapperConfiguration(
fun config ->
config.CreateMap<container, scontainer>()
.ForMemberFs((fun sc -> sc.name), (fun opts -> opts.MapFrom(fun m _ -> m.status.name))) |> ignore
)
I'm trying to map with next code
let con = { id = Guid.NewGuid(); name = "Template 1"; container.status = { id = Guid.NewGuid(); name = "Draft" } }
let mapper = c.CreateMapper()
let sc = mapper.Map<scontainer>(con)
But member mapping isn't called and sc.status contains a string representation of status object(id and name together).
When I add new map:
config.CreateMap<status, string>().ConvertUsing(fun s -> s.name) |> ignore
then sc.status contains correct value.
Does anyone know how to make it work without additional mappings?
Next lines solves my problem:
let c =
AutoMapper.MapperConfiguration(
fun config ->
config.ShouldMapProperty <- fun _ -> false
config.ShouldMapField <- fun _ -> true
)
Metadata produced for constructor is (id, name, status) in both scontainer and container.
Metadata produced for properties is: id, name, status in both scontainer and container.
Metadata produced for fields is: id#, name#, status# in both scontainer and container.
If I don't disable properties usage then automapper will find a match between constructor parameters and properties and will use properties as resolver, which means constructor resolver will be used.
If I disable properties usage, then there will be no match between constructor parameters and fields and constructor will no be used.
I might be wrong but bug is in the next few lines in method: private void MapDestinationCtorToSource(TypeMap typeMap, List ctorParamConfigurations)
in next code:
var resolvers = new LinkedList();
var canResolve = typeMap.Profile.MapDestinationPropertyToSource(typeMap.SourceTypeDetails, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers, IsReverseMap);
if ((!canResolve && parameter.IsOptional) || IsConfigured(parameter))
{
canResolve = true;
}
Here is a test that reproduces a bug
using AutoMapper;
using System;
using Xunit;
namespace ConstructorBug
{
public class Status
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Container
{
public Guid Id { get; set; }
public string Name { get; set; }
public Status Status { get; set; }
}
public class SContainer
{
public SContainer()
{
}
public SContainer(string id, string name, string status)
{
Id = id;
Name = name;
Status = status;
}
public string Id { get; set; }
public string Name { get; set; }
public string Status { get; set; }
}
public class ConstructorBug
{
[Fact]
public void ConstructorMapBug()
{
var mapperConfiguration = new MapperConfiguration(
config =>
{
config.CreateMap<Container, SContainer>()
.ForMember(dest => dest.Status, opts => opts.MapFrom(x => x.Status.Name));
}
);
var mapper = mapperConfiguration.CreateMapper();
var con = new Container
{
Id = Guid.NewGuid(),
Name = "Container 1",
Status = new Status { Id = Guid.NewGuid(), Name = "Draft" }
};
var scon = mapper.Map<SContainer>(con);
Assert.Equal(scon.Id, con.Id.ToString());
Assert.Equal(scon.Name, con.Name);
Assert.Equal(scon.Status, con.Status.Name);
}
}
}

How to search List of name which are present in cosmos documents

I have a list of name. I need to find which name is present in FirstName or LastName Property of Document in Collection.
I had tried the Linq query to archive this but it through error ("Object reference not set to an instance of an object.").
public class UserDoc
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DocumentType { get { return "userdoc"; } private set { } }
}
List<string> Names = new List<string>() { "satya", "singh" };
IEnumerable<UserDoc> Users = await _dBRepository.GetItemsAsync<UserDoc>
(x => (Names.Contains(x.FirstName + " " + x.LastName))&& x.DocumentType == "userdoc");
public async Task<IEnumerable<T>> GetItemsAsync<T>(Expression<Func<T, bool>> predicate) where T : class
{
IDocumentQuery<T> query = _client.CreateDocumentQuery<T>(documentCollectionUri:
UriFactory.CreateDocumentCollectionUri(databaseId: _databaseId, collectionId: _collectionId),
feedOptions: new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true })
.Where(predicate)
.AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<T>());
}
return results;
}
According to your description. You code should be modified as below:
IEnumerable<UserDoc> Users = await _dBRepository.GetItemsAsync<UserDoc>
(x => (Names.Contains(x.FirstName)|| Names.Contains(x.LastName))&& x.DocumentType == "userdoc");
I have tested the code and it worked.
According to your error message, i think the the most likely reason is that the object _client point to Null, please check it and try again.

ArangoDb.Net upsert always insert

I'm making a small application with CRUD functions with ArangoDatabase and its driver:
http://www.arangoclient.net/
Here is my code:
var insert = new Account
{
Email = "email01#gmail.com",
FirstName = "Adam",
LastName = "Smith"
};
var update = new Account
{
Email = "email01#gmail.com",
FirstName = "John",
LastName = "Peterson"
};
using (var arangoDatabase = new ArangoDatabase(new DatabaseSharedSetting()
{
Url = "http://127.0.0.1:8529/",
Database = "_system",
Credential = new NetworkCredential()
{
UserName = "root",
Password = "xvxvc"
}
}))
{
arangoDatabase.Query()
.Upsert(_ => new Account() {Email = insert.Email},
_ => insert, ((aql, x) => update))
.In<Account>()
.Execute();
}
For the first time running, [insert] object is added to database.
Therefore, my database now is :
But at the second time of running code, it throws me an error :
unique constraint violated (while executing). ErrorNumber: 1210 HttpStatusCode: 409
The question is: What is my problem and how to solve it?
Thank you,
Problem could be upsert search expression serialization:
Assume Account class definition is:
public class Account
{
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
upsert search expression: new Account() {Email = insert.Email} will serializes to:
{ Email: "email01#gmail.com", FirstName: null, LastName: null }
but what is expected is:
{ Email: "email01#gmail.com" }
Since search expression will never found a document, then insert will occur and you get unique constraint violated.
There are two solution to avoid serializing FirstName and LastName members:
One is we could use Json.net JsonProperty attribute to ignore nulls in serialization:
public class Account
{
public string Email { get; set; }
[Newtonsoft.Json.JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string FirstName { get; set; }
[Newtonsoft.Json.JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string LastName { get; set; }
}
And the other way is to use an anonymous object for search expression:
arangoDatabase.Query()
.Upsert(_ => new Account() {Email = insert.Email}
// should be
arangoDatabase.Query()
.Upsert(_ => new {Email = insert.Email}
One note about using anonymous object is that Email member could resolve to something else base on what you specify for its naming convention, for example:
public class Account
{
[DocumentProperty(Identifier = IdentifierType.Key)]
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
If you specify Email as a Key identifier, then you should use _key in the anonymous object:
arangoDatabase.Query()
.Upsert(_ => new { _key = insert.Email }

create sort on list for web API controller

I am writing my first web API controller so I am a bit of a noob in this area. I am trying to retrieve a list of data through a static class called CustomerDataSource:
public static class CustomerDataSource
{
public static List<Customer> customerData
{
get
{
Customer customer1 = new Customer() { name = "Bert", address = "London" };
Customer customer2 = new Customer() { name = "Jon", address = "New York" };
List<Customer> listCustomers = new List<Customer>();
listCustomers.Add(customer1);
listCustomers.Add(customer2);
return listCustomers;
}
}
}
public class Customer
{
public string name { get; set; }
public string address { get; set; }
}
I am a bit stuck with my ApiController because I am trying to sort the list either on 'name' or 'address' but using a string called 'field' does not compile. What would be a good implementation for a WebAPI controller GETmethod which provides for sorting on one of the Customer properties ?
public class ValuesController : ApiController
{
// GET api/values
public List<Customer> Get(string field)
{
var list = CustomerDataSource.customerData.OrderBy(field);
}
}
Create an extension method like below, then you can use it anywhere within the same namespace in your project.
public static class extensionmethods
{
public static IQueryable<T> OrderByPropertyName<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var rs = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(rs);
}
}
Then you can use it like:
public List<Customer> Get(string PropertyName)
{
var list = CustomerDataSource.customerData.AsQueryable().OrderByPropertyName("PropertyName",true).ToList();
}
Note:
Because the extension method uses IQueryable and returns IQuerybale, so you need to convert your List to IQueryable. Also you can order the list by ascending and descending order, just pass the boolean type value to the second parameter. The default is ascending.
You need to use a lambda expression.
if (field == "name")
var list = CustomerDataSource.customerData.OrderBy(d => d.name);
else if (field == "address")
var list = CustomerDataSource.customerData.OrderBy(d => d.address);

How do insert a record into a from a string using split function in MVC4?

Hi all i have a string like this which i am passing an ajax function to my controller action method
Brand1~1001=>undefined_undefined|
Category1~2001=>1001_Brand1|
Category2~2002=>1001_Brand1|
Product3~3003=>2002_Category2|
Product4~3004=>Product3~3003|
Product5~3005=>2002_Category2|
Product6~3006=>2002_Category2|
and i have an Menus table in db i had added that as an entity model to my project
Menus
[MenuID],[MenuName],[ParentID]
and i have model like this
public class MenuItems
{
public List<MenuItems> GetALL { get; set; }
public int MenuId { get; set; }
public string MenuName { get; set; }
public int parentId { get; set; }
}
now i want to split the string i have and insert into the above table like this
[MenuID],[MenuName],[ParentID]
1001 ,Brand1 ,null
2001 ,category1 ,1001
2002 ,category2 ,1001
3003 ,product3 ,2002
3004 ,product4 ,3003
3005 ,product5 ,2002
3006 ,product6 ,2002
in the above string Brand1~1001=>undefined_undefined| here Brand1~1001 is the parentmenu and 1001 is the id of the menu
Category1~2001=>1001_Brand1| and here Category1~2001 is the sub menu of the 1001_Brand1 i think you all got waht i amtrying to do can any one help me here please
what i amtrying
public ActionResult MenuDrag()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MenuDrag(string menustring)
{
if (!String.IsNullOrEmpty(menustring))
{
string[] menus = menustring.Split('|');
foreach (var m in menus)
{
string[] list = m.Split('>');
//stuck from here confused what to do next and how do i insert the data in my accordingly
}
}
return View();
}
You are almost there just replace your post method with this
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MenuDrag(string menustring)
{
MenuItems items = new MenuItems();
//check the string for empty
if (!String.IsNullOrEmpty(menustring))
{
string[] menus = menustring.Split('|');
foreach (var m in menus)
{
if (m != "")
{
string[] list = m.Split('>');
string[] menu = list[0].Split('~');
string[] parents = list[1].Split('~');
items.MenuItemID = Convert.ToInt16(menu[1]);
items.MenuName = menu[0].ToString();
if (parents[0] == "undefined")
{
items.ParentID = 0;
db.MenuItems.Add(items);
db.SaveChanges();
}
else
{
int parentid=Convert.ToInt16(parents[0]);
var menuid = from me in db.MenusMains where me.MenuItemID == parentid select new { MenuID = me.MenuID };
foreach (var id in menuid)
{
items.ParentID = Convert.ToInt16(id.MenuID);
}
db.MenuItems.Add(items);
db.SaveChanges();
}
}
}
}
return View();
}
}
i had used
if (m != "")
{
}
since u may get an index out of bound exception there since when in your string at this
string[] menus = menustring.Split('|');
u will get an empty ('|') you have to handle this
hope this works

Resources