Nhibernate and Mysql. Insert child uniqie entity - c#

I have related tables.
For example -
Worker (id, mame, idWorkerType, ...) and WorkerType (id, code (unique key), ...)
Then I have same classes, that was mapped.
Then, i have list of generated objects like this
Worker1
Id1
Name1
WorkerType1
IdType1
CodeType1
Worker2
Id2
Name2
WorkerType1
IdType1
CodeType1
...
The logic is as follows:
foreach (var Worker in Workers)
{
var WorkerTypeFromDB = GetWorkerTypeByField("Code", Worker.WorkerType.Code).FirstOrDefault();
if (WorkerTypeFromDB == null)
{
session.Insert(Worker.WorkerType);
}
else
{
session.Update(Worker.WorkerType);
}
var WorkerFromDB = GetWorkerByField("Code", Worker.Code).FirstOrDefault();
if (WorkerFromDB == null)
{
session.Insert(Worker);
}
else
{
session.Update(Worker);
}
}
So, in first iteration I inserting WorkerType1 and Worker1.
In second iteration I updating WorkerType1 (because same unique Code) and trying insert Worker2... but I can't!
I get exception - object references an unsaved transient instance - save the transient instance before flushing or set cascade action for the property to something that would make it autosave.
I know, that, it happened because i don't insert WorkerType, but I dont need that. I already has WorkerType1 in db.
Сan somebody explain me how resolve this problem?

It is possible, but we have to fix the (in)appropriate reference handling. Once we know, that object exists in DB (has the same "CODE") and we have it loaded in a session, we should use that object reference.
So the change should be like:
var WorkerTypeFromDB = GetWorkerTypeByField("Code", Worker.WorkerType.Code).FirstOrDefault();
if (WorkerTypeFromDB == null)
{
session.Save(Worker.WorkerType); // Save/Insert not existing
}
else
{
Worker.WorkerType = WorkerTypeFromDB // assign the existing to our object
}
So now, once we have the existing object, we are assigning it as a property WorkerType
To handle the Worker, we can do even more: Merge() in case that Worker already exists.
var WorkerFromDB = GetWorkerByField("Code", Worker.Code).FirstOrDefault();
if (WorkerFromDB == null)
{
session.Save(Worker); // insert not existing
}
else
{
// here, we should assign the existing Worker ID to passed one
Worker.ID = WorkerFromDB.ID; // current worker has same identifier
session.Merge(Worker); // both will be merged, latest changes will be applied
}
And at the end, when session.Flush() is called, all changes will be persisted. (Merge could be used even for WorkerType... as required)*
Read more about Merge() here 9.4.2. Updating detached objects, An extract:
... using Merge(Object o). This method copies the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. The method returns the persistent instance. If the given instance is unsaved or does not exist in the database, NHibernate will save it and return it as a newly persistent instance. Otherwise, the given instance does not become associated with the session. In most applications with detached objects, you need both methods, SaveOrUpdate() and Merge().

Related

Trouble Attaching an object to a Telerik OpenAccess Data Context

