Grails: Does the addTo() method not work on newly created objects? - grails

I'm using Grails 2.5.5, and I have a simple scenario here: A has many Bs.
And so I have a transactional service method which basically does the following:.
A a
if (isNew) {
a = new A()
} else {
a = A.findById(someId)
}
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)
The interesting thing is that if I create a new object (as in, isNew is true in the above logic), then I get the following exception when save() is called: reassociated object has dirty collection reference (or an array).
However, if I get an object which already exists in the DB, then everything works perfectly.
The workaround I found is that if I save the new object before adding Bs to A, then things work. But I would rather not have to call save() twice, and the code is just a lot cleaner if the call was just at the end.
I've googled the exception but nothing seems to explain what's going on here.
Can somebody help out with this?

Like others have said in the comments, you need to save the object so it exists in the database, and has an id. When you have a A has many Bs relationship, a new table in the database (something like a_b) is created to map A.id to B.id, which is why you can't add Bs to A without saving first.
A a
if (isNew) {
a = new A()
a.save()
} else {
a = A.findById(someId)
}
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)

Use findOrSaveBy for such an operations. You will get the proper object from db or persist new one:
def a = A.findOrSaveByField(field)
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)

Related

Do I need a double save() after a domain modification using afterInsert()?

I have a domain class that modifies one of its properties in the afterInsert event.
A small example:
class Transaction {
Long transactionId
static constraints = {
transactionId nullable: true
}
def afterInsert() {
// copy the record id to transactionId;
transactionId = id
}
}
Whenever I save the domain object (transaction.save(flush: true)) in
my unit tests, all is well, and the transactionId is updated. But when I try to find the saved record using Transaction.findByTransactionId(), I get no results:
// do something
transaction.save(flush: true)
Transaction transaction = Transaction.findByTransactionId(1)
// !! no results; transaction == null
And I have to do a double save() before I can find the record using findByTransactionId():
// do something
transaction.save(flush: true)
transaction.save(flush: true)
Transaction transaction = Transaction.findByTransactionId(1)
// !! it works....
The double save() seems awkward. Any suggestions on how to eliminate the need for it?
The call to save() will return the persisted entity if validation passes, so there isn’t any reason to look it up separately afterwards. I think that your problem is that you’re re-instantiating the transaction variable (using that same name). If you must look it up (I don’t suggest doing so), call it something else. Also, the 1 id that you’re looking up may not exist if the column is an AUTO-INCREMENT.
def a = a.save(flush: true)
a?.refresh() // for afterInsert()
Transaction b = (a == null) ? null : Transaction.findByTransactionId(a.id)
// (Why look it up? You already have it.)
Update:
Because you’re using afterInsert(), Hibernate may not realize that it needs to refresh the object. Try using the refresh() method after you call save().
This small piece of code makes it obviously work:
def afterInsert() {
transactionId = id
save() // we need to call save to persist the changens made to the object
}
So calling save in the afterInsert is needed to persist the changes made in afterInsert!

Transaction when saving many object in Grails service

I am having a problem with transaction in Grails. I want to save a list of object to DB by a checking condition at each object. All these process I want to put to one transaction, it means if the k-th object does not satisfied the checking condition, all previous objects (from the first object to the (k-1)th one) will be rolled back from DB. Here is my example:
static transactional = true
public void saveManyPeople() {
// ...
List<People> peoples = new ArraysList();
for(i = 0, i < n, i++) {
People newPeople = createPeopleFromRawData(); // return a people object in memory
if(<checking-condition>) {
newPeople.save(flush : false)
} else {
throw new MyCustomizedException() // MyCustomizedException has extended from RuntimException
}
}
// ...
}
As you may see, I set transactional variable to true and I've tried to use flush : true and flush : false, but it didn't work as I want. I've read this article Rolling back a transaction in a Grails Service
And the author recommended that the service method should throw a RuntimeException then the process will be rollbacked. But if I want to throw another exception, so what I have to do?
Could you please give me some suggestions on this problem?
Thank you so much!
You can throw any exception that extends from RuntimeException to rollback the transaction. Or you can use Programmatic Transactions, using withTransation, to have more control over the transaction.
Could you verify that saveManyPeople() is within a Service and not a Controller?
The static transactional = true isn't respected in a Controller. I am suspecting that this is the issue.
If you need to have transactional support with the controller, you could always use DomainClass.withTransaction. Reference Documentation
Example:
Account.withTransaction { status ->
def source = Account.get(params.from)
def dest = Account.get(params.to)
def amount = params.amount.toInteger()
if(source.active) {
source.balance -= amount
if(dest.active) {
dest.amount += amount
}
else {
status.setRollbackOnly()
}
}
}

