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.

asked Jun 21 '10 at 08:48

Editor's gravatar image

Editor
46154156157


One Answer:

The only public option you have here is:

  • Manually filter DisconnectedState.Operations (of OperationLog type) to extract the operations you're going to replay on remote server. OperationLog is enumerable.
  • Create a new OperationLog and add these operations to it
  • Deliver it to remote server and replay them there.

Issues:

  • You should ensure these operations won't be applied twice further
  • OperationLog.Replay returns KeyMapping specifying how new keys in DisconnectedState are mapped to actual ones. If you'll study DisconnectedState.ApplyChanges method, it will be clear that this mapping must be also applied to cached state, and there is no public way to do this. You can can use reflection to perform this, but another problem appears there: some operations in OperationLog may also reference old keys. There is no way to remap keys stored inside operations, but remapping actually anyway happens when they're applied. So this is possible as well, but again, you need to hack the framework to do this. Of course, a lot depends on your actual case: i.e. if your're going to save just already existing entities - no problems; if all the operations related to all newly created entities you're going to save are filtered and applied as well - again, no problems with remapping the keys in OperationLog.
  • When DO4 logs the operation related to a particular entity for the first time in OperationLog, it prepents it by ValidateVersionOperation ensuring optimistic version checks. And since you don't call ApplyChanges / clear or filter OperationLog (this can be achieved via reflection), there optimistic version checks won't appear for objects you already persisted by your own, and this is generally wrong.

So a bit modified idea:

  • Let's imagine you populate your DisconnectedState just once - e.g. via PopulateMyDisconnectedState method
  • To apply a part of operations, you split the OperationLog into two parts: sequenceToApply and sequenceToLeave
  • sequenceToApply is delivered to remote server and replayed there
  • Then PopulateMyDisconnectedState produces new state on the client and applies sequenceToLeave to the Session it is attached to
  • Here we have a DisconnectedState containing only sequenceToLeave - i.e. the procedure with partial application can be repeated from this moment, if necessary.

Sorry, no code example - this will eat few hours, although the case seems pretty unusual. But if you'll face any issues, describe them here - I'll try to help.


> 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.

> 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.

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.

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.

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.

Sorry, I just looked up the example more closer, and found that actually this isn't the "standard" case:

  • Key of Link<tl, tr=""> includes both generated and externally defined parts.
  • As far as I remember, default key generators require that key type is exactly mapped to generator's type (so this won't work)
  • If you use your own key generator, you must ensure all the field values are provided by it in Tuple it returns. In your case you fill just the first one; moreover, since its type isn't the same as expected one (first field of its key has the same type as first field of AppointmentCore key), and there are no errors, I suspect this generator is really never invoked.

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.


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.


Btw, both were quite simple:

On the other hand, I fixed few more, and they were more complex ;)

answered Jun 21 '10 at 18:57

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

No, there are no known problems related to GUIDs. Could you provide a stack trace?

(Jun 21 '10 at 18:57) Alex Yakunin Alex%20Yakunin's gravatar image

stefmen wrote: Any thoughts on this?

(Jun 21 '10 at 18:57) Editor Editor's gravatar image

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.

(Jun 21 '10 at 18:57) Alex Yakunin Alex%20Yakunin's gravatar image

Test is here: http://goo.gl/BrnM

Both issues are reproduced on it. Hopefully, we'll fix this today.

(Jun 21 '10 at 18:57) Alex Yakunin Alex%20Yakunin's gravatar image

Bugs are fixed. Updated installers will be published in a day or two.

(Jun 21 '10 at 18:57) Alex Yakunin Alex%20Yakunin's gravatar image

And finally, I also added output of DisconnectedState.Operations on ApplyChanges to log.

(Jun 21 '10 at 18:57) Alex Yakunin Alex%20Yakunin's gravatar image

stefmen wrote: Nice!! :D

Thanks!

(Jun 21 '10 at 18:57) Editor Editor's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA