After successfully adding child entity with parent reference, child does not show under parent resource - spring-data-rest

I have two entities, Shelf and Book. A Shelf can have multiple Books (the relationship is bi-directional). I've exposed both of these as JpaRepositories.
Here's the issue:
I create a shelf by posting { "name":"sci-fi" } to /shelves.(success)
I create a book for that shelf by posting { "name":"mybook", "shelf":"localhost:8080/shelves/1" } to /books. (success)
When I get the book I just created at /books/1, it has the correct link to the parent shelf.
But when I go to shelves/1/books, I get an empty result, { }!
Any ideas what I might be missing?
Right now I've constructed a workaround by explicitly adding the book to its shelf in a beforeCreate event, but it seems like this should be totally unnecessary. (It does fix the problem, however.)
#HandleBeforeCreate(Book.class)
public void handleCreate(Book book) {
// why is this necessary?
book.getShelf().getBooks().add(book);
}
Here are the entity classes:
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToOne
private Shelf shelf;
public Shelf getShelf() {
return shelf;
}
public void setShelf(Shelf shelf) {
this.shelf = shelf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Book other = (Book) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
#Entity
public class Shelf {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany
private List<Book> books = new ArrayList<Book>();
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;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Shelf other = (Shelf) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
I'm using Spring Boot 1.1.8.

In your Shelf entity, add the mappedBy = "self" property to the #OneToMany annotation in books:
#OneToMany(mappedBy = "self")
private List<Book> books = new ArrayList<Book>();
This will auto populate the list of books with the books whose reference to self matches.

I think you need to update shelf to add new book you created as a last step. This is because you are using bi-directional relationship and shelf has no idea about books till you update it with the books it should hold.

Related

automatically expand related entity with OData controller

I have these classes:
public class Items
{
[Key]
public Guid Id { get; set; }
public string ItemCode { get; set; }
public decimal SalesPriceExcl { get; set; }
public decimal SalesPriceIncl { get; set; }
public virtual ICollection<ItemPrice> SalesPrices { get; set; }
public Items()
{
SalesPrices = new HashSet<App4Sales_ItemPrice>();
}
}
public class ItemPrice
{
[Key, Column(Order = 0), ForeignKey("Items")]
public Guid Id { get; set; }
public virtual Items Items { get; set; }
[Key, Column(Order=1)]
public Guid PriceList { get; set; }
public decimal PriceExcl { get; set; }
public decimal PriceIncl { get; set; }
public decimal VatPercentage { get; set; }
}
I want to query the Items and automatically get the ItemPrice collection.
I've created an OData V3 controller:
// GET: odata/Items
//[Queryable]
public IQueryable<Items> GetItems(ODataQueryOptions opts)
{
SelectExpandQueryOption expandOpts = new SelectExpandQueryOption(null, "SalesPrices", opts.Context);
Request.SetSelectExpandClause(expandOpts.SelectExpandClause);
return expandOpts.ApplyTo(db.Items.AsQueryable(), new ODataQuerySettings()) as IQueryable<Items>;
}
But I get the error:
"Cannot serialize null feed"
Yes, some Items have no ItemPrice list.
Can I get past this error, or can I do something different?
Kind regards
Jeroen
I found the underlying error is:
Unable to cast object of type
'System.Data.Entity.Infrastructure.DbQuery1[System.Web.Http.OData.Query.Expressions.SelectExpandBinder+SelectAllAndExpand1[.Models.Items]]'
to type '.Models.Items'.
I've solved it after I came across this post: http://www.jauernig-it.de/intercepting-and-post-processing-odata-queries-on-the-server/
This is my controller now:
SelectExpandQueryOption expandOpts = new SelectExpandQueryOption(null, "SalesPrices", opts.Context);
Request.SetSelectExpandClause(expandOpts.SelectExpandClause);
var result = expandOpts.ApplyTo(db.Items.AsQueryable(), new ODataQuerySettings());
var resultList = new List<Items>();
foreach (var item in result)
{
if (item is Items)
{
resultList.Add((Items)item);
}
else if (item.GetType().Name == "SelectAllAndExpand`1")
{
var entityProperty = item.GetType().GetProperty("Instance");
resultList.Add((Items)entityProperty.GetValue(item));
}
}
return resultList.AsQueryable();
Jeroen
GetItems([FromODataUri] ODataQueryOptions queryOptions)
expanding on Jeroen's post. Anytime a select or expand is involved, OData wraps the results in a SelectAll or SelectSome object; so, we need to unwrap the values rather than do an direct cast.
public static class ODataQueryOptionsExtensions
{
public static IEnumerable<T> ApplyODataOptions<T>(this IQueryable<T> query, ODataQueryOptions options) where T : class, new()
{
if (options == null)
{
return query;
}
var queryable = options.ApplyTo(query);
if (queryable is IQueryable<T> queriableEntity)
{
return queriableEntity.AsEnumerable();
}
return UnwrapAll<T>(queryable).ToList();
}
public static IEnumerable<T> UnwrapAll<T>(this IQueryable queryable) where T : class, new()
{
foreach (var item in queryable)
{
yield return Unwrap<T>(item);
}
}
public static T Unwrap<T>(object item) where T : class, new()
{
var instanceProp = item.GetType().GetProperty("Instance");
var value = (T)instanceProp.GetValue(item);
if (value != null)
{
return value;
}
value = new T();
var containerProp = item.GetType().GetProperty("Container");
var container = containerProp.GetValue(item);
if (container == null)
{
return (T)null;
}
var containerType = container.GetType();
var containerItem = container;
var allNull = true;
for (var i = 0; containerItem != null; i++)
{
var containerItemType = containerItem.GetType();
var containerItemValue = containerItemType.GetProperty("Value").GetValue(containerItem);
if (containerItemValue == null)
{
containerItem = containerType.GetProperty($"Next{i}")?.GetValue(container);
continue;
}
var containerItemName = containerItemType.GetProperty("Name").GetValue(containerItem) as string;
var expandedProp = typeof(T).GetProperty(containerItemName);
if (expandedProp.SetMethod == null)
{
containerItem = containerType.GetProperty($"Next{i}")?.GetValue(container);
continue;
}
if (containerItemValue.GetType() != typeof(string) && containerItemValue is IEnumerable containerValues)
{
var listType = typeof(List<>).MakeGenericType(expandedProp.PropertyType.GenericTypeArguments[0]);
var expandedList = (IList)Activator.CreateInstance(listType);
foreach (var expandedItem in containerValues)
{
var expandedInstanceProp = expandedItem.GetType().GetProperty("Instance");
var expandedValue = expandedInstanceProp.GetValue(expandedItem);
expandedList.Add(expandedValue);
}
expandedProp.SetValue(value, expandedList);
allNull = false;
}
else
{
var expandedInstanceProp = containerItemValue.GetType().GetProperty("Instance");
if (expandedInstanceProp == null)
{
expandedProp.SetValue(value, containerItemValue);
allNull = false;
}
else
{
var expandedValue = expandedInstanceProp.GetValue(containerItemValue);
if (expandedValue != null)
{
expandedProp.SetValue(value, expandedValue);
allNull = false;
}
else
{
var t = containerItemValue.GetType().GenericTypeArguments[0];
var wrapInfo = typeof(ODataQueryOptionsExtensions).GetMethod(nameof(Unwrap));
var wrapT = wrapInfo.MakeGenericMethod(t);
expandedValue = wrapT.Invoke(null, new[] { containerItemValue });
if (expandedValue != null)
{
expandedProp.SetValue(value, expandedValue);
allNull = false;
}
}
}
}
containerItem = containerType.GetProperty($"Next{i}")?.GetValue(container);
}
if (allNull)
{
return (T)null;
}
return value;
}
}

could not get a field value by reflection getter of Topic.id

Hello i have a RSSFEED with Maven and Jboss know i try to add Topics to a Newsfeed but i get this error:
14:02:43,526 WARN [com.arjuna.ats.arjuna] (default task-10) ARJUNA012125: TwoPhaseCoordinator.beforeCompletion - failed for SynchronizationImple< 0:ffffc0a80185:737c90a9:552bae74:4b, org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization#694357 >: javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: could not get a field value by reflection getter of Topic.id
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.spi.AbstractEntityManagerImpl$CallbackExceptionMapperImpl.mapManagedFlushFailure(AbstractEntityManagerImpl.java:1882) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.engine.transaction.synchronization.internal.SynchronizationCallbackCoordinatorNonTrackingImpl.beforeCompletion(SynchronizationCallbackCoordinatorNonTrackingImpl.java:119) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization.beforeCompletion(RegisteredSynchronization.java:50) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
I try to have a #ManytoMany with my Topic.java and News.Java
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
#Table(name = "Topic")
#Entity
public class Topic implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="seq_topic")
#SequenceGenerator(name="seq_topic", sequenceName="seq_topic", allocationSize=1)
private int id;
private String name;
public int getId() {
return id;
}
#ManyToMany (fetch = FetchType.EAGER, mappedBy = "topic")
private List<News> News = new ArrayList<>();
public List<News> getNews() {
return News;
}
public void setNews(List<News> news) {
News = news;
}
public void setId(int id) {
this.id = id;
}
public Topic(){
}
public Topic(String name){
this.name =name;
}
public String getName() {
return name;
}
public void setName(String name) {
name = name;
}
#Override
public String toString() {
return name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Topic other = (Topic) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
Now I dont really now where ist the Problem because i Have a getter and Setter Methode !

dealing with property that does not map directly onto column on the database

I have the following tables
Client Table and Product Table
ID
Name
ClientProduct Table
ID
ClientID
ProductID
Product class
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
protected string name;
public Product () { }
public Product (string name)
{
this.name = name;
}
public Product (string name)
{
this.name = name;
}
public string Name
{
get { return name; }
set { name = value; }
}
Client class
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
protected string name;
public Client () { }
public Client (string name)
{
this.name = name;
}
public Client (string name)
{
this.name = name;
}
public string Name
{
get { return name; }
set { name = value; }
}
ClientProduct class
protected Client client;
protected Product product;
public ClientProduct () { }
public ClientProduct (Client client, Product product)
{
this.client= client;
this.product= product;
}
public Client client {
get { return client; }
set { client= value; }
}
public Product product {
get { return product; }
set { product= value; }
}
How can I do the following in petaPOCO?
public static System.Collections.Generic.IList<ClientProduct> LoadForClient(Client client)
{
if (null != client)
return Load("ClientID = " + client.ID);
else
return null;
}
such that I can have list of all products for that client that I will later used in my view as
private void LoadProducts(Client client )
{
Products = ClientProduct.LoadForClient(client)
.Select(x => x.Product.Name)
.OrderBy(x => x).ToArray();
}
The 1:M and M:1 relationships seem like what you're after
http://www.toptensoftware.com/Articles/115/PetaPoco-Mapping-One-to-Many-and-Many-to-One-Relationships
You can define a custom relator callback; Brad's example for two tables (if your products mapped directly to the client) would look something like this:
var posts = db.Fetch<Product, Client, ClientProduct>(
(p,c)=> { p.client_obj = c; return p; },
#"SELECT * FROM Product
LEFT JOIN Client ON Product.clientId = Client.id ORDER BY Product.clientId
");
I realize your dealing with a M:M relationship so you would need to update the above to map across the three objects, but the concept is the same. The key is that your 3rd argument in the call (ClientProduct) represents the joined row, and you can then reference the Client and/or Products directly from the single list.

RavenDb Strange Behaviour with objects with equality members

I got to the bottom of a strange bug (in my code ) when I was trying to store a list of objects to ravendb. The problem was that the object to be stored has equality members generated by resharper. The object in question is as follows (note that I have commented out the equality members to solve the issue) -
//[DataContract(Namespace = "")]
//[KnownType(typeof(IApplicationEntity))]
public class ApplicationEntity: IApplicationEntity
{
public ApplicationEntity()
{
}
public ApplicationEntity(string processName)
{
ProcessName = processName;
Id = "Processes/" + ProcessName;
}
public ApplicationEntity(string key, string processName)
{
ProcessName = processName;
Key = key;
Id = string.Format("Processes/{0}_{1}", Key, ProcessName);
}
//[DataMember]
public string Id { get; set; }
//[DataMember]
public string Key { get; set; }
//[DataMember]
public string ProcessName { get; set; }
//[DataMember]
public string ProcessDescription { get; set; }
/// <summary>
/// used to generate sequential unique activity ID generation only.
/// </summary>
//[DataMember]
public string ActivityCount { get; set; }
//public bool Equals(ApplicationEntity other)
//{
// if (ReferenceEquals(null, other)) return false;
// if (ReferenceEquals(this, other)) return true;
// return Equals(other.ProcessName, ProcessName);
//}
//public override bool Equals(object obj)
//{
// if (ReferenceEquals(null, obj)) return false;
// if (ReferenceEquals(this, obj)) return true;
// if (obj.GetType() != typeof (ApplicationEntity)) return false;
// return Equals((ApplicationEntity) obj);
//}
//public override int GetHashCode()
//{
// return (ProcessName != null ? ProcessName.GetHashCode() : 0);
//}
}
Now if I stored the object with equality members implement then the following code produces strange results -
int count = 0;
using (var session = _store.OpenSession(_databaseName))
{
foreach (var applicationEntity in _listOfApplications)
{
var entity = new ApplicationEntity(count.ToString(), applicationEntity.ProcessName);
//ravenRepositoryCachable.Add(entity);
session.Store(entity);
count++;
}
session.SaveChanges();
}
The strange behaviour is that I would expect the Key field to be incrementing to 400 as the list has 400 members , but instead the Key for the first 10 object stored is correct i.e 0 to 9. but the 11th one started from 0 again and so on.
but if I comment the equality members off(as in the code snippet above) then this problem disappears.
Also If i add the objects one at a time as opposed to batch the problem disappears -
int count = 0;
foreach (var applicationEntity in _listOfApplications)
{
using (var session = _store.OpenSession(_databaseName))
{
var entity = new ApplicationEntity(count.ToString(), applicationEntity.ProcessName);
//ravenRepositoryCachable.Add(entity);
session.Store(entity);
session.SaveChanges();
count++;
}
}
I know I have resolved the issue but I do not understand what happened here and why only the key field was effected? This is a undefined behaviour and I am worried as bit as code is supposed to be deployed in production!! It is clear that the equality members are not to be defined. I need to get to the bottom of it , is it a bug?
No, I don't think there is such a bug in RavenDB. Instead, I guess there is another issue with your code, because this test (that is based on your example above) works for me:
public class EqualityMembersInSessionCache
{
public class ApplicationEntity
{
public ApplicationEntity(string key, string processName)
{
ProcessName = processName;
Key = key;
Id = string.Format("Processes/{0}_{1}", Key, ProcessName);
}
public string Id { get; set; }
public string Key { get; set; }
public string ProcessName { get; set; }
public bool Equals(ApplicationEntity other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.ProcessName, ProcessName);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(ApplicationEntity)) return false;
return Equals((ApplicationEntity)obj);
}
public override int GetHashCode()
{
return (ProcessName != null ? ProcessName.GetHashCode() : 0);
}
}
[Fact]
public void Has_correct_behaviour()
{
var randomNames = new List<string>();
for (int c = 1; c < 100; c++)
{
randomNames.Add(string.Format("test{0}", c));
}
using (var store = new EmbeddableDocumentStore { RunInMemory = true })
{
int count = 0;
using (var session = store.OpenSession())
{
foreach (var name in randomNames)
{
session.Store(new ApplicationEntity(count.ToString(), name));
count++;
}
session.SaveChanges();
}
using (var session = store.OpenSession())
{
var results = session.Query<ApplicationEntity>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.ToList();
Assert.NotEmpty(results);
Assert.Equal(99, results.Count);
for (int c = 0; c < 99; c++)
{
Assert.Equal(c, int.Parse(results[c].Key));
}
}
}
}
}
If you want us to investigate further on this, please provide a failing test.

Querying UserType's in NHibernate

I have the following scenario:
Let's say that my "Product" table in this legacy database has a "Categories" column of type string. This column stores the category ID's separated by some sort of ascii character. For instance: "|1|" (for category 1), "|1|2|3|" (for categories 1, 2, and 3), etc.
Instead of exposing a string property for that, I want to expose an IEnumerable, so that users of my Product class don't have to worry about parsing those values.
I'm creating a SelectedCatories type that's simply an IEnumerable, and my Product class looks like this:
public class Product
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual bool Discontinued { get; set; }
public virtual SelectedCategories Categories { get; set; }
}
I then created a SelectedCategoriesUserType class like so:
public class SeletedCategoriesUserType : IUserType
{
static readonly SqlType[] _sqlTypes = {NHibernateUtil.String.SqlType};
public bool Equals(object x, object y)
{
// Fix this to check for Categories...
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (obj == null) return null;
string[] stringCategories = obj.ToString().Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries);
var categories = new Categories();
return
new SelectedCategories(
stringCategories.Select(
stringCategory => categories.Single(cat => cat.Id == int.Parse(stringCategory)))
.ToList());
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
var theCategories = (SelectedCategories) value;
var builder = new StringBuilder();
builder.Append("|");
theCategories.ForEach(i => builder.AppendFormat("{0}|", i.Id.ToString()));
((IDataParameter) cmd.Parameters[index]).Value = builder.ToString();
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
throw new NotImplementedException();
}
public object Assemble(object cached, object owner)
{
throw new NotImplementedException();
}
public object Disassemble(object value)
{
throw new NotImplementedException();
}
public SqlType[] SqlTypes
{
get { return _sqlTypes; }
}
public Type ReturnedType
{
get { return typeof (SelectedCategories); }
}
public bool IsMutable
{
get { return false; }
}
}
I then want to build a query that gives me back any product that belongs in a specific category (say, category 2), matching both "|2|", and "|1|2|3|".
Right now, my naive implementation that barely makes my test pass looks like this:
public IEnumerable<Product> GetByCategory(Category category)
{
using (ISession session = NHibernateHelper.OpenSession())
{
return session
.CreateSQLQuery("select * from product where categories LIKE :category")
.AddEntity(typeof(Product))
.SetString("category", string.Format("%|{0}|%", category.Id))
.List()
.Cast<Product>();
}
}
My question is: what's the proper way to right that query?
A different way to do that ICriteria query would be this...
return Session
.CreateCriteria(typeof(Product), "product")
.Add(Expression.Sql(
"{alias}.categories LIKE ?",
string.Format("%|{0}|%", category.Id),
NHibernateUtil.String))
.List<Product>();
However, you may want to think about setting up a many-to-many table between Product and Category and setting up a collection of Categories in the Product class. You can still keep your field of concatenated Category Ids (I assume it's needed for legacy purposes), but tie it to the collection with something like this.
public virtual ISet<Category> Categories { get; private set; }
public virtual string CategoriesString
{
get { return string.Join("|", Categories.Select(c => c.Id.ToString()).ToArray()); }
}
Doing something like this will let you set foreign keys on your tables, and make the queries a bit easier to construct.
return Session
.CreateCriteria(typeof(Product), "product")
.CreateCriteria("product.Categories", "category")
.Add(Restrictions.Eq("category.Id", category.Id))
.List<Product>();