what's the best practice to attach a entity object which is detached from anthoer ObjectContext?

As mentioned in the title, how many methods are available?
I just have this case: I get a entity object from one ObjectContext, and then I detach the entity obejct from OjbectContext object, and return it.
Later, if I make some changes on this object, and I want to save the changes back to database. I think I should write like this, right? (Well, this works for me.)
public Url GetOneUrl()
{
Url u;
using(ServicesEntities ctx = new ServicesEntities())
{
u = (from t in ctx.Urls select t).FirstOrDefault<Url>();
ctx.Detach(u);
}
return u;
}
public void SaveToDB(Url url)
{
using(ServicesEntities ctx = new ServicesEntities())
{
var t = ctx.GetObjectByKey(_Url.EntityKey) as Url;
ctx.Detach(t);
ctx.Attach(url);
ctx.ObjectStateManager.ChangeObjectState(url, System.Data.EntityState.Modified);
ctx.SaveChanges();
}
}
Url url = GetOneUrl();
url.UrsString = "http://google.com"; //I just change the content.
SaveToDB(url);
OR
public void SaveToDB(Url url)
{
using(ServicesEntities ctx = new ServicesEntities())
{
var t = ctx.GetObjectByKey(_Url.EntityKey) as Url;
t = url; //this will make t.UrlString becomes "http://google.com"
ctx.ApplyCurrentValues<Url>("Urls", t);
ctx.SaveChanges();
}
}
This way is also works for me.
The first way will generate sql statement to update all the columns of Url table, but the second method will provide a sql script only update the "UrlString" Columns.
Both of them will have to retrieve a temp entity object from database which is the 't' in above code.
Are there any other methods to achieve this purpose? Or other better method you know about it? Or any official solution about this topic?
Many Thanks.
I don't understand your first example. Why do you first get entity from ObjectContext? It is not needed because you have just created new instance of the context. You can just use:
public void SaveToDB(Url url)
{
using(ServicesEntities ctx = new ServicesEntities())
{
ctx.Attach(url);
ctx.ObjectStateManager.ChangeObjectState(url, System.Data.EntityState.Modified);
ctx.SaveChanges();
}
}
In your second example you can just call:
public void SaveToDB(Url url)
{
using(ServicesEntities ctx = new ServicesEntities())
{
var t = ctx.GetObjectByKey(_Url.EntityKey) as Url; // Ensures that old values are loaded
ctx.ApplyCurrentValues<Url>("Urls", url);
ctx.SaveChanges();
}
}
Now the difference between two approaches is clear. First approach (Attach) does not need to query the DB first. Second approach (ApplyCurrentValues) needs to query the DB first to get old values.
You can use two additional approaches. First is just extension of your former approach. It allows you defining which properties were changed. Second approach is manual synchronization with loaded entity. This approach doesn't use any special methods. You will simply set loaded entity's properties to required values manually. This approach is useful if you work with object graph instead of single entity because EF is not able to automatically synchronize changes in relations.

Merge an Object that wen outside the datacontext

