I have an application with two entities:
Employer (OneToMany with Employee)
Employee (ManyToOne with Employer)
Employer Entity:
#OneToMany(mappedBy = "employer")
#JsonIgnore
private Set<Employee> employees = new HashSet<>();
Employee Entity:
#ManyToOne
private Employer employer;
I know that the #JsonIgnore remove the possibility to fetch the list of employee from JSON. But I don't know what should I do to retrieve them.
Client Side
EmployerService.js:
angular.module('app')
.factory('Employer', function ($resource) {
return $resource('api/employer/:id', {}, {
'query': { method: 'GET', isArray: true},
'get': {
method: 'GET',
transformResponse: function (data) {
data = angular.fromJson(data);
return data;
}
},
'update': { method:'PUT' }
});
});
Should I create a new rest call to fetch them here, (with a new rest RequestMapping on the server side Rest Controller) and remove the #JsonIgnore?
Thank you.
So to resolve this, I used a service to retrieve the data in a Eager way.
Employer and Employee entities don't change.
EmployerService
#Service
#Transactional
public class EmployerService {
#Inject
private EmployerRepository employerRepository;
#Transactional(readOnly = true)
public Set<Employee> getAllEmployeeForEmployerId(Long id) {
Employer employer = employerRepository.findOne(id);
if ( employer == null ) {
return null;
}
employer.getEmployees().size(); //Fetch Eager on Lazily
return employer.getEmployees();
}
}
In EmployerService.js add a new factory
.factory('EmployerFactory', function($resource) {
return {
employees: $resource('api/employer/employee/:id'),
}
And add the matching RestRequest in your controller.
Related
I am working on an asp.net mvc application and I am using Entity Framework and AngularJS in it. I am using AngularJS's $http service to call an action method and retrieve data from the server. The correct data is retrieved from the server (I confirmed this by debugging), but somehow an error occurs after the action method returns the retrieved data and the error callback function is fired instead of the success callback function. And then I get a status 500 in the browser's console.
Here are the involved blocks of codes:
(From angularjs controller)
$http({
url: rootUrl + "User/GetUser",//'#Url.Action("GetUser","User")',
method: 'POST',
params: {
uname: $scope.username,
pword: $scope.pass
}
}).then(function (response) {
alert('success!');
$scope.user = response.data;
if ($scope.user.Fullname != undefined) {
$http({
url: rootUrl + "Session/Set",
method: "POST",
data: {
"key": "curr_user",
"value": JSON.stringify($scope.user)
}
});
window.location.href = rootUrl + 'Product/List/';
} else {
//invalid login
$("input[name='password']").select();
$("#validation-summary").html("Wrong email or password.");
$scope.invalidlogin = true;
$(btnLogin).removeClass('disabled');
$(btnLogin).text("Submit");
}
(From mvc controller)
[HttpPost]
public JsonResult GetUser(string uname, string pword)
{
JBManager manager = null;
using (SE_Context db = new SE_Context())
{
try
{
manager = db.Managers
.Include("Transactions.Items")
.Where(m => m.Username == uname && m.Password == pword)
.FirstOrDefault();
//At this point, manager has the desired data
return Json(manager, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return null;
}
}
}
And here's a screenshot of the error in the browser:
Would really appreciate any help. Thanks!
UPDATE:
Everything was working fine before I used Entity Framework. (Just in case it has something to do with the issue)
I think your issue is nested objects.You can flatten object graphs that contain nested objects using DTOs (Data Transfer Objects).
You can just try simple example as like below.If it'll work then you need to extend it to work with your EF query.
public class MyDto
{
public string Name { get; set; }
}
[HttpPost]
public JsonResult GetUser(string uname, string pword)
{
JBManager manager = null;
using (SE_Context db = new SE_Context())
{
try
{
//construct the DTO here
manager = db.Managers.Select(a=> new MyDto(
{
Name = a.Name
})).FirstOrDefault(m => m.Username == uname && m.Password == pword);
return Json(manager, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return null;
}
}
}
You can read more about DTOs here : Create Data Transfer Objects (DTOs)
When I try to use the DELETE verb I either get a null parameter or the controller doesn't fire.
First I tried this:
[HttpDelete]
public IHttpActionResult Delete(Announcement announcement) {
_unitOfWork.Announcements.Remove(announcement);
_unitOfWork.Complete();
return Ok();
}
The controller fires, but announcement is null. If I check on the client side the parameter is not null, it is a properly formed object.
If I add a Route attribute like the below, then the controller doesn't fire at all.
[HttpDelete]
[Route("api/announcements/{announcement}")]
public IHttpActionResult Delete(Announcement announcement) {
_unitOfWork.Announcements.Remove(announcement);
_unitOfWork.Complete();
return Ok();
}
The client side is initiating the DELETE via angular.
myAPIservice.DeleteAnnouncement = function (announcement) {
console.log('In myAPIservice DeleteAnnouncement');
console.log(announcement);
return $http.delete(serviceURLRoot + 'api/announcements/', announcement, { withCredentials: true }).success(function (data) {
console.log('myAPIservice.DeleteAnnouncement Success');
});
};
EDIT ---
The Announcement class:
public class Announcement {
public int AnnouncementId { get; set; }
public string AnnouncementText { get; set; }
}
You can't send a 'body' with a DELETE call.
You could send the announcement id in the form of a parameter:
myAPIservice.DeleteAnnouncement = function (announcementId) {
console.log('In myAPIservice DeleteAnnouncement');
console.log(announcement);
return $http.delete(serviceURLRoot + 'api/announcements/', announcementId, { withCredentials: true }).success(function (data) {
console.log('myAPIservice.DeleteAnnouncement Success');
});
};
Then retrieve it from your database and delete it server side:
[HttpDelete]
[Route("api/announcements/{announcementId}")]
public IHttpActionResult Delete(int announcementId) {
var announcement = _unitOfWork.GetAnnouncementById(announcementId);
_unitOfWork.Announcements.Remove(announcement);
_unitOfWork.Complete();
return Ok();
}
Or of course delete by id... whatever works.
The important part to note here is that DELETE can't carry a payload / body.
I am sending a jQuery Ajax request as such:
$scope.geo.setSuburbs2 = function(areaId) {
var idData = JSON.stringify({ 'areaId': 3, 'agentId': 1 });
$.ajax({
url: "/Area/SuburbsJson",
method: "POST",
data: idData,
dataType: "ajax"
}).then(function(idData) {
alert("Done!");
},
function(data, status) {
alert("Error: " + status);
});
};
This is supposed to fetch suburbs in ID AreaId serviced by an agent with ID agentId. The suburbs returned are key-value pairs in an array on the viewmodel. When I receive the model at the controller, all the keys are still correct in the array, but all numeric values are null.
When I send a data option from the $.ajax call that looks like:
{ 'areaId': 3, 'agentId': 1 }
I get the same size array of key-values on the web app, in OnActionExecuting in the controller, the above array item looks like:
{ 'areaId': null, 'agentId': null }
I strongly suspect a serialization problem on the browser, because the null values should not yet have arrived at my JsonDotNet custom (from the web) serializer. I normally only use that to improve serialization in POST resonses, when I send data back to the UI.
Is this some known problem, or do I have to pore through all the settings, a long list, on the JsonDotNet serializer? If it is a known problem, what should I do about it?
The viewmodel is List<CheckListItemModel>, where:
public class CheckListItemModel
{
public int Id { get; set; }
public string Label { get; set; }
public bool IsChecked { get; set; }
}
I don't use serialized viewmodels to talk between the controllers and the UI, I just pass a UI Angular controller function a filter ID for a list, and it returns the serialized list, with all numeric values set to null.
The action that sends the correct data to the UI is:
public JsonResult SuburbsJson(int areaId, int agentId)
{
var allBurbs = _areaClient.GetSuburbs(areaId).ToList();
var agentBurbIds = _agentClient.GetAgentSuburbs(agentId).Select(s => s.SuburbId).ToList();
var model = new List<CheckListItemModel>();
foreach (var burb in allBurbs)
{
model.Add(new CheckListItemModel { Id = burb.SuburbId, Label = burb.SuburbName, IsChecked = agentBurbIds.Contains(burb.SuburbId) });
}
return Json(model, JsonRequestBehavior.DenyGet);
}
It's a question of Id and IsChecked. And item is already checked if its Id is in a list of Agents associated with that SuburbId.
The JSON.stringify() method converts a JavaScript value to a JSON
string,
Recheck your payload construction. you need to stringify actual JavaScript object like...
$scope.geo.setSuburbs2 = function(areaId) {
var value = { areaId: 3, agentId: 1 }; //Object to be sent to controller
var idData = JSON.stringify(value);
$.ajax({
url: "/Area/SuburbsJson",
type: "POST",
contentType: 'application/json',
dataType: 'json',
data: idData
}).then(function(idData) {
alert("Done!");
},
function(data, status) {
alert("Error: " + status);
});
};
by removing the single quotes on the keys. Also note the changes to how the request was made to the controller.
I am trying to delete an object and return an list which does trigger the web api controller method but then get the error
Expected response to contain an object but got an array (Request: DELETE
$scope.deleteProduct = function (productId) {
productResource.delete({
id: productId
}, function (data) {
$scope.products = data;
});
}
Resource controller
function productResource($resource) {
return $resource("/api/products/:id");
}
Web api controller
public IQueryable Delete(int id)
{
var repository = new ProductRepository();
return repository.Delete(id).AsQueryable();
}
And this is the call to the database which returns a list of products.
internal List<Product> Delete(int Id)
{
IDbConnection connection;
using (connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Liberty"].ToString()))
{
var result = connection.QueryMultiple("DeleteProduct", new{prodId = Id}, commandType: CommandType.StoredProcedure);
var products = result.Read<Product>().ToList();
return products;
}
}
How am I going about this the wrong way?
You could specify that the return type of the DELETE operation is an array because that's what your Web API controller returns:
function productResource($resource) {
return $resource("/api/products/:id", { }, {
'delete': {
method: 'DELETE',
isArray: true
}
});
}
I have a simple spring REST API as follow :
2 Entities (Product, Category)
#Entity
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idCat;
private String nameCat;
#OneToOne // I want is as a one to one relation
#JoinColumn(name = "product_id")
private Product product;
// setters getters etc
}
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idProduct;
private String nameProduct;
#OneToOne(mappedBy = "product")
private Category category;
// setters getters etc
}
I have 2 RestController :
Product :
#RestController
public class ProductRestService {
#Autowired
ProductBusiness productBusiness; // Spring DATA Repo
#RequestMapping(value="/products", method = RequestMethod.POST)
public Ferme addProduct(#RequestBody Product p) {
return productBusiness.addProduct(p);
}
}
Category :
#RestController
public class CategoryRestService {
#Autowired
CategoryBusiness categoryBusiness; // Spring DATA Repo
#RequestMapping(value="/categories", method = RequestMethod.POST)
public Ferme addCategory(#RequestBody Category c) {
return categoryBusiness.addCategory(c);
}
}
And here is my AngularJS code which consumes the REST API :
var app = angular.module("myApp", []);
app.controller("myController", function($scope, $http) {
$scope.product = null;
$scope.addedProduct = null;
$scope.category = {"product":$scope.addedProduct, "nameCat":null};
$scope.saveProduct = function() {
$http.post("/products", $scope.product)
.then(function(response){
$scope.addedProduct = response.data;
}, function(err){
console.log(err);
});
};
$scope.saveCat = function() {
$http.post("/categories", $scope.category)
.then(function(response){
$scope.addedCategory = response.data;
}, function(err){
console.log(err);
});
};
});
I call the methods saveProduct() and then saveCategory() by using a ng-click in an html file, the first method works and the value of the returned Product is saved in the $scope.addedProduct, then I click to save the Category it works too but the product doesn't get saved in the category even if i'm using $scope.category = {"product":$scope.addedProduct, "nameCat":null}; then I pass the category object here $http.post("/categories", $scope.category){.. which means that the previously addedProduct should be saved inside the $scope.category but it's not the case, a value NULL is added in the category table in the database instead of the actual product_id which I verified it exists in $scope.addedProduct. I don't know how to solve this. Sorry if it seems a bit unclear, and I can provide more info if needed.
Thank you in advance.
EDIT :
//
Try declaring $scope.category = {"product":$scope.addedProduct, "nameCat":null}; as
$scope.category = {product:$scope.addedProduct, "nameCat":null};
and then instead updating $scope.addedProduct = response.data; update with
$scope.category.product = response.data;
in your code addedProduct is nowhere backtracking where its assigned and its not going to work.
Solved, I had to put $scope.category = {"product":$scope.addedProduct, "nameCat":null}; inside the saveProduct method.