How to implement a many to many mapping when using Quarkus and Mutiny - quarkus-panache

How can a many to many mapping be done in Quarkus when using hibernate-reactive-panache extension together with reactive-pg-client. I tried it, failed terribly and resorted to the non-reactive way of implementing it. Here is my non-reactive way of implementing it.
#Entity
#Table(name = "categories")
public class CategoryEntity extends PanacheEntityBase{
//some code here
#Fetch(FetchMode.JOIN)
#ManyToMany(mappedBy = "categories")
#JsonManagedReference
public Set<ProductEntity> products = new HashSet<>();
public void addProduct(ProductEntity product){
boolean added = products.add(product);
if(added){
product.categories.add(this);
}
}
#Transactional
public static void create(Category category){
CategoryEntity entity = new CategoryEntity();
entity.description = category.description;
entity.name = category.name;
ProductEntity pe = ProductEntity.findByName(category.productName);
if(pe != null){
pe.addCategory(entity);
}
entity.persistAndFlush();
}
Here is the ProductEntity...
#Table(name = "products")
#Entity
public class ProductEntity extends PanacheEntityBase{
#Fetch(FetchMode.JOIN)
#ManyToMany(
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
})
#JoinTable(
name = "product_category",
joinColumns = {#JoinColumn(name = "product_id")},
inverseJoinColumns = {#JoinColumn(name = "category_id")},
uniqueConstraints = {
#UniqueConstraint(columnNames = {"product_id", "category_id"})
}
)
#JsonBackReference
public Set<CategoryEntity> categories = new HashSet<>();
public void addCategory(CategoryEntity category){
boolean added = categories.add(category);
if(added){
category.products.add(this);
}
}
public static ProductEntity findByName(String name){
return find("name", name).firstResult();
}
#Transactional
public static void create(Product product){
ProductEntity entity = new ProductEntity(
product.name, product.imageURL, product.description, product.status, product.price
);
CategoryEntity ce = CategoryEntity.findByName(product.categoryName);
if(ce != null){
ce.addProduct(entity);
}
entity.persistAndFlush();
}
The main roadblock that I faced when I implemented it in a reactive way was I could not call a category from the database in order to link it with a product I was creating. Not that the code couldn't compile it just couldn't run.
When creating a product I wrote the following code...
public static Uni<ProductEntity> create(Product product){
ProductEntity entity = new ProductEntity(
product.name, product.imageURL, product.description, product.status, product.price
);
//this adding of a category could not work i.e a category in the database
//would not be retrieved to be linked with the product I was creating.
CategoryEntity.findByName(product.categoryName)
.onItem().transform(category -> entity.addCategory(category));
return entity.persistAndFlush();
I also tried
CategoryEntity cEntity = CategoryEntity.findByName(product.categoryName).await().indefinitely();
productEntity.addCategory(cEntity);
I would be surprised with a runtime error even after annotating the method with #Blocking
NOTE: I have some pojos, Product and Category. The Product POJO has a categoryName, and the Category POJO has a productName. I use them when creating new products and categories.
Thanks for your help in advance.

Related

MVC5 - Entity loaded in base controller conflicting with entity updates elsewhere

I have a base controller that loads an EF6 entity in a protected Dictionary member. But somehow it stops other controllers from updating that entity. Below is the simplified code -
public abstract class BaseController : Controller {
protected IDictionary<string, int> MyList;
public BaseController() {
MyList = new Dictionary<string, int>();
foreach (var rc in db.MyTable.Where(r => r.IsActive).ToList())
MyList.Add(rc.Name, rc.Id);
ViewBag.MyListViewBag = MyList;
}
}
public class MyController : BaseController {
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Name,...other properties")] MyTable mt) {
if (ModelState.IsValid)
{
db.Entry(mt).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mt);
}
}
The error is "Attaching an entity of type failed because another entity of the same type already has the same primary key value" and it is thrown at db.Entry(mt).State = EntityState.Modified;
As you can see, BaseController loads the entity in a list first, that should severe any connections (at least I thought it would). Is there a way around it?
Your mt variable, which you take from action arguments, is not a part of EF tracked objects. So, you should take it from db and then update it's necessary properties:
public ActionResult Edit([Bind(Include = "Id,Name,...other properties")] MyTable mt)
{
if (ModelState.IsValid)
{
var temp = db.MyTable.Single(mt.Id);
temp.Name = mt.Name;
//copying of other properties...
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mt);
}

How to get data from two occurences of same partial view using encapsulting class model

I have an autocompleter as a partial view which I would like to use twice in the same page. There are three models involved
public partial class CustomParent
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 CustomParentId { get; set; }
public Geoname ParentGeoname { get; set; }
public Geoname ChildGeoname { get; set; }
public CustomParent()
{
ParentGeoname = new Geoname();
ChildGeoname = new Geoname();
}
}
public partial class Geoname
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 GeonameId { get; set; }
public string Name { get; set; }
}
public partial class GeonameWithFilter
{
public GeonameWithFilter()
{
FilterString = "";
}
public Geoname Geoname { get; set; }
public String FilterString { get; set; }
}
I have a controller set up
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CustomParentId,ParentGeoname,ChildGeoname")] CustomParent customParent)
{...}
I set up the two Html partials on my create view initially only using the customparent and geoname models and it worked fine setting the values in of the parent and child geonames as expected. I now require additional parameters to be passed to the partial view so I created an encapsulating class (GeonameWithFilter). I made changes to my partial view and to my two html.partials on the view page which now look like this :
#Html.Partial("_GeonameAutocomplete",new GeonameWithFilter(){Geoname = Model.ParentGeoname,FilterString="'featurecodes':'CUPA'"},
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", new GeonameWithFilter() { Geoname = Model.ChildGeoname, FilterString = "'featurecodes':'ADM1,ADM2,ADM3,ADM4'" },
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ChildGeoname" }
})
The problem is that the customparent.parentGeoname and customparent.childGeoname are not now getting returned to my controller. I'm guessing this is because the partial view's model is not the same class as my page models parentGeoname and childGeoname but cannot work out or find any examples of how to handle such a circumstance if indeed it is possible.
Well - it took me most of a day, but I now have what I required.
I gave up on the encapsulation and instead added my basic geoname model to the new ViewDataDictionary and nullified the default model for the Html.Partial.
I then added the filterstring parameter as a key in a new ViewDataDictionary which I used as the argument for the one with the model and TemplateInfo.
Many thanks to https://stackoverflow.com/users/7714/craig-stuntz for his answer to a different question Shorthand for creating a ViewDataDictionary with both a model and ViewData items? that pointed me in the direction I have gone.
My autocompleter now just use #ViewData["FilterString"] to access the filter parameters. The GeonameWithFilter encapsulation is no longer needed. My two Html.Partials on my view page now look like this:
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { {"FilterString", "featurecodes:'CUPA'" }})
{
Model = Model.ParentGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { { "FilterString", "featurecodes:'ADM1,ADM2,ADM3,ADM4'" } })
{
Model = Model.ChildGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ChildGeoname" }
})
If anyone knows a better way to achieve the end result I would still like to hear it.