I have the following question:
It is easy to insert an oBject in database with a form.
Just create an object
link it to the fields in your from.
Post back to controller,
create a new datacontext and do datacontext.InsertOnSubmit(object)
.
public static void AddPage(string lang, Page page)
{
using (var db = new CardReaderDataContext())
{
page.Lang = lang;
page.URL = UrlHelper.CreateValidSeoUrl(page.Name, "-");
db.Pages.InsertOnSubmit(page);
db.SubmitChanges();
}
}
But if you want to update an object, it is a tedious job.
You do the same flow,
you get the object,
link it to your form,
post it, but THEN !!!
because it went outside your datacontext, you have to reload the object from the datacontext,
transfer all the variables and save it,
this is a little complex explained so I give an example:
To update an object that you modified in a form:
public static void Update(Page page)
{
using (var db = new CardReaderDataContext())
{
var _page = db.Pages.Where(p => p.Guid == page.Guid).Single();
_page.ModificationDate = DateTime.Now;
_page.Title = page.Title;
_page.Description = page.Description;
_page.Content = page.Content;
_page.Keywords = page.Keywords;
_page.Name = page.Name;
_page.WTLang = page.WTLang;
_page.WTSKU = page.WTSKU;
_page.WTTi = page.WTTi;
_page.WTUri = page.WTUri;
_page.URL = UrlHelper.CreateValidSeoUrl(page.Name, "-");
// _page.Order = GetMaxOrderByMenuGuid(page.MenuGuid);
db.SubmitChanges();
}
}
I don't know if it is clear, if it isn't comment me, I will edit
I think you're looking for DataContext.Attach, but you can only use that with linqtosql objects that have been serialised/deserialised.
Have a read of the answer to this question -
http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/384a1c03-3acf-43ef-9a25-b84f93025e63/
"It's also not a good idea to even
attempt to fetch the old version. By
doing that you are in effect turning
off optimistic concurrency, so unless
you intended that this is a bad
approach. What you need to do is
round trip both the original state and
the current state of the object."

Linq to SQL update not working using Repository pattern

I am using asp.net mvc for an application. I've taken some guidance from Rob Conery's series on the MVC storefront. I am using a very similar data access pattern to the one that he used in the storefront.
However, I have added a small difference to the pattern. Each class I have created in my model has a property called IsNew. The intention on this is to allow me to specify whether I should be inserting or updating in the database.
Here's some code:
In my controller:
OrderService orderService = new OrderService();
Order dbOrder = orderService.GetOrder(ID);
if (ModelState.IsValid)
{
dbOrder.SomeField1 = "Whatever1";
dbOrder.SomeField2 = "Whatever2";
dbOrder.DateModified = DateTime.Now;
dbOrder.IsNew = false;
orderService.SaveOrder(dbOrder);
}
And then in the SQLOrderRepository:
public void SaveOrder(Order order)
{
ORDER dbOrder = new ORDER();
dbOrder.O_ID = order.ID;
dbOrder.O_SomeField1 = order.SomeField1;
dbOrder.O_SomeField2 = order.SomeField2;
dbOrder.O_DateCreated = order.DateCreated;
dbOrder.O_DateModified = order.DateModified;
if (order.IsNew)
db.ORDERs.InsertOnSubmit(dbOrder);
db.SubmitChanges();
}
If I change the controller code so that the dbOrder.IsNew = true; then the code works, and the values are inserted correctly.
However, if I set the dbOrder.IsNew = false; then nothing happens...there are no errors - it just doesn't update the order.
I am using DebuggerWriter here: http://www.u2u.info/Blogs/Kris/Lists/Posts/Post.aspx?ID=11 to trace the SQL that is being generated, and as expected, when the IsNew value is true, the Insert SQL is generated and executed properly. However, when IsNew is set to false, there appears to be no SQL generated, so nothing is executed.
I've verified that the issue here (LINQ not updating on .SubmitChanges()) is not the problem.
Any help is appreciated.
In your SaveOrder method you are always creating a new ORDER object. You need to change this so that if order.IsNew is false, it retrieves the existing one from the DB and updates it instead.
public void SaveOrder(Order order)
{
ORDER dbOrder;
if (order.IsNew)
{
dbOrder = new ORDER();
dbOrder.O_ID = order.ID;
}
else
{
dbOrder = (from o in db.ORDERS where o.O_ID == order.ID select o).Single();
}
dbOrder.O_SomeField1 = order.SomeField1;
dbOrder.O_SomeField2 = order.SomeField2;
dbOrder.O_DateCreated = order.DateCreated;
dbOrder.O_DateModified = order.DateModified;
if (order.IsNew)
db.ORDERs.InsertOnSubmit(dbOrder);
db.SubmitChanges();
}
I think you have the problem that your entity is detached from your context.
You should try to attach your entity back to your context if you want to update. The downside of LINQtoSQL is that for the re-attachment you'll need the original state of the object when it was detached...
Another solution is to re-get your entity from the context and copy all the data from your entity in the parameter. This will do until you'll have more complex entities.
What tvanfosson said.
I would just like to add that I use logic where if Id equals default(0 or Empty if using guids), then I assume it is new. Otherwise if I have the id passed in, then I go get the existing object and update it.

Resources