I have the classes Person and Dog.
A dog belongs to one Person and a Person can have different dogs (so one-to-many).
I have a test method that gets 2 persond out of the db. If a person does not exist in the db, I make it. Then I want to save 4 dogs.
This is the test:
Person person1= personResourceAccess.GetPersonByName("Person1");
if(person1==null)
{
Person person = new Person()
{
Name = "Person1"
};
person1= personResourceAccess.CreatePerson(person);
}
Person person2= personResourceAccess.GetPersonByName("Person2");
if(person2==null)
{
Person person = new Person()
{
Name = "Person2"
};
person2= personResourceAccess.CreatePerson(person);
}
Dog dog1 = new Dog(){name="Dog1", owner = person1};
Dog dog2 = new Dog(){name="Dog2", owner = person1};
Dog dog3 = new Dog(){name="Dog3", owner = person1};
Dog dog4 = new Dog(){name="Dog4", owner = person2};
dog1 = dogResourceAccess.CreateDog(dog1);
dog2 = dogResourceAccess.CreateDog(dog2);
dog3 = dogResourceAccess.CreateDog(dog3 );
dog4 = dogResourceAccess.CreateDog(dog4 );
This is the code of the resource access:
public Dog CreateDog(Dog dog)
{
try
{
db.Dogs.AddObject(dog);
db.SaveChanges();
return dog;
}
catch(Exception ex)
{
return null
}
}
When I save the first dog, all other dogs are created in the db. Why is this and how can I prevent it from happening?
I think it might have something to do with the context but I can't seem to resolve the problem.
This is correct behavior. SaveChanges always save all changes currently tracked by the context. There is no way to avoid it - it is called unit of work. The reason why this happens is assigning owner in initialization of the new dog. Person is already tracked by the context so assigning it to any other entity will immediately start tracking of that entity.
If you want to save changes in sequence you must do it different way:
Dog dog1 = new Dog(){name="Dog1"};
dog1 = dogResourceAccess.CreateDog(dog1);
dog1.owner = person1; // Attach the person after you added a new dog.
Related
Could someone explain the difference to me between these code samples (Grails 3.3.11)
Session session = sessionFactory.openSession()
Person person = new Person()
person.firstName = "John"
person.lastName = "Roy"
person.address = "New York"
session.save(person)
and
Person person = new Person()
person.firstName = "John"
person.lastName = "Roy"
person.address = "New York"
person.save(person)
One difference is that session.save(person) will work with any entity that is mapped in that Hibernate session where person.save() only works with GORM entities (which are also mapped in the Hibernate session for you).
When using GORM there really aren't good reasons to use session.save(person).
I know that you didn't ask about best practice or about GORM Data Service but related to your question...
With recent versions of GORM best practice would be to have an abstract GORM Data Service like this...
import grails.gorm.services.Service
#Service(Person)
interface PersonService {
Person save(Person p)
}
And then inject that wherever you want to do the save. For example, in a controller:
class SomeController {
PersonService personService
def someAction() {
Person p = new Person()
// ...
personService.save p
// ...
}
}
The problem:
I have "route" domain, that has stations (domains).
If I save routes, I should save stations too. (used cascade: 'all' mapping)
But, if station with the same name already exists in DB, I want to use it, and don't create such station anymore.
so the route, I save, must save only new stations.
example:
domain:
class Route {
String name
List<Station> stations
static mapping = {
stations cascade: 'all', lazy: false
}
static hasMany = [
stations: Station
]
}
class Station {
String name
}
controller/service:
def route = new Route()
route.stations.add(new Station("stationOne"))
route.stations.add(new Station("stationTwo"))
route.save()
//now in db there are 2 stations.
//now create new route with 2 stations, with one similar to already saved ('stationOne').
def route2 = new Route()
route2.stations.add(new Station("stationOne")) //<-- this one i want to be replaced
// with the inDB one, if i save the route
// in DB must be only one "stationOne"
// and every route must point to it,
// not own "stationOne", if the route saved
route2.stations.add(new Station("stationThree"))
route2.save()
//now i wish in DB there are only 3 stations.
//and route2 has both from DB. And the reference (in list) from route2 to "stationOne"
//inMemory object is now replaced with reference to inDB station object.
i could write code, like "replaceWithDBStationReferences(route)"
But my project is enough big, for testing such things in code.
is it possible to define this somewhere in domain? or any other solutions?
You can make use of the findOrCreate dynamic methods:
route2.stations.add(Station.findOrCreateByName("stationOne"))
findOrCreateByName is similar to findByName except that where the plain finder would return null (if nothing is found) the findOrCreate will create a new transient instance based on the query parameters, in this case a new Station(name:'stationOne').
There's also findOrSaveBy... which does the same but also saves the new instance before returning it.
If you need to use the existing Route from the DB you should just need to fetch it first. Something like this:
def route = Route.findByName(name)
def stations = getNewAndModifiedStations()
stations.each { station ->
def existingStation = route.stations.find { station.name?.equals(it.name) }
if (existingStation) {
// if station is found
existingStation.updateProperties(station)
} else {
// new station here
route.stations.add(station)
}
}
route.save()
There appears to be two ways to update a disconnected Entity Framework entity using the "attach" method.
Method One is to simply set the disconnected entity's state as modified:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
This will save all fields on the "dog" object. But say you are doing this from an mvc web page where you only allow editing of Dog.Name, and the only Dog property contained on the page is Name. Then one could do Method Two:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();
Method Two could get quite verbose when there are a lot of properties to update. This prompted me to attempt Method Three, setting IsModified = false on the properties I don't want to change. This does not work, throwing the runtime error "Setting IsModified to false for a modified property is not supported":
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
I'd much prefer to use Method One everywhere, but there are many instances where my asp.net mvc view does not contain every scalar property of the Dog class.
My questions are:
Are there any attributes I could use on the POCO class that would tell Entity Framework that I never want the property to up updated? Eg, [NeverUpdate]. I am aware of the [NotMapped] attribute, but that is not what I need.
Failing that, is there any way I can use Method One above (myDbContext.Entry(dog).State = EntityState.Modified;
) and exclude fields that I don't want updated?
P.S. I am aware of another way, to not use "attach" and simply fetch a fresh object from the database, update the desired properties, and save. That is what I am doing, but I'm curious if there is a way to use "attach," thus avoiding that extra trip to the database, but do it in a way that is not so verbose as Method Two above. By "fetch a fresh object" I mean:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
The following may work works.
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
// You need to give Foreign Key Property name
// instead of Navigation Property name
entry.RejectPropertyChanges("OwnerID");
}
myDbContext.SaveChanges();
If you want to do it in a single line, use the following extension method:
public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
{
entry.RejectPropertyChanges(propertyName);
}
}
And use it like this
// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();
As you can see, you can modify this solution to fit your needs, e.g. use string[] properties instead of string propertyName as the argument.
Suggested Approach
A better solution would be to use an Attribute as you suggested ([NeverUpdate]). To make it work, you need to use SavingChanges event (check my blog):
void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
ObjectContext context = sender as ObjectContext;
if(context != null)
{
foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var type = typeof(entry.Entity);
var properties = type.GetProperties();
foreach( var property in properties )
{
var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
if(attributes.Length > 0)
entry.RejectPropertyChanges(property.Name);
}
}
}
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{
}
//In your POCO
public class Dogs
{
[NeverUpdate]
public int OwnerID { get; set; }
}
Warning: I did not compile this code. I'm not at home :/
Warning 2: I have just read the MSDN documentation and it says:
ObjectStateEntry.RejectPropertyChanges Method
Rejects any changes made to the property with the given name since the
property was last loaded, attached, saved, or changes were accepted.
The orginal value of the property is stored and the property will no
longer be marked as modified.
I am not sure what its behavior would be in the case of attaching a modified entity. I will try this tomorrow.
Warning 3: I have tried it now. This solution works. Property that is rejected with RejectPropertyChanges() method are not updated in the persistence unit (database).
HOWEVER, if the entity that is updated is attached by calling Attach(), the current context remains dirty after SaveChanges(). Assume that the following row exists in the database:
Dogs
ID: 1
Name: Max
OwnerID: 1
Consider the following code:
var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
The current state of database after SaveChanges():
Dogs:
ID: 1
Name: Achilles
OwnerID: 1
The current state of myDbContext after SaveChanges():
var ownerId = myDog.OwnerID; // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged
So what you should do? Detach it after SaveChanges():
Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;
I'm having an issue with grails. I have a domain that looks like:
class Book {
static belongsTo = Author
String toString() { title }
Author bookAuthor
String title
String currentPage
static constraints = {
bookAuthor()
title(unique:true)
currentPage()
}
}
The main thing to note is that I have title(unique:true) to avoid from adding the same book twice. However, this is causing issues. In the controller I have created:
def populate = {
def bookInstance = new Book()
def dir = 'C:/currentBooks.txt'
def bookList
bookList = readFile(dir) //read file and push values into bookList
int numOfBooks = bookList.size()
numOfBooks.times {
bookInstance.setBookAuthor(bookList.author[it])
bookInstance.setTitle(bookList.title[it])
bookInstance.setCurrentPage(bookList.title[it])
bookInstance.save()
}
}
I call populate to read a file and populate the database with new Books. The problem is that I want to update it with new values. For instance, lets say that the book already exists in the database but I have read farther into the book and want to change the currentPage so the data is changed in the file and populate is called but doesn't update the page because the title already exists.
Can someone explain how to update the results with the new values?
First of all, you need a key for your Book domain object. You have the title marked as unique, which suggests you want to use that to uniquely identify a Book. I'd recommend against that (what happens when two books have the same title?) and use the id grails provides by default. That means you'll have to store the id in your currentBooks.txt in addition to your other fields.
Once you've got an id, you can try loading an existing record from the database. If not, create a new one. For Example:
def dir = 'C:/currentBooks.txt'
def bookList
bookList = readFile(dir) //read file and push values into bookList
int numOfBooks = bookList.size()
numOfBooks.times {
def bookInstance.get(bookList.id[it])
if (!bookInstance) {
bookInstance = new Book()
}
bookInstance.setBookAuthor(bookList.author[it])
bookInstance.setTitle(bookList.title[it])
bookInstance.setCurrentPage(bookList.title[it])
bookInstance.save()
}
Alternatively, you could use the title as the id. This is a bad idea as indicated above, but it saves having to keep track of a separate id and change the format of currentBooks.txt. With Book defined as below, you could call Book.get(bookList.title[it]):
class Book {
static belongsTo = Author
String toString() { title }
Author bookAuthor
String title
String currentPage
static constraints = {
bookAuthor()
title(unique:true)
currentPage()
}
static mapping = {
id name: 'title', generator: 'assigned'
}
}
I'm struggling to get association right on Grails. Let's say I have two domain classes:
class Engine {
String name
int numberOfCylinders = 4
static constraints = {
name(blank:false, nullable:false)
numberOfCylinders(range:4..8)
}
}
class Car {
int year
String brand
Engine engine = new Engine(name:"Default Engine")
static constraints = {
engine(nullable:false)
brand(blank:false, nullable:false)
year(nullable:false)
}
}
The idea is that users can create cars without creating an engine first, and those cars get a default engine. In the CarController I have:
def save = {
def car = new Car(params)
if(!car.hasErrors() && car.save()){
flash.message = "Car saved"
redirect(action:index)
}else{
render(view:'create', model:[car:car])
}
}
When trying to save, I get a null value exception on the Car.engine field, so obviously the default engine is not created and saved. I tried to manually create the engine:
def save = {
def car = new Car(params)
car.engine = new Engine(name: "Default Engine")
if(!car.hasErrors() && car.save()){
flash.message = "Car saved"
redirect(action:index)
}else{
render(view:'create', model:[car:car])
}
}
Didn't work either. Is Grails not able to save associated classes? How could I implement such feature?
I think you need a belongsTo in your Engine ie
static belongsTo = [car:Car]
Hope this helps.
For what is worth, I finally nailed it.
The exception I got when trying to save a car was
not-null property references a null or
transient value
It was obvious that the engine was null when trying to save, but why? Turns out you have to do:
def car = new Car(params)
car.engine = new Engine(name: "Default Engine")
car.engine.save()
Since engine doesn't belongs to a Car, you don't get cascade save/update/delete which is fine in my case. The solution is to manually save the engine and then save the car.