I am writing some tests to excersize the repository layer of a library built on Telerik OpenAccess ORM and am running into some problems with managing the Context.
I am creating a new RegionEntity object and adding it to the database. I use the using statement so that the context cleans up after itself. I additionally create a Detached copy of the added RegionEntity so that it can be re-attached to a context later on.
private RegionEntity AddTestRegionToTable()
{
String regionName = Guid.NewGuid().ToString();
RegionEntity newRegion = new RegionEntity () { /*...property assignment goes here ...*/ };
RegionEntity ret = null;
using (DbContext ctx = new DbContext())
{
ctx.Add(newRegion);
ctx.SaveChanges();
ret = ctx.CreateDetachedCopy<RegionEntity>(newRegion);
}
return ret;
}
So far ... no problem. In my TestMethod below I call the above method and receive a Detached RegionEntity. (I have pulled out my assert statements as they are inconsequential to the issue). I then pass the entity to the Respository method I want to test.
[TestMethod]
public void RemoveRegion_Success()
{
//
// Assemble
RegionEntity origEntity = AddTestRegionToTable();
//
// Act
deletedEntity = RegionRepository.RemoveEntity<RegionEntity>(origEntity);
//
// Assert
/* asserts go here */
}
For the sake of completeness, below I have included ALL the remaining code, exactly as it appears in my application. The repository methods are Generic (again ... should not be relevant to the issue). The first method is the one that is called by the test method, passing in the region as the entityToRemove parameter. This method, in turn calls the DBUtils method, GetContext(), that will either retrieve the DbContext from the entity, or ... if one is not able to be derived... create a new context to be used. In our example a new context is being created.
public class RegionRepository
{
public static T RemoveEntity<T>(T entityToRemove) where T : class
{
T ret = null;
using (DbContext ctx = DbUtils.GetContext<T>(entityToRemove))
{
ret = RemoveEntity<T>(ctx, entityToRemove);
ctx.SaveChanges();
}
return ret;
}
public static T RemoveEntity<T>(DbContext ctx, T entityToRemove) where T : class
{
//
// first chcek to see if the listingToUpdate is attached to the context
ObjectState state = OpenAccessContext.PersistenceState.GetState(entityToRemove);
//
//If the object is detached then attach it
if (state.HasFlag(ObjectState.Detached))
{
ctx.AttachCopy<T>(entityToRemove);
}
//
// confirm that the DETACHED flag is no longer present.
ObjectState state2 = OpenAccessContext.PersistenceState.GetState(entityToRemove);
if (state2.HasFlag(ObjectState.Detached))
{
throw new Exception("Unable to attach entity to context");
}
ctx.Delete(entityToRemove);
return entityToRemove;
}
}
public class DBUtils
{
public static DbContext GetContext<T>(T entity)
{
DbContext ret = OpenAccessContextBase.GetContext(entity) as DbContext;
if(ret == null)
{
ret = new DbContext();
}
return ret;
}
}
Anyway, the method then passes this context and the entity as parameters to an overload. This method takes the DbContext as an additional parameter (allows a single context to be used in multi-step workflows). So the context that is used should still be the one we extracted from the entity or created in our GetContext() method. I then check to see if the entity is attached to the context or not. In this scenario I AM getting a flag of "Detached" as one of the state flags (others are MaskLoaded | MaskManaged | MaskNoMask) so the process then attaches the entity to the context and upon the second check I confirm that the Detached flag is no longer present.
As it turns out the entity is NOT being attached ... and the exception is being thrown.
I have read the Telerik documentation on Detaching and attaching objects to a context ... Attaching and Detaching Objects
By design ObjectState is flags enum that contains both the basic values that form the persistent states of Data Access and the persistent states themselves.
In this enum, Detached is a value that participates in the three detached persistent states: DetachedClean, DetachedDirty, and DetachedNew. You can find more information about the values and the states in this article.
When you detach an object from the context, its state is DetachedClean. If at this point you change any of the properties, the state of the object will become DetachedDirty. If you attach the object back, it will remain in the state before the attachment. Simply put, the action of attaching the object does not change its state.
In other words, checking for Detached is the reason why you get the "Unable to attach entity to context" exception. This value will always be available in the state of your object.
As I am reading the code forward, on this line:
ctx.Delete(entityToRemove);
You will get an exception anyway, because Data Access does not allow you to delete objects that are retrieved through another instances of the context. The exception is:
InvalidOperationException: Object references between two different object scopes are not allowed.
I hope this helps.
-= EDIT =-
When you attach a certain object to an instance of the context and call the SaveChanges() method, Data Access will automatically decide whether to insert a new row in the database or to update an existing row. In this connection, the insert and update scenarios are handled by the Attach / Detach API.
Regarding the delete scenario, you have two options:
To retrieve the object from the database and to delete it through the Delete() method (and call SaveChanges()), like this:
var myObj = ctx.RegionEntities.First(r => r.Id == entityToRemove.Id);
ctx.Delete(myObj);
ctx.SaveChanges();
To use the BulkDelete feature like this:
var myObj = ctx.RegionEntities.Where(r => r.Id == entityToRemove.Id);
int deletedObjects = myObj.DeleteAll();
Something you need to consider with the first option is whether to call SaveChanges() after you attach the object. It is a good idea to do so if there are changes you would like to persist before deleting the object. Additionally, when you use the Delete() method of the context you need to commit the change through the SaveChanges() method before you dispose the current instance of the context. If you do not do this, the transaction will be rolled back, meaning that the object will not be deleted. Details about the transaction handling are available here.
The second option, Bulk Delete, executes the delete operations in a separate transaction upon the call to the DeleteAll() method. Therefore, any other uncommitted changes are not affected. Nevertheless, you need to consider a call to SaveChanges() after attaching the object, especially if the attached object and the deleted one are one and the same object.

How do you save an Already Populated EntityCollection<Entity> using the adapter.SaveEntityCollection() method in LLBLGEN Pro

