DO 5.0.4

We have entities:

  1. TestEntity : Entity
  2. TestEntity1 : TestEntity
  3. TestEntity2 : TestEntity
  4. TestEntity11 : TestEntity1

Run recreate

Change TestEntity11 ancestor from TestEntity1 on TestEntity2

Run perform safely

Problems:

  1. No upgrade warnings
  2. Data in TestEntity1 table exist after perform
  3. Data in TestEntity2 don't exist after perform (TestEntity11 has PK only on TestEntity table, after perform TestEntity2 table doesn't have data for TestEntity11, but no errors)

Code sample:

using System;

using Xtensive.Orm;
using Xtensive.Orm.Configuration;

class Program
{
    static void Main(string[] args)
    {
        var dc = new DomainConfiguration("sqlserver://localhost/DO40-Tests");

        dc.Types.Register(typeof(TestEntity));
        dc.Types.Register(typeof(TestEntity1));
        dc.Types.Register(typeof(TestEntity11));
        dc.Types.Register(typeof(TestEntity2));

        dc.UpgradeMode = DomainUpgradeMode.Recreate; // Change to PerformSafely before second run
        var sessionConfiguration = new SessionConfiguration(SessionOptions.AutoActivation | SessionOptions.ServerProfile);

        using (var domain = Domain.Build(dc))
        {
            using (var session = domain.OpenSession(sessionConfiguration))
            using (session.Activate())
            using (var t = session.OpenTransaction())
            {
                new TestEntity11 { TestField = "Test" };

                t.Complete();
            }
        }
    }
}

[HierarchyRoot]
[Serializable]
public class TestEntity : Entity
{
    [Key]
    [Field(Nullable = false)]
    public Guid Id { get; private set; }

    [Field]
    public string TestField { get; set; }
}

[Serializable]
public class TestEntity1 : TestEntity
{
}

[Serializable]
public class TestEntity11 : TestEntity1 // Change to TestEntity2 before second run
{
}

[Serializable]
public class TestEntity2 : TestEntity
{
}

Results in DB:

After recreate

alt text

After perform

alt text

asked Jun 05 '15 at 07:31

Anton%20Guschin's gravatar image

Anton Guschin
73303035

Hello Anton. Changing hierarchy is not simple operation. You should change it more carefully with creation of new class and then copying data from old to new table. I will create example based on your hierarchy model and post it as answer

