Unstanding JPA propagation of updates across related entities - jpa

UPDATED: I've taken Chris's answer into account but it didn't help - I'm still facing the issue. I've updated the code below to incorporate Chris's answer. Something to note is that when implementing Chris's suggestion, the relations were persisted but not reflected on the view.xhtml page. I had to replace the db object with the object returned form the call to GenericDao.update().
I've got the following relations:
One Customer to many PurchaseOrders (PO)
One PO to many Invoices.
I've read up on bi-directional relations and I know that if I have a bi-directional relation, I should update both sides of the relation when updating entities.
I perform the following steps:
Create a customer (Customer 1)
Create a purchase order (Order 1) linked to Customer 1
Create an invoice (Invoice 1) linked to Order 1.
What I observe is that all entities and relations are persisted but the list of Customer's Orders is not displayed.
view Customer:
view PurchaseOrder:
DB queries:
> select * from customer;
> +----+------------+
> | ID | NAME |
> +----+------------+
> | 1 | Customer 1 |
> +----+------------+
> 1 row in set (0.00 sec)
>
> mysql> select * from purchaseorder;
> +----+---------+-------------+
> | ID | NAME | customer_id |
> +----+---------+-------------+
> | 1 | Order 1 | 1 |
> +----+---------+-------------+
> 1 row in set (0.00 sec)
>
> mysql> select * from invoice;
> +----+-----------+------------------+
> | ID | NAME | purchaseorder_id |
> +----+-----------+------------------+
> | 1 | Invoice 1 | 1 |
> +----+-----------+------------------+
> 1 row in set (0.00 sec)
The DB reflects the relation between the configured entities so I know my changes are being persisted and, as far as I can tell, I've implemented the Customer-PO relation the same ways as the PO-Invoice relation. Since the list of Invoices for PO is updated correctly, I don't think I have a systemic issue so there must be something different between the implementation of Customers-PO and PO-Invoices relations but I can't spot it.
Why don't I see a list of PO's for my Customer even though there are clearly PO's configured for the customer?
Any help will be appreciated.
Classes(truncated for brevity):
Entities
Customer
private int id; //#Id and #GeneratedValue(IDENTITY) on getter
private String name;
#OneToMany(mappedBy="customer")
private Set<PurchaseOrder> purchaseOrders;
public Customer()
{
purchaseOrders = new HashSet<PurchaseOrder> ();
}
public Set<PurchaseOrder> getPurchaseOrders()
{
return this.purchaseOrders;
}
public void setPurchaseOrders(Set<PurchaseOrder> orders)
{
this.purchaseOrders = orders;
}
public void addPurchaseOrder(PurchaseOrder purchaseOrder)
{
this.purchaseOrders.add(purchaseOrder);
//this IF is important for avoiding an infinite loop
if (purchaseOrder.getCustomer() != this)
{
purchaseOrder.setCustomer(this);
}
}
public void removePurchaseOrder(PurchaseOrder purchaseOrder)
{
this.purchaseOrders.remove(purchaseOrder);
//this IF is important to avoid an infinite loop
if(purchaseOrder.getCustomer() != null)
{
purchaseOrder.removeFromCustomer(this);
}
}
PO
private int id; //#Id and identity column
private String name;
#ManyToOne
#JoinColumn(name="customer_id")
private Customer customer;
#OneToMany(mappedBy="purchaseOrder")
private Set<Invoice> invoices;
public PurchaseOrder() {
invoices = new HashSet<Invoice> ();
}
public Customer getCustomer()
{
return this.customer;
}
public void setCustomer(Customer customer)
{
this.customer = customer;
}
public void addToCustomer(Customer customer)
{
//this IF is important for avoiding an infinite loop
if(!customer.getPurchaseOrders().contains(this))
{
customer.addPurchaseOrder(this);
}
this.customer = customer;
}
public void removeFromCustomer(Customer customer)
{
//this IF is important for avoiding an infinite loop
if(customer.getPurchaseOrders().contains(this))
{
customer.removePurchaseOrder(this);
}
this.customer = null;
}
public Set<Invoice> getInvoices()
{
return this.invoices;
}
public void setInvoices(Set<Invoice> invoices)
{
this.invoices = invoices;
}
public void addInvoice(Invoice invoice)
{
this.invoices.add(invoice);
//this IF is important for avoiding an infinite loop
if (invoice.getPurchaseOrder() != this)
{
invoice.addToPurchaseOrder(this);
}
}
public void removeInvoice(Invoice invoice)
{
this.invoices.remove(invoice);
//this IF is important to avoid an infinite loop
if(invoice.getPurchaseOrder() != null)
{
invoice.removeFromPurchaseOrder(this);
}
}
Invoice
private int id; //#Id and identity column
private String name;
#ManyToOne
#JoinColumn(name="purchaseorder_id")
private PurchaseOrder purchaseOrder;
public Invoice() {
}
public PurchaseOrder getPurchaseOrder()
{
return this.purchaseOrder;
}
public void setPurchaseOrder(PurchaseOrder purchaseOrder)
{
this.purchaseOrder = purchaseOrder;
}
public void addToPurchaseOrder(PurchaseOrder purchaseOrder)
{
//this IF is important for avoiding an infinite loop
if(!purchaseOrder.getInvoices().contains(this))
{
purchaseOrder.addInvoice(this);
}
this.purchaseOrder = purchaseOrder;
}
public void removeFromPurchaseOrder(PurchaseOrder purchaseOrder)
{
//this IF is important for avoiding an infinite loop
if(purchaseOrder.getInvoices().contains(this))
{
purchaseOrder.removeInvoice(this);
}
this.purchaseOrder = null;
}
GenericDao (Parent of all other DAOs)
#Stateful
public class GenericDao<T extends Serializable, PK> implements IGenericDao<T, PK>
{
#PersistenceContext(unitName = "my_PU")
protected EntityManager em;
private Class<T> type;
public Class<T> getType()
{
return type;
}
public void setType(Class<T> type)
{
this.type = type;
}
public void create(T newObject)
{
em.persist(newObject);
}
public T read(PK id)
{
return em.find(type, id);
}
public T update(T transientObject)
{
return em.merge(transientObject);
}
public void delete(T objectToDelete)
{
em.remove(objectToDelete);
}
public T getResultObject(String namedQuery, Map<String, Object> criteria)
throws DatabaseException
{
List<T> records = getResultSetList(namedQuery, criteria);
if(records.isEmpty())
{
return null;
}
else if (records.size() != 1)
{
throw new DatabaseException("Too many records found!");
}
else
{
return records.remove(0);
}
}
}
Controllers
CustomerController
#RequestScoped
public class CustomerController extends FormRequestController
{
#Inject
private HTMLDataTableActionBean htmlDataTableActionBean;
#EJB
private ICustomerDao customerDao;
#Inject
private Customer customer;
#PostConstruct
public void init() throws DatabaseException
{
setEntityObjectList(findAll());
if (null == this.getCustomer())
{
setCustomer(new Customer());
}
}
public void processRequest(FormActionToPerform action) throws DatabaseException
{
switch (action)
{
case SHOW_ADD_VIEW:
setCustomer(new Customer());
break;
case SHOW_VIEW_FOR_LIST:
setEntityObjectList(findAll());
break;
case SHOW_EDIT_VIEW:
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
setCustomer((Customer) getHtmlDataTableActionBean()
.getSelectedEntityObject());
break;
case SHOW_DELETE_VIEW:
setCustomer((Customer) getHtmlDataTableActionBean()
.getSelectedEntityObject());
delete();
break;
}
}
public String doShowUIView(FormActionToPerform action)
{
String responseURL = "fail.xhtml";
if (null == this.customer)
{
return responseURL;
}
else
{
switch (action)
{
case SHOW_ADD_VIEW:
responseURL = "customer.xhtml";
break;
case SHOW_EDIT_VIEW:
responseURL = "customer.xhtml";
break;
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
responseURL = "viewCustomer.xhtml";
break;
case SHOW_DELETE_VIEW:
responseURL = "customerList.xhtml";
break;
case SHOW_VIEW_FOR_LIST:
if (this.entityObjectList.size() == 0)
{
setErrorMessage("No customers to display");
}
responseURL = "customerList.xhtml";
break;
default:
responseURL = "index.xhtml";
}
}
return responseURL;
}
public String save()
{
String url = "success.xhtml";
Customer existingCustomer = null;
try
{
existingCustomer =
customerDao.getCustomerByName(this.getCustomer().getName());
if(existingCustomer != null)
{
//there's already a customer with this name, don't make a new one
setErrorMessage("Customer already exists");
url = "fail.xhtml";
}
customerDao.update(customer);
}
catch (DatabaseException e)
{
setErrorMessage(e.toString());
e.printStackTrace();
url = "fail.xhtml";
}
return url;
}
}
POController
#RequestScoped
public class PurchaseOrderController extends FormRequestController
{
#Inject
private HTMLDataTableActionBean htmlDataTableActionBean;
#EJB
private IPurchaseOrderDao purchaseOrderDao;
#EJB
private IInvoiceDao invoiceDao;
#EJB
private ICustomerDao customerDao;
#Inject
private PurchaseOrder purchaseOrder;
private List<SelectItem> customerList;
private String selectedCustomer;
#PostConstruct
public void init() throws DatabaseException
{
setEntityObjectList(findAll());
if (null == purchaseOrder)
{
purchaseOrder = new PurchaseOrder();
setEditMode(false);
}
}
public void processRequest(FormActionToPerform action)
throws DatabaseException
{
switch (action)
{
case SHOW_ADD_VIEW:
setPurchaseOrder(new PurchaseOrder());
break;
case SHOW_VIEW_FOR_LIST:
setEntityObjectList(findAll());
break;
case SHOW_EDIT_VIEW:
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
{
setPurchaseOrder(
(PurchaseOrder)getHtmlDataTableActionBean().
getSelectedEntityObject());
}
break;
case SHOW_DELETE_VIEW:
{
setPurchaseOrder(
(PurchaseOrder)getHtmlDataTableActionBean().
getSelectedEntityObject());
delete();
}
break;
}
}
String doShowUIView(FormActionToPerform action)
{
String responseURL = "fail.xhtml";
switch (action)
{
case SHOW_ADD_VIEW:
responseURL = "purchaseOrder.xhtml";
break;
case SHOW_EDIT_VIEW:
setEditMode(true);
setComponent(null);
responseURL = "purchaseOrder.xhtml";
break;
case SHOW_DELETE_VIEW:
case SHOW_VIEW_FOR_LIST:
if (this.entityObjectList.size() == 0)
{
setErrorMessage("No orders to display");
}
responseURL = "purchaseOrderList.xhtml";
break;
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
responseURL = "viewPurchaseOrder.xhtml";
break;
default:
responseURL = HOME;
}
return responseURL;
}
public String save()
{
String responseURL = "fail.xhtml";
try
{
PurchaseOrder dbPurchaseOrder =
purchaseOrderDao.getPurchaseOrderByName(purchaseOrder.getName());
if(dbPurchaseOrder == null)
{
dbPurchaseOrder = purchaseOrder;
}
Customer customer = customerDao.getCustomerByName(selectedCustomer);
dbPurchaseOrder.addToCustomer(customer);
purchaseOrder = purchaseOrderDao.update(dbPurchaseOrder);
//replace the not-yet-persisted dbPurchaseOrder object in customer
//with the persisted purchaseOrderobject returned from the update()
//call above.
customer.removePurchaseOrder(dbPurchaseOrder);
customer.addPurchaseOrder(purchaseOrder);
customerDao.update(customer);
System.out.println("# of Purchase orders for customer: "+
purchaseOrder.getCustomer().getPurchaseOrders().size());
//Output: # of Purchase orders for customer: 1
responseURL = "success.xhtml";
}
catch (DatabaseException e)
{
e.printStackTrace();
setErrorMessage(e.toString());
responseURL = null;
}
return responseURL;
}
}
InvoicesController
#RequestScoped
public class InvoiceController extends FormRequestController
{
#Inject
private HTMLDataTableActionBean htmlDataTableActionBean;
#EJB
private IInvoiceDao invoiceDao;
#Inject
private Invoice invoice;
#EJB
private IPurchaseOrderDao purchaseOrderDao;
private List<SelectItem> purchaseOrderList;
private String selectedPurchaseOrder;
#PostConstruct
public void init() throws DatabaseException
{
setEntityObjectList(findAll());
if (null == invoice)
{
invoice = new Invoice();
setEditMode(false);
}
}
public void processRequest(FormActionToPerform action) throws DatabaseException
{
switch (action)
{
case SHOW_ADD_VIEW:
break;
case SHOW_VIEW_FOR_LIST:
setEntityObjectList(findAll());
break;
case SHOW_EDIT_VIEW:
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
{
setInvoice((Invoice)getHtmlDataTableActionBean().
getSelectedEntityObject());
}
break;
case SHOW_DELETE_VIEW:
{
setInvoice((Invoice)getHtmlDataTableActionBean().
getSelectedEntityObject());
delete();
}
break;
}
}
String doShowUIView(FormActionToPerform action)
{
String responseUrl = "fail.xhtml";
if (null == invoice)
{
System.out.println("invoice == null");
return responseUrl;
}
else
{
switch (action)
{
case SHOW_ADD_VIEW:
responseUrl = "invoice.xhtml";
break;
case SHOW_EDIT_VIEW:
setEditMode(true);
setComponent(null);
responseUrl = "invoice.xhtml";
break;
case SHOW_VIEW_TO_VIEW_SELECTED_OBJECT:
responseUrl = "viewInvoice.xhtml";
break;
case SHOW_DELETE_VIEW:
case SHOW_VIEW_FOR_LIST:
if (this.entityObjectList.size() == 0)
{
setErrorMessage("no invoices to display");
}
responseUrl = "invoiceList.xhtml";
break;
default:
responseUrl = "index.xhtml";
}
}
return responseUrl;
}
public String save()
{
String responseUrl = "fail.xhtml";
try
{
Invoice dbInvoice = invoiceDao.getInvoiceByName(invoice.getName());
if(dbInvoice == null)
{
//this is a new invoice
dbInvoice = invoice;
}
PurchaseOrder purchaseOrder =
purchaseOrderDao.getPurchaseOrderByName(selectedPurchaseOrder);
dbInvoice.addToPurchaseOrder(purchaseOrder);
invoice = invoiceDao.update(dbInvoice);
//replace the not-yet-persisted dbInvoice object in purchaseOrder
//with the persisted invoice object returned from the update() call above.
purchaseOrder.removeInvoice(dbInvoice);
purchaseOrder.addInvoice(invoice);
purchaseOrderDao.update(purchaseOrder);
System.out.println("# of Invoices for purchase order: "+
invoice.getPurchaseOrder().getInvoices().size());
//Output: # of Invoices for purchase order: 1
responseUrl = "success.xhtml";
}
catch (Exception e)
{
System.out.println(e.toString());
e.printStackTrace();
setErrorMessage(e.toString());
}
return responseUrl;
}
}
FormRequestController
public abstract class FormRequestController implements NavigationConstants
{
protected enum FormActionToPerform {
SHOW_ADD_VIEW,
SHOW_EDIT_VIEW,
SHOW_DELETE_VIEW,
SHOW_VIEW_TO_VIEW_SELECTED_OBJECT,
SHOW_VIEW_FOR_LIST;
}
protected FacesContext context;
protected List<?> entityObjectList;
private UIComponent component;
protected boolean editMode;
protected String componentId = null;
public String showViewDataTable() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_VIEW_FOR_LIST);
return doShowUIView(FormActionToPerform.SHOW_VIEW_FOR_LIST);
}
public String showViewToAdd() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_ADD_VIEW);
return doShowUIView(FormActionToPerform.SHOW_ADD_VIEW);
}
public String showViewToEdit() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_EDIT_VIEW);
return doShowUIView(FormActionToPerform.SHOW_EDIT_VIEW);
}
public String showViewToDeleteDetails() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_DELETE_VIEW);
return doShowUIView(FormActionToPerform.SHOW_DELETE_VIEW);
}
public String showViewToViewDetails() throws DatabaseException {
processRequest(FormActionToPerform.SHOW_VIEW_TO_VIEW_SELECTED_OBJECT);
return doShowUIView(FormActionToPerform.SHOW_VIEW_TO_VIEW_SELECTED_OBJECT);
}
abstract String doShowUIView(FormActionToPerform action);
abstract void processRequest(FormActionToPerform action) throws DatabaseException;
protected void bindData() {
}
abstract String save();
abstract void delete() throws DatabaseException;
public List<?> getEntityObjectList() {
return entityObjectList;
}
public void setEntityObjectList(List<?> entityObjectList) {
this.entityObjectList = entityObjectList;
}
public FacesContext getContext() {
setContext(FacesContext.getCurrentInstance());
return context;
}
public void setContext(FacesContext context) {
this.context = context;
}
public UIComponent getComponent() {
return component;
}
public void setComponent(UIComponent component) {
this.component = component;
}
}
I'm iterating over the Customer#purchaseOrders and PurchaseOrder#invoices using a h:dataTable. SO keeps misidentifying my JSF code as misformatted code and telling me to indent it as such so I can't show my webpage code but hopefully there's enough above to spot my mistake.
Thanks in advance for your time.