I am currently trying to save an EntityCollection that is populated with both new and Dirty Entity objects in different scenarios.
I have set up a transaction to roll back in the Event of failure while saving.
However, it always seems to fail and throws an Error...in both cases, saving a new or an existing EntityCollection.
I also have a method that picks and adds individual Entities i.e LanguagetranslationEntity to an Entitycollection that is defined as property in the class.
public EntityCollection<LanguageTranslationEntity> LanguagetranslationCollection { get; set; }
public void AddLanguageTranslationToCollection(LanguageTranslationEntity prompt,bool isnew)
{
//Add the prompt to the collection
LanguagetranslationCollection.Add(prompt);
Isnewcollection = isnew;
}
However, an exception is always thrown regardless of whether i try to save new or old entities like as shown below.
An exception was caught during the execution of an action query: Violation of PRIMARY KEY constraint 'PK_LanguageTranslations'. Cannot insert duplicate key in object 'dbo.LanguageTranslations'. The duplicate key value is (translation_10374, 1).
public void SaveLanguageTranslationCollection(DataAccessAdapter adapter)
{
using (DataAccessAdapter newadapter = adapter)
{
adapter.SaveEntityCollection(LanguagetranslationCollection);
}
}
Should i save each Entity on its Own?? and also, how should i use the SaveEntityCollection()?
I intend to use it for saving a number of LanguageTranslationEntities by populating them into an EntityCollection and saving them all at once,using a Transaction for purposes of Rollback in the Event an Exception is thrown.
Kindly help
The exception suggests that one of the entities inside LanguagetranslationCollection is marked as 'new' but the primary key is already used in your DB.
So, you don't have to save them individually, but it actually could help to identify what is the duplicate entity. Once you identify it, you can investigate further Why is it using an already used PK.
I finally figured it out :-)
Within every transaction, one must always remember that they shouldnt have any methods reinitializing the DataaccessAdapter i.e
using(var adapter = new DataAccessAdapter())
{
//Do saving here
SaveLanguageTranslationCollection(adapter);
};
this is what causes the OurOfSyncException to be thrown,as the state data is cleared and initialized a new for the transaction that had been created with the initial dataAccessAdapter.
here is an Example.
public void Save(PromptEntity prompt)
{
using (var adapter = new DataAccessAdapter())
{
//start transaction
adapter.StartTransaction(IsolationLevel.ReadCommitted, "SavePrompt");
try
{
//saving occurs here.
adapter.SaveEntity(prompt);
SaveLanguageTranslationCollection(adapter);
adapter.Commit();
}
catch (Exception)
{
adapter.Rollback();
throw;
}
}
}
you must pass the same adapter running the transaction to the methods saving. i.e
private void savetranslationprompt(LanguageTranslationEntity translationentity,
DataAccessAdapter adapter)
{
adapter.SaveEntity(translationentity);
}

Entity Framework Pass Object from One Context to Another

I am new to Entity Framework so please bear with me.
I have a program that I want to select multiple records from a table and store it in a queue:
private Queue<RecordsToProcess> getRecordsToProcess()
{
Queue<RecordsToProcess> results = new Queue<RecordsToProcess>();
using (MyEntity context = new MyEntity())
{
var query = from v in context.RecordsToProcess
where v.Processed == false
select v;
foreach (RecordsToProcess record in query)
{
results.Enqueue(record);
}
}
}
Then I spin up multiple worker threads. Each worker thread takes one of the items in queue, processes it, and then saves it to the database.
private void processWorkerThread(object stateInfo)
{
while (workQueue.Count > 0)
{
RecordToProcess record = new RecordToProcess;
lock(workQueue)
{
if (workQueue.Count > 0)
RecordToProcess = workQueue.Dequeue();
else
break;
}
//Do the record processing here
//How do I save that record here???
}
}
My understanding is that to save changes back to the database you just call context.SaveChanges() but I can't do that in this situation can I?
Any help is appreciated.
Thanks!
Since you are disposing your MyEntity context in the first method (by wrapping it in a using statement), the entities that are enqueued will be in a "detached" state. That means, among other things, that changes done to the entity will not be tracked and you will not be able to lazy load navigation properties.
It is perfectly fine to dequeue these entities, "attaching" them to a different context, update them, and then call SaveChanges to persist the changes.
You can read about Attaching and Detaching Objects and Add/Attach and Entity States
It might be safer if you save off the primary key in the queue instead and retrieve the entities again. This way you are more likely avoid any data concurrency issues.

How to access related objects after using statement has finished in Entity Framework?