(Jun 08 '15 at 04:33) Alexey Kulakov Alexey%20Kulakov's gravatar image

One Answer:

Hello again Anton.

As I said, type movements are not easy to implement. You need write it manually.

Correct way to do what you need is bellow. I added some another fields and types in model.

So, first model - model we upgrade from:


namespace ModelV1
{
  [HierarchyRoot]
  [Serializable]
  public class TestEntity : Entity
  {
    [Key]
    [Field(Nullable = false)]
    public Guid Id { get; private set; }

[Field]
public string TestField { get; set; }

}

[Serializable] public class TestEntity1 : TestEntity { [Field] public string TestEntityField { get; set; } }

[Serializable] public class TestEntity11 : TestEntity1 { [Field] public TestEntity1 ReferenceField { get; set; } }

[Serializable] public class TestEntity2 : TestEntity { [Field] public string TestEntity2Field { get; set; } }

[Serializable] [HierarchyRoot] public class TestEntity3 : Entity { [Key, Field] public Guid Id { get; set; }

[Field]
public TestEntity11 TestEntity11 { get; set; }

} }

And second model - model we upgrade to:


namespace ModelV2
{
  [HierarchyRoot]
  [Serializable]
  public class TestEntity : Entity
  {
    [Key]
    [Field(Nullable = false)]
    public Guid Id { get; private set; }

[Field]
public string TestField { get; set; }

}

[Serializable] public class TestEntity1 : TestEntity { [Field] public string TestEntityField { get; set; } }

[Serializable] [Recycled("ModelV1.TestEntity11")] public class OldTestEntity11 : TestEntity1 { [Field] public TestEntity1 ReferenceField { get; set; } }

[Serializable] public class TestEntity11 : TestEntity2 { [Field] public TestEntity1 ReferenceField { get; set; } }

[Serializable] public class TestEntity2 : TestEntity { [Field] public string TestEntity2Field { get; set; } }

[Serializable] [HierarchyRoot] public class TestEntity3 : Entity { [Key, Field] public Guid Id { get; set; }

[Field]
[Recycled("TestEntity11"), Obsolete]
public OldTestEntity11 OldTestEntity11 { get; set; }

[Field]
public TestEntity11 TestEntity11 { get; set; }

} }

As you see, I renamed TestEntity11 to OldTestEntity11 and marked it by [Recycled] attribute. Also I wrote new TestEntity11 which is ancestor of TestEntity2 now. Note that I added another referencing field to new TestEntity into TestEntity3 class. I didn't remove old field and marked it by [Recycled] attribute too.

Now we need to add upgrade handler which copies data from old TestEntity11 to new TestEntity:


public class CustomUpgradeHandler : UpgradeHandler
{
  public override bool CanUpgradeFrom(string oldVersion)
  {
    return true;
  }

public override void OnUpgrade() { // we have old and new TestEntity11 both in here // and we can do something with them. // we need map from old IDs to new Entities var testEntity11Map = new Dictionary<guid, testentity11="">();

// we get old entities and create new entity for every single old entity
var oldTestEntity11s = Session.Demand().Query.All<OldTestEntity11>();
var entity3s = Session.Demand().Query.All<TestEntity3>();
foreach (var oldTestEntity in oldTestEntity11s) {
  var testEntity = new TestEntity11() {
                      ReferenceField = oldTestEntity.ReferenceField,
                      TestField = oldTestEntity.TestField,
                      TestEntity2Field = oldTestEntity.TestEntityField // probably, I don't know specific of your model. Let it be.
                    };
    // creating map
    testEntity11Map.Add(oldTestEntity.Id, testEntity);
  }

  // TestEntity3 has reference to TestEntity11
  // and we remap from old to new TestEntity11 using our map;
  foreach (var order in entity3s) {
    order.TestEntity11 = testEntity11Map[order.OldTestEntity11.Id];
  }

  // also you can remove all old TestEntity11 after remapping all references.
  // I think it is easy now.
}

}

After upgrade you will not have recycled types and fields and all data you need will be transformed correctly (if you will write it correctly, of course).

And domain configurations for first and second domains. First:


var domainConfiguration = new DomainConfiguration("sqlserver://localhost/DO40-Tests");
domainConfiguration.Types.Register(typeof (TestEntity));
domainConfiguration.Types.Register(typeof (TestEntity1));
domainConfiguration.Types.Register(typeof (TestEntity11));
domainConfiguration.Types.Register(typeof (TestEntity2));
domainConfiguration.Types.Register(typeof (TestEntity3));

domainConfiguration.UpgradeMode = DomainUpgradeMode.Recreate;

Second:


var domainConfiguration = new DomainConfiguration("sqlserver://localhost/DO40-Tests");
domainConfiguration.Types.Register(typeof (NewModel.TestEntity));
domainConfiguration.Types.Register(typeof (NewModel.TestEntity1));
domainConfiguration.Types.Register(typeof (NewModel.TestEntity11));
domainConfiguration.Types.Register(typeof (NewModel.TestEntity2));
domainConfiguration.Types.Register(typeof (NewModel.TestEntity3));
domainConfiguration.Types.Register(typeof (NewModel.OldTestEntity11));
domainConfiguration.Types.Register(typeof (NewModel.CustomUpgradeHandler));

domainConfiguration.UpgradeMode = DomainUpgradeMode.PerformSafely;

answered Jun 08 '15 at 09:38

Alexey%20Kulakov's gravatar image

Alexey Kulakov
77225

It's all good

But in reality we get inconsistent data after perform safely (by accident or by hand is another question)

And at least DO must throw exception for this kind of changes

(Jun 09 '15 at 05:11) Anton Guschin Anton%20Guschin's gravatar image

Hello Anton

Unfortunately, DO can't detect cases when one of entities changes its ancestor and resolve them on its own for now and you have to write some parts of upgrade manually. I'm only trying to help you with handling model changes on version you have.

You have API which provides you influence on upgrade process and do what you need. It takes much time but you can do it.

(Jun 15 '15 at 12:59) Alexey Kulakov Alexey%20Kulakov's gravatar image

Continue.

Changing of upgrade will affect a lot of things. And it must be tested very good before release. It must be implemented in one of beta version. I see the problem. I agree that would be great if DO could resolve situations like this by itself. But Rome was not built in a day. We'll try to resolve it in one of beta versions.

I hope you understand me.

(Jun 15 '15 at 13:06) Alexey Kulakov Alexey%20Kulakov'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