Sort JPA relational entity lists

I'm using PrimeFaces to display information in a DataTable. The data in the datatable is actually a List of entity objects which is generated by OpenJPA. And the entities have relations to other entities. This means that there are Lists inside Lists.
For example, an entity called Authors, which has many Books. The data List<Authors> is listed in the datatable. And in order to sort the List i use Collections.sort() which of course doesn't work when i try to sort on the Authors book title. Because the title field is an instance of the Book class.
How do i go about to sort the Lists when there are relationships like this?
Thanks in advance.
Here is an example of sorting by books title, you need to sort your books list :
public static void main(String args[]) {
List<Author> list = new ArrayList<Author>();
for (int i=0;i<5;i++){
Author a = new Author();
a.setName("author"+i);
List<Book> books = new ArrayList<Book>();
for(int j=0;j<4;j++){
Random r = new Random();
char c = (char) (r.nextInt(26) + 'a');
Book b = new Book();
b.setTitle(c+"title");
books.add(b);
}
a.setBooks(books);
list.add(a);
}
/*
* At this point of time you have Authors list which you want to sort by book title.
* So you can do something like below if you want to do it through Collections.sort
*/
for(Author a : list){
Collections.sort(a.getBooks(), new Comparator<Book>(){
#Override
public int compare(Book o1, Book o2) {
return o1.getTitle().compareToIgnoreCase(o2.getTitle());
}});
}
System.out.println(list);
}
Author and Book used in above example:
import java.util.List;
public class Author {
private List<Book> books;
private String name;
#Override
public String toString() {
return "Author [books=" + books + ", name=" + name + "]";
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Book.java:
public class Book {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#Override
public String toString() {
return "Book [title=" + title + "]";
}
}

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);

foreach collection reference type nested property update

I see numerous examples on foreach collection reference type property update, but not quite what I am struggling with. What if you want to update a property of a property of the reference type item? like so:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public EmployeeType EmpType { get; set; }
}
public class EmployeeType
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class1
{
private IList<Employee> existingEmp;
public void edit()
{
var dbEmployees = GetExistingEmployees();
IList<Employee> employees = new List<Employee> {
new Employee{ Id = 1, Name="me", EmpType = new EmployeeType { Id = 1}},
new Employee{ Id = 2, Name="me again", EmpType = new EmployeeType { Id = 2}}
};
foreach (var emp in employees)
{
foreach (var oldEmp in dbEmployees)
{
if (emp.Id == oldEmp.Id)
{
UpdateChanges(emp, oldEmp);
existingEmp.Add(oldEmp);
}
}
}
}
private void UpdateChanges(Employee emp, Employee oldEmp){
if (oldEmp.EmpType.Id != emp.EmpType.Id)
{
LogChange();
oldEmp.EmpType.Id = emp.EmpType.Id;
}
}
private void LogChange()
{
throw new NotImplementedException();
}
//data access layer
public IList<Employee> GetExistingEmployees()
{
throw new NotImplementedException();
}
}
The issue here is the last employee in the collection if his/her employee type property Id changed in a ddl, updating it will cascade to all other employees' emp type in the collection. That is nutts. Due to the logging requirement I can not use lambda or other fancy construct. I need hep with fixing this with in foreach or for loops.
EDIT:
As expected the same code structure works somewhere else in my application. I don't get the last item's property updating bleeding to other items' properties.
I solved this using a hacky approach:
private void UpdateChanges(Employee emp, Employee oldEmp){
var oldEmpTemp = GetEmployeeById(oldEmp.Id);
if (oldEmp.EmpType.Id != emp.EmpType.Id)
{
LogChange();
oldEmpTemp.EmpType.Id = emp.EmpType.Id;
}
//instead of updating the collection items
// and bulk updating in the db, update directly in the db
UpdateEmployee(oldEmpTemp);
}
But still can't explain why it's not working for this instance.

Resources