I have a controller in grails that I am sending an ajax JSON post to with a knockoutJS view model. The view model (along with a javascript object) looks like this:
var childProperty= function(name, id) {
this.name = name;
this.id = id;
};
//KnockoutJS - Main view model
var viewModel = {
id: ko.observable(1),
childProperty: ko.observable(new childProperty("Chuck",1))
}
The data model on the controller side is trying to automatically use the Spring binding magic and bind the JSON request parameters to a new instance of my data model like so:
def jUpdate = {
def update = new SomeObject(params)
}
The problem comes in when I want the Spring binding to detect that childProperty.id is a one-to-many relationship in the data model and to go fetch the related property in the data model. The Grails documentation says this:
Data binding and Associations
If you have a one-to-one or many-to-one association you can use
Grails' data binding capability to update these relationships too. For
example if you have an incoming request such as:
/book/save?author.id=20
Grails will automatically detect the .id
suffix on the request parameter and look-up the Author instance for
the given id when doing data binding such as:
def b = new Book(params)
I am using the ko.toJS utility function and the simple properties are binding correctly. How can I set the view model child property up so that when it is posted to the grails controller, Spring detects it properly and fetches the associated record and builds the object?
I was never able to get the automagic spring bindings to work, so I just passed over the id for the child objects and manually set them on the server side in the params map. After that, GORM fetches the record appropriately. Something like this:
def update = {
params.put("childObject.id",params.childObjectId)
params.remove("childObjectId")
def parentObject = new ParentObject(params)
}
This fetches the related items and builds the object. If you had a lot of related fields, this would become a painful process.
Related
I have started a new project in Spring Boot after using Grails for 4 years.
In Grails I have used properties field of an instance of a domain class to update the associate row in a db table. The assignment of domain.properties was usually done inside a service.
The properties field was set with data coming from a web form.
This approach allows to update a domain instance with a single line, instead of writing n assignemnt, where n is the number of the attributes defined in the domain class.
Now the question.. there is something similar in Spring?
I would like to do something similar in Spring:
update(Long radioId,Map properties) {
// get the radio to be update from the db
Radio radio = getRadio(radioId)
radio.properties = properties
save(radio)
}
I add some detail,
My controller
public ModelAndView updateRadio(Radio radio) {
radioService.update(radio);
return new ModelAndView("redirect:/superadmin/radio/"+radio.getIdentifier()+"/zoom");
}
My Service
#Service
public class RadioService {
...
public void update(Radio radio) {
assert radio.getId() != null;
radioRepository.save(radio);
}
...
}
Now if the web form does not explicity send all the fields defined in Radio I have problem since I will loose the value of the field already stored.
If I could write somthing like that
public void update(Map radioProperties,Long radioId) {
Radio radio = radioRepository.findById(radioId);
radio.properties = radioProperties // only properties present in this map will be update (in grails)
radioRepository.save(radio);
}
it would be great.
In the latter method only the properties in the map (ence in the web form) will be updated, and the other store field of the radio instance will be untouched.
Take a look at Spring Data JPA, it can load domain objects by id, bind incoming request parameters to domain objects and also automagic you some CRUD repositories.
Ok!
I have to say both technology are great. Although there seems that something I do not get it.
You have a data in you database (and let say you want to show data from a table that has references to other tables).
I have a model with List or IEnumerable or IQueryable or whatever...
So in my view I want do foreach through the list of object and take advantage of cool feature of references to other tables. No problem in controller while you are in
using (var datatabse = new MyEntity)
{
}
But when you get out of using db has disposed and you get common error The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So i do not see other options as creating in memory copies of entity objects...but you loose all cool EF4 references and you have to manually load data first in your model and then with foreach show it on the view.
So instead of List<(EF4Type)> or IEnumerable<(EF4Type)> or IQueryable<(EF4Type)>
you have to do List<(MyCustomHelperClass)> where MyCustomHelperClass represents a class with properties similiar to entity objects and probably some additional beacuse you do not have access to properties of referenced tables Then you have to do foreach and Load data into this List and the another #foreach on the view with Razor to show all.
Twice as much work and if project is big...you can see a bigger picture of how manny those helperClasses you need. Was all this cool new technology really meant to be used in that way?....or am I missing something.
You are probably getting that error when you reference a lazy loaded property in your view. You should eager load everything you need in the Controller before passing it to the View.
See Loading Related Objects (Entity Framework).
The following example will cause all courses to be retrieved with the departments in the same query. This is eager loading.
// Load all departments and related courses
var departments1 = context.Departments
.Include(d => d.Courses)
.ToList();
Without the Include() part, courses could be retrieved later (possibly after your context has been disposed in the view). This is called lazy loading.
Along with eager loading as remembered by jrummell, there's also another way of loading related entries, it's explicit loading. Let's suppose you have a User entity, with many Groups entities related to it. You can explicitly load them:
var user = context.Users.Find(id); // Load the user.
context.Entry(user)
.Collection(u => u.Groups)
.Load();
This way you don't have to use the .Include(), and you can even filter the Groups:
context.Entry(user)
.Collection(u => u.Groups)
.Query()
.Where(g => g.SomeProperty.Contains("something"))
.Load();
TheMentor,
Depending on whether you have a repository or a db context, this object should only live for the duration of the controller action (Request), so you should be able to do everything required within the confines of the action.
Maybe i've misunderstood, but based on your question, this is what your issue appears to be. If I have misunderstood, then I'd still suggest that the db repository or db context should be referenced across the controller, rather then invoking it inside the action each time.
so you should see something like this in your controller:
public class TasksController : BaseController
{
private readonly TaskService _serviceTasks;
public TasksController(IRepository repository)
{
_serviceTasks = new TaskService(repository);
}
//
// GET: /Tasks/
public ActionResult Index()
{
var viewModel = _serviceTasks.All<Task>();
return View(viewModel);
}
public ActionResult Details(int id)
{
var domainModel = _serviceTasks.GetById<Task>(id);
var viewModel = PopulateDetailsViewModel(domainModel);
return View(viewModel);
}
//.. rest of actions cut
}
I am using Datamapper ORM 1.8.2.1 with CodeIgniter 2.1.0 and I have trouble accessing newly added related objects from within a parent.
I have a parent class like this:
class Virement extends DataMapper
{
var $has_many = array("lignevirement");
// ...
}
and a child class like this:
class LigneVirement extends DataMapper
{
var $has_one = array("virement");
// ...
}
In the user code, I instantiate this parent and add a few child objects:
$vrt = new Virement; // and do some inits on properties.
$vrt->save();
$lili = new LigneVirement; // do some inits also on properties.
$lili->save();
$vrt->save($lili);
$lili = new LigneVirement; // do some inits also on properties.
$lili->save();
$vrt->save($lili);
// ...
When I then count immediately after the added child objects, I get 0 :
echo $vrt->lignevirement->count();
... whereas taking a look in the database table shows that the parent record has been added, all the child records have been added, and they are correctly related with the parent record.
By the way, when I then try this workaround, I get the correct number of child rows:
$vrt = new Virement($vrt->id);
So what might be wrong with my code above ?
In my experience, Datamapper doesn't make related objects accessible immediately after a save. This includes displaying the properties of a related object; to follow on from your example, the following code will not display the ID of the $vrt object:
$vrt = new Virement;
$vrt->get_by_id(1);
$li = new LigneVirement;
$li->save($vrt);
echo $li->vrt->id;
However, if you redirect, or reload the page, and then reload the $li object, all related items will be available as expected.
If you really need to have related items immediately available, you can create a new related object and load it based on the _id value, as you suggest. Something like:
$vrt = new Virement;
$vrt->get_by_id(1);
$li = new LigneVirement;
$li->save($vrt);
$vrt_refresh = new Virement;
$vrt_refresh->get_by_id($li->vrt_id);
echo $vrt_refresh->id;
(Obviously the above code would be unnecessary, as you already know the ID of the $vrt object, but this is the general principle).
Incidentally, regarding the note in your comment about Datamapper using the same ID for two new objects, I'd avoid using the same variable name for both the objects; this will be unreliable in my experience. Instead, use different variable names, or (if this is unavoidable) take a look at the "Clear" method: http://datamapper.wanwizard.eu/pages/utility.html#clear .
Using ASP.NET MVC when I create my model, then a controller based on the model with CRUD operations, the CRUD views are generated. I added some code using Fluent API to require certain fields but for some reason the ModelState.IsValid passes even when these fields are not completed. What determines whether this passes or not? I thought it was based on your model property data types and other things like being required or maxlength, etc....
Also, I have manually added code to grab a list of Categories from the database and generate a checkbox for each one in the View. This is a navigation property for the Project model where there is a many-many relationship. To get the group of checked values in the Create(Project project) method in the controller I use:
var selected = Request["categories"].Split(',');
This however, throws the classic Object reference not set to an instance of an object error if no values are checked. So what I want to know is, how can I determine that this does not have any values so I can do something else once detected?
I added some code using Fluent API to require certain fields but for
some reason the ModelState.IsValid passes even when these fields are
not completed.
ASP.NET MVC doesn't know anything about the Fluent API of Entity Framework and doesn't evaluate this configuration. You only can use the data annotations which MVC will recognize:
[Required]
public string SomeProperty { get; set; }
...how can I determine that this does not have any values so I can do
something else once detected?
Not sure if I understand it correctly but I'd say:
var categories = Request["categories"];
if (categories != null)
{
var selected = categories.Split(',');
// ...
}
else
{
// do something else
}
First of all, I am new to ASP.Net MVC 3, and I am also using EF 4.1.
I have a complex object, something similar to let's say a Product object containing a Category object. So we have Product.CategoryId, Product.Category and some extra properties. I also have a form to create products with a dropdown list to select the category.
In my controller, after the product has been created, I need to have access to some property of the Category to perform some extra stuff. However, although Product.CategoryId is set, I cannot access Product.Category.SomeProperty because Product.Category is null. I expected Product.Category would be loaded automatically using some lazy loading, but it does not seem to be.
The code in my Controller looks like this:
[HttpPost]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
db.Products.Add(product);
db.SaveChanges();
string someString = product.Category.SomeProperty;
...
Now, this does not work because product.Category is null. What do I need to add so that I can access SomeProperty?
Lazy loading will not work in this scenario because you are adding a new object. Lazy loading will work on "Proxy" entities created by EF context.
So what you can do here is explicitly load the navigational property.
db.Products.Add(product);
db.SaveChanges();
db.Entry(product).Reference(p => p.Category).Load();
string someString = product.Category.SomeProperty;
Lazy loading doesn't work in your case because the product which is passed in into the controller action is not a proxy object but created as an ordinary Product instance by the model binder.
What you expect would work if product is created as a proxy:
var product = db.Products.Create();
product.CategoryId = 1;
db.Products.Add(product);
db.SaveChanges();
string someString = product.Category.SomeProperty;
// Category gets lazily loaded now
The Category property on the Product class must be virtual of course to have lazy loading working at all.
It doesn't help you in your situation because the model binder doesn't create a proxy.
Solutions: Either explicite loading (see #Eranga's answer) or in case you really only need to inspect the SomeProperty of the category fetch the value in a projection:
string someString = db.Entry(product).Reference(p => p.Category).Query()
.Select(c => c.SomeProperty).SingleOrDefault();
...or (because you have the key of the category)...
string someString = db.Categories.Where(c => c.Id == product.CategoryId)
.Select(c => c.SomeProperty).SingleOrDefault();
You may need to explicitly enable lazy loading in your entity framework object context, as described in the MSDN article How to: Use Lazy Loading to Load Related Objects
In the Entity Framework runtime, the default value of the
LazyLoadingEnabled property in an instance of ObjectContext is false.
db.ContextOptions.LazyLoadingEnabled = true;
More detail is provided in the Loading Related Objects article, just look in the section labeled "Lazily Loading Entity Objects".