Hello,
Maybe somebody could help me with the following problem:
I create a number of appointments in the disconnectedstate (I have my reasons :wink:), and then, after a while, I sometimes need to save (persist) a few of them (not all) to the database.
Can anybody give me an example of how to accomplish this?
Updated at 22.06.2010 6:52:34
Wow, that seems like a lot of work indeed... Thank you for you explaination.
I shall try to explain my situation:
I have an entity Program that holds a series of appointments. The program describes how the appointments should look like (first every month etc.), but doesn't save any physical appointments so that the Appointment table is as small as possible.
When I perform Appointment.GetAllAppointments(daterange, location, employee etc.) the idea is that it returns a List of Appointments, with the appointments generated from the database included in the list(flagged with 'IsProgramAppointmentl' property or something).
When there is deviated from the program, the appointment gets a real appointment and 'excluded' from the programentity. I thought it would be easy to say: Appointment.TurnIntoRealObject() (method names need to be created :D) and then persist this to the database.
I remember that there was a option in DO 3.x to just set the session property to null when you don't want the object te be persisted, and when you want the object to be persisted, just set the Session property again and the entity will be saved.. No such functionality in DO4?...
As I read your story something tells me it is easier to create a clone method (DO4.x doens't have any for entities?) and persist the objects I want in a different Session that is attached to a ConnectedState, and then cancel all changes on the DisconnectedState.
Updated at 22.06.2010 7:15:30
Edit: In addition to my previous post:
Actually.. I was wondering if there is a known problem with attaching entities with GUID keys generated by a custom GuidKeyGenerator to the DisconnectedState and then persisting them to the DB?
I have a Appointment that I connect to the DisconnectedState wich has a relationship with Unit (Location) through the AppointmentHasRelationshipWithUnit entity (some additional fields are kept here).
So I do this:
var disconnectedState = new DisconnectedState();
disconnectedState.Attach(session);
disconnectedState.Connect();
using (var trans = Transaction.Open())
{
AppointmentCore _virtualappointment = new AppointmentCore();
_virtualappointment.Start = DateTime.Now;
_virtualappointment.End = DateTime.Now.AddHours(1);
_virtualappointment.Units.Add(new BL.Model.Associations.AppointmentHasRelationshipWithUnit(_virtualappointment, Query.All<UnitCore>().FirstOrDefault(), false));
trans.Complete();
}
disconnectedState.ApplyChanges();
And it throws the following exception:
Entity with Key = 'AppointmentHasRelationshipWithUnit, (21, 2, d45e1908-7043-41b0-ae01-b9d3c2451b2a)' does not exist.
The AppointmentHasRelationshipWithUnit entity looks like this (the core):
[Serializable]
[HierarchyRoot]
[KeyGenerator(typeof(GuidKeyGenerator))]
public class AppointmentHasRelationshipWithUnit : Entity
{
/// <summary>
/// The identifier of the entity.
/// </summary>
[Key(Position = 2), Field]
public Guid Id { get; private set; }
[Key(Position = 0), Field]
protected AppointmentCore Appointment { get; private set; }
[Key(Position = 1), Field]
protected UnitCore Unit { get; private set; }
}
GuidKeyGenerator class:
public class GuidKeyGenerator : KeyGenerator<Guid>
{
/// <summary>
/// Generate new Key (GUID).
/// </summary>
/// <param name="temporaryKey"></param>
/// <returns></returns>
public override Xtensive.Core.Tuples.Tuple Next(bool temporaryKey)
{
return base.Next(temporaryKey);
}
/// <summary>
/// Creates an instance of the GuidKeyGenerator.
/// </summary>
public GuidKeyGenerator()
: base()
{ }
}
Is this a bug or am I doing something wrong?
Updated at 22.06.2010 7:50:14
> No such functionality in DO4?...
As far as I remember, this couldn't work with v3.9 as well... But may be I'm wrong.
There is an example here on the forum, but I can't find it anymore.. But it was old...
> it is easier to create a clone method (DO4.x doens't have any for entities?)
Yes, there is no such method for shallow cloning?, although I'll think about adding it.
Deep cloning will be possible with serialization, but currently this part isn't fully finished yet.
Okay! Deep cloning would be better here (since there are other objects 'attached' to the mainobject).
About the idea itself:
As far as I understand, there are two kinds of appointments:
Auto-generated (or virtual) - there are may be 90-95% of them, thus you don't want to store them in DB.
Actual - they must be stored in DB.
Correct! This construction is done for performance (trust me, when all appointments that lead from programs are physically created, the table gets really, really big) and usability for the programmer. When a program is edited not all the appointments have to be edited. Also some Business Layer advantages are created for us by the approach.
I'd think about the following possible implementations:
If I can save all the appointments anyway, probably, it's a good idea to do this. Moreover, may be there are even actual reasons for doing this - e.g. can users see the appointment tables in past? If so, do you need a real history here? If yes, likely, you must store auto-generated appointments anyway. If not, may be keeping just future & current auto-generated appointments is ok as well (they'll use relatively small % of all the stored history)?
One more possible approach is to introduce a special non-persistent type describing both kinds of appointments, and implement its conversion to/from persistent appointment.
I think option 2 is the best for me (as I see it now).
No, there are no known problems related to GUIDs. Could you provide a stack trace?
Sure!
at Xtensive.Storage.Query.Single(Session session, Key key)
at Xtensive.Storage.Operations.EntitySetItemAddOperation.Execute(OperationExecutionContext context)
at Xtensive.Storage.OperationLog.Replay(Session session)
at Xtensive.Storage.DisconnectedState.ApplyChanges(Session targetSession)
at Xtensive.Storage.DisconnectedState.ApplyChanges()
at ProjectNameCore.Test.Test06AppointmentAndPrograms.TestDisconnectedStateForAppointments() in C:\Documents and Settings\mypath\Test06AppointmentAndPrograms.cs:line 46
Updated at 22.06.2010 8:38:55
Try to:
get rid of GuidKeyGenerator usage (simply remove the attribute).
isolate the problem with persistence - e.g. try to create AppointmentHasRelationshipWithUnit earlier; btw does it has paired relationship to AppointmentCore.Unit? If so, _virtualappointment.Units.Add must fail.
I disabled the KeyGenerator by setting the attribute to: [KeyGenerator(KeyGeneratorKind.None)]. Just removing the attribute leads to an exception: Default generator can serve hierarchy with exactly one key field. (I have 3 keys, so that makes sence).
When the KeyGenerator is set to 'None', it leads to exactly the same exception...
Appointment has no direct relationship with Unit (so no _virtualappointment.Units.Add), because this relation is handled by the AppointmentHasRelationshipWithUnit entity.
Here's the code:
Appointment Entity:
[Field]
[Association(PairTo = "Master")]
public EntitySet<Associations.AppointmentHasRelationshipWithUnit> Units { get; private set; }
Unit Entity:
[Field]
[Association("Slave")]
public EntitySet<Associations.AppointmentHasRelationshipWithUnit> Appointments { get; private set; }
AppointmentHasRelationshipWithUnit Entity:
[Serializable]
[HierarchyRoot]
public class AppointmentHasRelationshipWithUnit : Link<AppointmentCore, UnitCore>
{
public AppointmentCore Appointment { get { return Master; } }
public UnitCore Unit { get { return Slave; } }
}
Link class:
[KeyGenerator(typeof(GuidKeyGenerator))]
public class Link<TL,TR> : Entity where TL : IEntity where TR : IEntity
{
/// <summary>
/// The identifier of the entity.
/// </summary>
[Key(Position = 2), Field]
public Guid Id { get; private set; }
/// <summary>
/// DON'T USE THIS PROPERTY FOR PRODUCTION. ONLY FOR LINKING 2 CLASSES. USE THE NAMED INSTANCE.
/// </summary>
[Key(Position = 0), Field]
protected TL Master { get; private set; }
/// <summary>
/// DON'T USE THIS PROPERTY FOR PRODUCTION. ONLY FOR LINKING 2 CLASSES. USE THE NAMED INSTANCE.
/// </summary>
[Key(Position = 1), Field]
protected TR Slave { get; private set; }
}
Updated at 23.06.2010 9:05:16
Everything works fine when the disconnectedState isn't involved. I need this GUID key, because the Master and Slave property as key isn't sufficient for us (this relation can be added more then once).
Can you public the code of AppointmentHasRelationshipWithUnit and Link<tl,tr> constructors? I'm trying to imagine how you set the values of Master and Slave fields there.
Sure!
Link class:
public Link(TL master, TR slave)
: base(master, slave, Guid.NewGuid())
{
}
AppointmentHasRelationshipWithUnit class:
public AppointmentHasRelationshipWithUnit(AppointmentCore parent, UnitCore child)
: base(parent, child)
{
}
Updated at 23.06.2010 10:52:30
So key generator really isn't used, and likely, issue is related to operation registration.
Can you also publish the output of the following code:
var sb = new StirngBuilder();
foreach (var o in disconnectedState.Operations)
sb.AppendLine(o.ToString());
var publishMe = sb.ToString();
It must be executed right before ApplyChanges call.
Create entity, Key = AppointmentCore, (-1)
Create entity, Key = AppointmentCore, (-1)
Set field, Key = AppointmentCore, (-1), Field = Created, Value = 23-6-2010 12:51:04
Set field, Key = AppointmentCore, (-1), Field = CreatedBy, Value = MPXUserCore, (1)
Set field, Key = AppointmentCore, (-1), Field = Start, Value = 23-6-2010 12:51:04
Set field, Key = AppointmentCore, (-1), Field = End, Value = 23-6-2010 13:51:04
Add item to entity set, Key = AppointmentCore, (-1), Field = Units, Item Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463)
Add item to entity set, Key = UnitCore, (2), Field = Appointments, Item Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463)
Create entity, Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463)
Create entity, Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463)
Set field, Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463), Field = Exclude, Value = False
Set field, Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463), Field = Created, Value = 23-6-2010 12:51:04
Set field, Key = AppointmentHasRelationshipWithUnit, (-1, 2, f7d40692-3ca1-457a-bfa6-438bfcc9a463), Field = CreatedBy, Value = MPXUserCore, (1)
Updated at 23.06.2010 12:37:19
At least two bugs are there:
- Duplicated "Create entity" - this bug was reported earlier, so now we'll be able to reproduce it;
- "Create entity" goes after this entity is added to EntitySet.
We'll try to fix this ASAP.
Okay, thank you for your support.
If I can be of any further assistance, please let me know!
This thread was imported from our support forum. The original discussion may contain more detailed answer.
Original topic by stefmen.