You should not have logic within the set/get methods if JPA is set to use property access. The logic within the set methods will cause JPA to trigger lazy collections etc when building entities and might have other adverse affects depending on the provider internals. I would either switch your annotations so they are on the fields, or remove the
if(!purchaseOrder.getInvoices().contains(this))
{
purchaseOrder.addInvoice(this);
}
logic from the set methods. The application can still use the addInvoice and have it set both sides of the relationship as the addInvoice methods are not used by JPA when loading entities.

Related

Best way to handle complex entities (relational) with Generic CRUD functions

I have tried using this generic functions to insert-update Entities but I always thought that maybe I am doing this totally wrong so therefore I would like to have your opinions/suggestions.
These are my Insert & Update functions:
public static bool Insert<T>(T item) where T : class
{
using (ApplicationDbContext ctx = new ApplicationDbContext())
{
try
{
ctx.Set<T>().Add(item);
ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// ...
}
}
}
public static bool Update<T>(T item) where T : class
{
using (ApplicationDbContext ctx = new ApplicationDbContext())
{
try
{
Type itemType = item.GetType();
// switch statement to perform actions according which type we are working on
ctx.SaveChanges();
return true;
}
catch (Exception ex)
{
// ...
}
}
}
I have learned that i can use ctx.Entry(item).State = EntityState.Modified; and I have seen so many ways of inserting-updating entities that I am very curious on what is the easiest most manageable way of performing CRUD actions ?
I know about the repository pattern and so on but i don't have much experience with interfaces or I don't seem to fully understand whats used so I prefer not to use it till I fully get it.
my approach for that is to use IRepository pattern to wrap CRUD and to make dependencies injection easier in my application, here an example on how i do it:
Define your contract like following:
(i am simplifying the example and admitting that all your tables have an integer id -i mean it is not guid or string or whatever- )
public interface IGenericRepository<TEntity> where TEntity : class
{
#region ReadOnlyRepository
TEntity GetById(int id);
ICollection<TEntity> GetAll();
ICollection<TEntity> GetAll(params Expression<Func<TEntity, object>>[] includeProperties);
ICollection<TEntity> Query(Expression<Func<TEntity, bool>> expression, params Expression<Func<TEntity, object>>[] includeProperties);
PagedModel<TEntity> Query(Expression<Func<TEntity, bool>> expression, SortOptions sortOptions, PaginateOptions paginateOptions, params Expression<Func<TEntity, object>>[] includeProperties);
int Max(Expression<Func<TEntity, int>> expression);
#endregion
#region PersistRepository
bool Add(TEntity entity);
bool AddRange(IEnumerable<TEntity> items);
bool Update(TEntity entity);
bool Delete(TEntity entity);
bool DeleteById(int id);
#endregion
}
and then the implementation:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
#region Fields
protected DbContext CurrentContext { get; private set; }
protected DbSet<TEntity> EntitySet { get; private set; }
#endregion
#region Ctor
public GenericRepository(DbContext context)
{
CurrentContext = context;
EntitySet = CurrentContext.Set<TEntity>();
}
#endregion
#region IReadOnlyRepository Implementation
public virtual TEntity GetById(int id)
{
try
{
//use your logging method (log 4 net used here)
DomainEventSource.Log.Info(string.Format("getting entity {0} with id {1}", typeof(TEntity).Name, id));
return EntitySet.Find(id); //dbcontext manipulation
}
catch (Exception exception)
{
/// example of error handling
DomainEventSource.Log.Error(exception.Message);
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);// this is specific error formatting class you can do somthing like that to fit your needs
}
}
public virtual ICollection<TEntity> GetAll()
{
try
{
return EntitySet.ToList();
}
catch (Exception exception)
{
//... Do whatever you want
}
}
public virtual ICollection<TEntity> GetAll(params Expression<Func<TEntity, object>>[] includeProperties)
{
try
{
var query = LoadProperties(includeProperties);
return query.ToList();
}
catch (Exception exception)
{
//... Do whatever you want
}
}
public virtual ICollection<TEntity> Query(Expression<Func<TEntity, bool>> expression, params Expression<Func<TEntity, object>>[] includeProperties)
{
try
{
var query = LoadProperties(includeProperties);
return query.Where(expression).ToList();
}
catch (Exception exception)
{
//... Do whatever you want
}
}
// returning paged results for example
public PagedModel<TEntity> Query(Expression<Func<TEntity, bool>> expression,SortOptions sortOptions, PaginateOptions paginateOptions, params Expression<Func<TEntity, object>>[] includeProperties)
{
try
{
var query = EntitySet.AsQueryable().Where(expression);
var count = query.Count();
//Unfortunatly includes can't be covered with a UT and Mocked DbSets...
if (includeProperties.Length != 0)
query = includeProperties.Aggregate(query, (current, prop) => current.Include(prop));
if (paginateOptions == null || paginateOptions.PageSize <= 0 || paginateOptions.CurrentPage <= 0)
return new PagedModel<TEntity> // specific pagination model, you can define yours
{
Results = query.ToList(),
TotalNumberOfRecords = count
};
if (sortOptions != null)
query = query.OrderByPropertyOrField(sortOptions.OrderByProperty, sortOptions.IsAscending);
var skipAmount = paginateOptions.PageSize * (paginateOptions.CurrentPage - 1);
query = query.Skip(skipAmount).Take(paginateOptions.PageSize);
return new PagedModel<TEntity>
{
Results = query.ToList(),
TotalNumberOfRecords = count,
CurrentPage = paginateOptions.CurrentPage,
TotalNumberOfPages = (count / paginateOptions.PageSize) + (count % paginateOptions.PageSize == 0 ? 0 : 1)
};
}
catch (Exception exception)
{
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
}
#endregion
#region IPersistRepository Repository
public bool Add(TEntity entity)
{
try
{
// you can do some extention methods here to set up creation date when inserting or createdBy etc...
EntitySet.Add(entity);
return true;
}
catch (Exception exception)
{
//DomainEventSource.Log.Failure(ex.Message);
//or
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
}
public bool AddRange(IEnumerable<TEntity> items)
{
try
{
foreach (var entity in items)
{
Add(entity);
}
}
catch (Exception exception)
{
//DomainEventSource.Log.Failure(ex.Message);
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
return true;
}
public bool Update(TEntity entity)
{
try
{
CurrentContext.Entry(entity).State = EntityState.Modified;
}
catch (Exception exception)
{
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
return true;
}
public bool Delete(TEntity entity)
{
try
{
if (CurrentContext.Entry(entity).State == EntityState.Detached)
{
EntitySet.Attach(entity);
}
EntitySet.Remove(entity);
}
catch (Exception exception)
{
var errors = new List<ExceptionDetail> { new ExceptionDetail { ErrorMessage = exception.Message } };
throw new ServerException(errors);
}
return true;
}
public bool DeleteById(TKey id)
{
var entityToDelete = GetById(id);
return Delete(entityToDelete);
}
#endregion
#region Loading dependancies Utilities
private IQueryable<TEntity> LoadProperties(IEnumerable<Expression<Func<TEntity, object>>> includeProperties)
{
return includeProperties.Aggregate<Expression<Func<TEntity, object>>, IQueryable<TEntity>>(EntitySet, (current, includeProperty) => current.Include(includeProperty));
}
#endregion
}
I am admitting that your model classes are already created and decorated.
After this , you need to create your entityRepository like following : this is an example of managing entity called Ticket.cs
public class TicketRepository : GenericRepository<Ticket>, ITicketRepository
{
// the EntityRepository classes are made in case you have some ticket specific methods that doesn't
//have to be in generic repository
public TicketRepository(DbContext context)
: base(context)
{
}
// Add specific generic ticket methods here (not business methods-business methods will come later-)
}
After this comes the UnitOfWork class which allows us to unify entry to the database context and provides us an instance of repositories on demand using dependency injection
public class UnitOfwork : IUnitOfWork
{
#region Fields
protected DbContext CurrentContext { get; private set; }
private ITicketRepository _tickets;
#endregion
#region ctor
public UnitOfwork(DbContext context)
{
CurrentContext = context;
}
#endregion
#region UnitOfWorkBaseImplementation
public void Commit()
{
try
{
CurrentContext.SaveChanges();
}
catch (Exception e)
{
/// catch
}
}
public void Rollback()
{
foreach (var entry in CurrentContext.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Detached:
break;
case EntityState.Unchanged:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
#region complete RollBack()
private void RejectScalarChanges()
{
foreach (var entry in CurrentContext.ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Modified:
case EntityState.Deleted:
entry.State = EntityState.Modified; //Revert changes made to deleted entity.
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Detached:
break;
case EntityState.Unchanged:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
private void RejectNavigationChanges()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var deletedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(e => e.IsRelationship && !this.RelationshipContainsKeyEntry(e));
var addedRelationships = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(e => e.IsRelationship);
foreach (var relationship in addedRelationships)
relationship.Delete();
foreach (var relationship in deletedRelationships)
relationship.ChangeState(EntityState.Unchanged);
}
private bool RelationshipContainsKeyEntry(System.Data.Entity.Core.Objects.ObjectStateEntry stateEntry)
{
//prevent exception: "Cannot change state of a relationship if one of the ends of the relationship is a KeyEntry"
//I haven't been able to find the conditions under which this happens, but it sometimes does.
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var keys = new[] { stateEntry.OriginalValues[0], stateEntry.OriginalValues[1] };
return keys.Any(key => objectContext.ObjectStateManager.GetObjectStateEntry(key).Entity == null);
}
#endregion
public void Dispose()
{
if (CurrentContext != null)
{
CurrentContext.Dispose();
}
}
#endregion
#region properties
public ITicketRepository Tickets
{
get { return _tickets ?? (_tickets = new TicketRepository(CurrentContext)); }
}
#endregion
}
Now for the last part we move to our business service layer and make a ServiceBase class which will be implemented by all business services
public class ServiceBase : IServiceBase
{
private bool _disposed;
#region IServiceBase Implementation
[Dependency]
public IUnitOfWork UnitOfWork { protected get; set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
var disposableUow = UnitOfWork as IDisposable;
if (disposableUow != null)
disposableUow.Dispose();
}
_disposed = true;
}
#endregion
}
and finally one example of business service class and how to use your CRUD and play with your business rules (i am using properties injection which is not the best to do so i suggest to change it and use constructor injection instead)
public class TicketService : ServiceBase, ITicketService
{
#region fields
private IUserService _userService;
private IAuthorizationService _authorizationService;
#endregion
#region Properties
[Dependency]
public IAuthorizationService AuthorizationService
{
set { _authorizationService = value; }
}
[Dependency]
public IUserService UserService
{
set { _userService = value; }
}
public List<ExceptionDetail> Errors { get; set; }
#endregion
#region Ctor
public TicketService()
{
Errors = new List<ExceptionDetail>();
}
#endregion
#region IServiceBase Implementation
/// <summary>
/// desc
/// </summary>
/// <returns>array of TicketAnomalie</returns>
public ICollection<Ticket> GetAll()
{
return UnitOfWork.Tickets.GetAll();
}
/// <summary>
/// desc
/// </summary>
/// <param name="id"></param>
/// <returns>TicketAnomalie</returns>
public Ticket GetTicketById(int id)
{
return UnitOfWork.Tickets.GetById(id);
}
/// <summary>
/// description here
/// </summary>
/// <returns>Collection of Ticket</returns>
public ICollection<Ticket> GetAllTicketsWithDependencies()
{
return UnitOfWork.Tickets.Query(tick => true, tick => tick.Anomalies);
}
/// <summary>
/// description here
/// </summary>
/// <param name="id"></param>
/// <returns>Ticket</returns>
public Ticket GetTicketWithDependencies(int id)
{
return UnitOfWork.Tickets.Query(tick => tick.Id == id, tick => tick.Anomalies).SingleOrDefault();
}
/// <summary>
/// Add new ticket to DB
/// </summary>
/// <param name="anomalieId"></param>
/// <returns>Boolean</returns>
public bool Add(int anomalieId)
{
var anomalie = UnitOfWork.Anomalies.Query(ano => ano.Id.Equals(anomalieId), ano => ano.Tickets).FirstOrDefault();
var currentUser = WacContext.Current;
var superv = _userService.GetSupervisorUserProfile();
var sup = superv.FirstOrDefault();
if (anomalie != null)
{
var anomalies = new List<Anomalie>();
var anom = UnitOfWork.Anomalies.GetById(anomalieId);
anomalies.Add(anom);
if (anomalie.Tickets.Count == 0 && sup != null)
{
var ticket = new Ticket
{
User = sup.Id,
CreatedBy = currentUser.GivenName,
Anomalies = anomalies,
Path = UnitOfWork.SearchCriterias.GetById(anom.ParcoursId),
ContactPoint = UnitOfWork.ContactPoints.GetById(anom.ContactPointId)
};
UnitOfWork.Tickets.Add(ticket);
UnitOfWork.Commit();
}
}
else
{
Errors.Add(AnomaliesExceptions.AnoNullException);
}
if (Errors.Count != 0) throw new BusinessException(Errors);
return true;
}
public bool Update(Ticket ticket)
{
if (ticket == null)
{
Errors.Add(AnomaliesExceptions.AnoNullException);
}
else
if (!Exists(ticket.Id))
{
Errors.Add(AnomaliesExceptions.AnoToUpdateNotExistException);
}
if (Errors.Count != 0) throw new BusinessException(Errors);
UnitOfWork.Tickets.Update(ticket);
UnitOfWork.Commit();
return true;
}
public bool Exists(int ticketId)
{
var operationDbEntity =
UnitOfWork.Tickets.Query(t => t.Id.Equals(ticketId)).ToList();
return operationDbEntity.Count != 0;
}
#endregion
#region Business Implementation
//play with your buiness :)
#endregion
}
Finally,
i suggest that you redo this using asynchronous methods (async await since it allows a better management of service pools in the web server)
Note that this is my own way of managing my CRUD with EF and Unity. you can find a lot of other implementations that can inspire you.
Hope this helps,

JPA Entity not stored OneToMany relationship

i trie to run the following code.
But the child is not created to the parent Entity 'Erfasser'.
If i comment out the line erfasser.getErfasst().add(neu) everything works fine.
#PostConstruct
public void init() {
Erfasser erfasser = new Erfasser();
erfasser.setEmail("benjamin.koubik#auditweb.de");
erfasser.setPasswort("counting88");
gesamtAnzahl.einfuegenErfasser(erfasser);
Erfasst neu = new Erfasst();
neu.setDatum(new Date());
neu.setJuristische(1);
neu.setNatuerliche(0);
gesamtAnzahl.einfuegen(neu);
erfasser.getErfasst().add(neu);
gesamtAnzahl.update(erfasser);
}
Only the Erfasser itself is stored correctly in the DB.
#Entity
public class Erfasser implements Serializable {
private static final long serialVersionUID = 1L;
public Erfasser() {
super();
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int erfasser_id;
#Column(length = 50)
#Email(message = "Inkorrekt EMail")
private String email;
#Column(length = 30)
private String passwort;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(referencedColumnName = "erfasser_id", name = "erfasst_id_referenz")
private List<Erfasst> erfasst;
public int getErfasser_id() {
return erfasser_id;
}
public void setErfasser_id(int erfasser_id) {
this.erfasser_id = erfasser_id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPasswort() {
return passwort;
}
public void setPasswort(String passwort) {
this.passwort = passwort;
}
public List<Erfasst> getErfasst() {
return erfasst;
}
public void setErfasst(List<Erfasst> erfasst) {
this.erfasst = erfasst;
}
}
And here my SessionBeans:
AnzahlErfasstGesamtLocal.java
#Local
public interface AnzahlErfasstGesamtLocal {
public abstract List<Integer> gesamt();
public abstract List<Erfasst> gesamtNatuerlich();
public abstract List<Erfasst> gesamtJuristisch();
public abstract void einfuegenErfasser(Erfasser e);
public abstract void einfuegen(Erfasst e);
public abstract void update(Erfasser e);
public abstract void loeschen(Erfasst e);
}
AnzahlErfasstGesamt.java
#Stateless
#LocalBean
public class AnzahlErfasstGesamt implements AnzahlErfasstGesamtLocal {
#PersistenceContext
private EntityManager em;
public AnzahlErfasstGesamt() {
}
#Override
public List<Integer> gesamt() {
return null;
}
#Override
public List<Erfasst> gesamtNatuerlich() {
try {
TypedQuery<Erfasst> q = em.createQuery(
"SELECT COUNT(e) FROM Erfasst e WHERE e.natuerliche = 1 AND e.juristische = 0; ", Erfasst.class);
List<Erfasst> liste = q.getResultList();
if (!liste.isEmpty()) {
return liste;
} else {
return null;
}
} catch (NoResultException e) {
return null;
}
}
#Override
public List<Erfasst> gesamtJuristisch() {
try {
TypedQuery<Erfasst> q = em.createQuery(
"SELECT COUNT(e) FROM Erfasst e WHERE e.juristische = 1 AND e.natuerliche = 0; ", Erfasst.class);
List<Erfasst> liste = q.getResultList();
if (!liste.isEmpty()) {
return liste;
} else {
return null;
}
} catch (NoResultException e) {
return null;
}
}
#Override
public void einfuegen(Erfasst e) {
em.persist(e);
}
#Override
public void update(Erfasser e) {
em.merge(e);
}
#Override
public void loeschen(Erfasst e) {
em.remove(em.merge(e));
}
#Override
public void einfuegenErfasser(Erfasser e) {
em.persist(e);
}
}
There is nothing wrong with JPA - something is wrong in external code (and certainly with your description of the problem). For example I don't see where the actual erfasst list is created - if nothing happens in einfuegenErfasser (whatever that means), then you will get a NullPointerException while trying to add an element to a null list. Is that what happens?
The problem is the combination of JPA entity setup and the code using it. The JPA entity Erfasser has CascadeType.ALL, therefore the gesamtAnzahl.update(erfasser); updates the child entities erfasst with it. At the same time you do not setup the erfasser reference on the neu instance. You need to do something alog the line neu.setErfasser(erfasser) before gesamtAnzahl.update(erfasser);.
On separated line of concern, using the native German naming drives my head crazy, even though I am more German then English speaker.

deleting and then inserting values using Entity framework

My requirement is that i require to delete some rows from table and then insert some in the same table. I am using Unit of work and so both deletion and insertion are part of same transaction. But when i am trying to save the data, entity framework is duplicate key throwing error. Please find below an example and code:
example:Table name- Table1, Columns - col1(c.K), col2(C.K), col3
Row To delete- 78,1,1
RowTo Add- 78,1,1
78,2,2
My Unit of Work Class:
public class DataRepository<T> : IRepository<T> where T:class // IDisposable,
{
#region Variables
private readonly CWSEntities _context;
protected readonly IDbSet<T> _dbset;
#endregion
#region Constructors
public DataRepository()
{
_context = new CWSEntities();
_dbset = _context.Set<T>();
}
public DataRepository(CWSEntities context)
{
_context =context;
_dbset = _context.Set<T>();
}
#endregion
#region Methods
public IQueryable<T> All()
{
return _context.Set<T>();
}
// public IQueryable<T> AllInclude(params Expression<Func<T,object>>[] include)
public IQueryable<T> Include(params Expression<Func<T, object>>[] include)
{
IQueryable<T> retValue = _context.Set<T>();
foreach (var item in include)
{
retValue = retValue.Include(item);
}
return retValue;
}
public T GetById(object id)
{
return this._dbset.Find(id);
}
public IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> 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).ToList();
}
else
{
return query.ToList();
}
}
public T Add(T entity)
{
if (entity != null)
{
return _dbset.Add(entity);
}
return null;
}
public void Delete(object id)
{
T entityToDelete = _dbset.Find(id);
Delete(id);
}
public void Delete(T entityToDelete)
{
if (entityToDelete != null)
{
if (_context.Entry(entityToDelete).State == EntityState.Detached)
{
_dbset.Attach(entityToDelete);
}
_dbset.Remove(entityToDelete);
}
}
public void Update(T entityToUpdate)
{
if (entityToUpdate != null)
{
_dbset.Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
// _context.Entry(entity).State = System.Data.Entity.EntityState.Modified;
}
}
public virtual void Save()
{
try
{
_context.SaveChanges();
}
catch(DbEntityValidationException exception)
{
}
}
From what I can gather you are trying to add entities with the same primary key, as you said in your example
RowTo Add- 78,1,1 78,2,2
It doesn't look like your Add method is handling this correctly. You could first check if the entity exists by passing the primary key values of the entity and if it doesn't then do the add, if not then possibly an update?
public T Add(T entity, params object[] keys)
{
if (entity != null)
{
var existing = _dbset.Find(keys)
if (existing == null)
return _dbset.Add(entity);
else
Update(entity);
}
return null;
}

JPA Eclipselink JOIN FETCH LAZY relation returning null

I am always getting NULL from a JOIN FETCH clause in my JPA Query, even though I have everything configured as expected:
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#Entity
#Table(name = "TB_BANNER_IMAGE")
public class BannerImage extends BaseEntity<Integer> {
protected FileReference fileReference;
private String type;
private String labelTitle;
protected BannerImage() {}
#Id
#TableGenerator(name="genBannerImage", table="TB_ID_GENERATOR",
pkColumnName="ID_NAME", valueColumnName="ID_VAL",
pkColumnValue="TB_BANNER_IMAGE", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="genBannerImage")
#Column(name = "ID_BANNER_IMAGE", unique = true, nullable = false)
public Integer getId() {
return super.getId();
}
#Override
public void setId(Integer id) {
super.setId(id);
}
#Column(name="TYPE")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="ID_FILE_REFERENCE", nullable=false)
public FileReference getFileReference() {
return fileReference;
}
public void setFileReference(FileReference fileReference) {
this.fileReference = fileReference;
}
#Column(name="LABEL_TITLE")
public String getLabelTitle() {
return labelTitle;
}
public void setLabelTitle(String labelTitle) {
this.labelTitle = labelTitle;
}
}
for File Reference Class:
#Entity
#Table(name = "TB_FILE_REFERENCE")
public class FileReference extends BaseNamedEntity<String> {
private String type;
public FileReference() {}
#Id
#TableGenerator(name="genFileReference", table="TB_ID_GENERATOR",
pkColumnName="ID_NAME", valueColumnName="ID_VAL",
pkColumnValue="TB_FILE_REFERENCE", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="genFileReference")
#Column(name = "ID_FILE_REFERENCE", unique = true, nullable = false)
public String getId() {
return super.getId();
}
#Override
public void setId(String id) {
super.setId(id);
}
#Column(name = "TYPE")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Service class:
#Path("/banner")
public class BannerImageService extends BaseServiceFacade<BannerImage, Integer> {
#SuppressWarnings("unchecked")
#Override
public Crud<BannerImage, Integer> lookupService() throws ServiceLocatorException {
return ServiceLocator.getInstance()
.getLocalHome(ServicesConstants.BANNER_IMAGE_SERVICE);
}
#Override
protected String getDefaultGetQuery() {
return BannerImageDAO.GET_BY_ID_FETCH_FILE_REF;
}
#Override
protected String getDefaultQuery() {
return BannerImageDAO.GET_ALL_FETCH_FILE_REF;
}
}
get REST method of BaseServiceFacade:
#Override
#GET
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/{id}")
public T get(#PathParam("id") ID id) {
try {
if (!validateID(id)) {
logMessage("Invalid Entity ID: " + id);
return null;
}
String defaultGetQuery = getDefaultGetQuery();
if (defaultGetQuery != null) {
Map<String, Object> mapParams = new HashMap<String, Object>();
mapParams.put("id", id);
List<T> entityList = getService().search(defaultGetQuery, mapParams);
if (entityList != null && entityList.size() == 1) {
T ent = entityList.get(0);
return ent;
} else {
logMessage("Invalid search by Entity ID: " + id);
}
} else {
return getService().findById(clazz, id);
}
} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return null;
}
And finally the Service Bean EJB which reads from entityManager:
public class BaseServiceBean<T extends IEntity<ID>, ID extends Serializable> implements Crud<T,ID> {
// ... generic methods to be reused by subclasses
#Override
public List<T> search(String queryOrNamedQuery) throws ServiceException {
return search(queryOrNamedQuery, null, 0, 0);
}
#SuppressWarnings("unchecked")
public List<T> search(String namedQueryOrHql, Map<String, Object> parameters, int start, int chunkSize) {
try {
Query query = createQuery(namedQueryOrHql, getQueryType(namedQueryOrHql));
if (start > 0) {
query.setFirstResult(start);
}
if (chunkSize > 0) {
query.setMaxResults(chunkSize);
}
addParameters(query, parameters);
List<T> result = query.getResultList();
afterSearch(result);
return result;
} catch (NoResultException nre) {
nre.printStackTrace();
} catch (ClassCastException cce) {
cce.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void afterSearch(List<T> result) {
}
// etc...
implementation specific class for BannerImageService:
#Stateless(mappedName="ejb/BannerImageService")
public class BannerImageServiceBean extends BaseServiceBean<BannerImage, Integer> implements BannerImageServiceBeanRemote, BannerImageServiceBeanLocal {
#Override
protected void afterSearch(List<BannerImage> result) {
if (result != null && result.size() == 1) {
BannerImage bannerImage = result.get(0);
bannerImage.getFileReference();
}
super.afterSearch(result);
}
// additional code ...
When I try to fetch my BannerImage class together with it's corresponding FileReference member I always get NULL even though in my DB there is an existing foreign key present:
JPQL:
"SELECT a FROM BannerImage a join fetch a.fileReference WHERE a.id = :id";
Generated SQL:
SELECT t1.ID_BANNER_IMAGE, t1.LABEL_TEXT, t1.LABEL_TITLE, t1.TYPE,
t1.ID_FILE_REFERENCE, t0.ID_FILE_REFERENCE, t0.NAME,
t0.TYPE FROM TB_FILE_REFERENCE t0, TB_BANNER_IMAGE
t1 WHERE (t0.ID_FILE_REFERENCE = t1.ID_FILE_REFERENCE) AND t1.ID_BANNER_IMAGE = 1
in my DB the record shows a correct reference:
BANNER_IMAGE:
1;"";"main";"2bdbb063d0d0ee2939c89763945d9d9e";"banner1.png";"image/png"
If I execute :
select * from TB_FILE_REFERENCE where ID_FILE_REFERENCE = '2bdbb063d0d0ee2939c89763945d9d9e'
I can find the record in the DB, although my EclipseLink JPA Implementation always returns null:
EclipseLink Version 2.5.2-M1
This is how the Entity gets passed from Service Layer to the
Can someone help pointing why the JOIN FETCH is not properly working?
I faced a similar issue and looking closely I see that this issue was happening only to entities recently created/saved. Then I figured that it has something to do with eclipselink cache. I solved this problem by adding this line before making a join fetch JPQL query,
em.getEntityManagerFactory().getCache().evictAll();
em.createQuery("SELECT a FROM BannerImage a join fetch a.fileReference WHERE a.id = :id").getResultList();
HTH!

Unit testing generic repository

I'm pretty new to unit testing and I'm having some problems with regards, to unit testing a generic repository in my application. I've implemented the unit of work pattern in my ASP.NET MVC application. My classes look like this:
public class UnitOfWork : IUnitOfWork
{
private bool disposed = false;
private IGenericRepository<Shop> _shopRespository;
public UnitOfWork(PosContext context)
{
this.Context = context;
}
public PosContext Context { get; private set; }
public IGenericRepository<Shop> ShopRepository
{
get
{
return this._shopRespository ?? (this._shopRespository = new GenericRepository<Shop>(this.Context));
}
}
public void SaveChanges()
{
this.Context.SaveChanges();
}
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.Context.Dispose();
}
this.disposed = true;
}
}
}
public class PosContext : DbContext, IPosContext
{
public DbSet<Shop> Shops { get; private set; }
}
public class GenericRepository<T> : IGenericRepository<T>
where T : class
{
private readonly PosContext context;
private readonly DbSet<T> dbSet;
public GenericRepository(PosContext context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual IEnumerable<T> Get(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = this.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).ToList();
}
else
{
return query.ToList();
}
}
public virtual T Find(object id)
{
return this.dbSet.Find(id);
}
public virtual void Add(T entity)
{
this.dbSet.Add(entity);
}
public virtual void Remove(object id)
{
T entityToDelete = this.dbSet.Find(id);
this.Remove(entityToDelete);
}
public virtual void Remove(T entityToDelete)
{
if (this.context.Entry(entityToDelete).State == EntityState.Detached)
{
this.dbSet.Attach(entityToDelete);
}
this.dbSet.Remove(entityToDelete);
}
public virtual void Update(T entityToUpdate)
{
this.dbSet.Attach(entityToUpdate);
this.context.Entry(entityToUpdate).State = EntityState.Modified;
}
I'm using NUnit and FakeItEasy to write my unit tests. In my set up function, I create a UnitIfWork object with a fake PosContext object. I then populate the context with a few Shop objects.
[SetUp]
public void SetUp()
{
this.unitOfWork = new UnitOfWork(A.Fake<PosContext>());
this.unitOfWork.ShopRepository.Add(new Shop() { Id = 1, Name = "Test name1" });
this.unitOfWork.ShopRepository.Add(new Shop() { Id = 2, Name = "Test name2" });
this.unitOfWork.ShopRepository.Add(new Shop() { Id = 3, Name = "Test name3" });
this.unitOfWork.ShopRepository.Add(new Shop() { Id = 4, Name = "Test name4" });
this.unitOfWork.ShopRepository.Add(new Shop() { Id = 5, Name = "Test name5" });
this.Controller = new ShopController(this.unitOfWork);
}
It works fine when I test the Find-method of the GenericRepository. The correct Shop object is returned and I can assert that it works fine:
[TestCase]
public void DetailsReturnsCorrectShop()
{
// Arrange
int testId = 1;
// Act
Shop shop = this.unitOfWork.ShopRepository.Find(testId);
ViewResult result = this.Controller.Details(testId) as ViewResult;
// Assert
Shop returnedShop = (Shop)result.Model;
Assert.AreEqual(testId, returnedShop.Id);
}
But when I want to test that the Get-method returns all shops from the repository, if I do not give any filter params, I get an empty list back. I can't figure out why?
[TestCase]
public void IndexReturnsListOfShops()
{
// Arrange
// Act
ViewResult result = this.Controller.Index() as ViewResult;
// Assert
List<Shop> returnedShops = (List<Shop>)result.Model;
Assert.AreEqual(5, returnedShops.Count);
}
The ShopController looks like this:
public class ShopController : Controller
{
private readonly IUnitOfWork unitOfWork;
public ShopController(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
// GET: /Shop/
public ActionResult Index()
{
return View(this.unitOfWork.ShopRepository.Get());
}
// GET: /Shop/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Shop shop = this.unitOfWork.ShopRepository.Find(id);
if (shop == null)
{
return HttpNotFound();
}
return View(shop);
}
}
Can you help me figure out why I get an empty list back from the Get-method?