I use the EF 3.5 in VS 2010. I have a method which returns a struct. In the struct there is an object armatuur. When the struct is returned i want to access the related objects from the armatuur instance.
However
the method returning the struct:
public LampPostDetail getLamppostInfo(int id)
{
LampPostDetail lpd;
lpd.xPos = 0;
lpd.ypos = 0;
lpd.armatuur = new Armatuur();
//get the info from object
using (var db = new OvisionDBEntities())
{
var objects = from o in db.Objects
where o.ObjectId == id
select o;
foreach (OVSL.Data.Object o in objects)
{
lpd.xPos = o.XCoordinatie;
lpd.ypos = o.YCoordinatie;
lpd.armatuur = o.Armatuur; //which is a table in my db
}
return lpd;
}
}
struct:
public struct LampPostDetail
{
#region [ Data Members (14)]
//lamppost info
public double? xPos;
public double? ypos;
//a lamppost can have several armaturen
public OVSL.Data.Armatuur armatuur; //is a table in my db
#endregion [ Data Members ]
}
when doing this in my client:
LampPostDetail lpd = client.getLamppostInfo(id);
string brand = lpd.armatuur.producer.name; //producer is related object of armatuur
I get a ObjectDisposedException. I understand that this happens because the LampPostDetail object is disposed after the using block is finished. But how do i get this to work? Retrieving all information I need (like brand name e.g.) before I return it to the client is not not an option.
The only thing that gets disposed here is the OvisionDBEntities context. After that, no lazy loading is possible. How to deal with that? In fact your question is: what can you do to feed a client with all data that are potentially required for user actions at any time? I see three or four options:
The standard way to enable access to navigation properties of entities after context disposal is calling Include: from o in db.Objects.Include("Armatuur.Producer")... But that's clearly not an option for you.
Let the context live and rely on lazy loading to fetch data on demand. This may be an option for you. But long-lived contexts may cause problems like gradually declining performance as the internal change track record grows, and stale cached data giving rise to refresh/reload statements scattered all over the place.
In stead of navigation properties/lazy loading fetch data on demand from a service/repository layer that uses context instances per call. I think this option could work well for you.
More a functional than a technical option: design use cases that can do with less data (so that Include may suffice after all). No one can take in a grid with thousands of rows and tens of columns. Well-designed user interaction can drastically reduce the amount of data that is pumped into a client (and I'm only at the beginning of getting this).
Its not you LampPostDetail that is getting disposed, it is the Armatuur object you retrieved from the database that it references, or an object that Armatuur is referencing.
I can see two options to getting around this. The first is to make the Entity context an optional parameter to your getLamppostInfo info method. Since you are using 3.5 you will have to do an overload to keep the orignal functionality:
public LampPostDetail getLamppostInfo(int id,OvisionDBEntities context)
{
...
try
{
OvisionDBEntities db;
if (context == null)
db = new OvisionDBEntities();
else
db = context;
...
}
finally
{
if (context == null && db != null)
db.Dispose() // or close maybe
}
retun lpd;
}
// Overloaded function to keep orignal functionality (C# 3.5 does not have
// optional parameters)
public LampPostDetail getLamppostInfo(int id)
{
return LampPostDetail(id,null)
}
Now you can call it as:
using (var db = new OvisionDBEntities())
{
LampPostDetail lpd = client.getLamppostInfo(id,db);
string brand = lpd.armatuur.producer.name;
}
And your objects will still exist when you try to reference them.
The other option is to detach your referenced objects from the entity context, before disposing of it.
db.Detach(o.Armatuur);
However, I don't believe that detaches any objects references by that object. So you would have to interate the reference trees and detach thoes objects as well.

Entity Framework - "The relationship between the two objects cannot be defined" error, but I think I'm using the same context

In my ViewModel I have some code like that:
public class OrderViewModel
{
private UserOrder order;
private DeliveryCentre deliveryCentre;
// This is my EF Container
private CatalogueContainer catalogue = new CatalogueContainer();
// do some stuff...
public void Save()
{
if (order == null)
{
order = catalogue.UserOrders.CreateObject();
}
// do some other stuff...
if ((deliveryCentre == null)
|| (deliveryCentre.Id != deliveryCentreId))
{
deliveryCentre = catalogue.DeliveryCentres.First(centre => centre.Id == deliveryCentreId);
//Causes a context error, not sure why...
order.DeliveryCentre= deliveryCentre;
}
catalogue.SaveChanges();
}
So when the delivery centre is new and the order is new, I am hit by the old "The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects" error, which seems a trifle unfair to me - I just can't figure out what I need to do to make them belong more to the same object context. I assume this is due to some fundamental misunderstanding of the behaviour of Entity Framework.
You are not disposing your context. It may be possible that one of the entities order or deliveryCentre is attached to an old context which still holds references to the entities. You can create and dispose your context with an using statement inside of the Save method instead to using it as a member variable:
public void Save()
{
using (var catalogue = new CatalogueContainer())
{
// your code...
}
}
And remove the private catalogue member.
The solution turned out to only be indirectly related to the error message- #Slauma asked about the //do stuff... placeholders and when I commented those out the error disappeared.
It turned out that there was another relationship there, where I was creating the object as this.Item = new Item() rather than using this.Item = catalogue.Items.CreateObject() so it was being created out of context and when it was added to the order, although the order itself was created from the local context, when the Item was added to it this was somehow dirtying up the context but for some reason this only showed up as a problem when I added the next related object.

Categories