Hi,

I have the following bug with DataObjects 5 and the new SessionOptions. It quite hard to reproduce because it needs to have :

  • SessionOptions.AutoSaveChanges | SessionOptions.NonTransactionalReads
  • events attached to session.SystemEvents
  • a ref<ientity> object
  • no Query.All<testentity> at some points

Please let me know if you can reproduce it as well

-- CODE

namespace UnitTests.Orm
{
  [TestClass]
  public class DataObjects
  {
    [TestMethod, TestCategory("Transaction.Rollback")]
    public void TestTransactionRollbackWithAutoSaveChangesAndRef()
    {
      // Removing SessionOptions.NonTransactionalReads will make the test pass
      Domain domain = CreateDomain(SessionOptions.ValidateEntities | SessionOptions.AutoActivation | SessionOptions.AutoSaveChanges | SessionOptions.NonTransactionalReads);
      using (Session session = domain.OpenSession())
      {
        Ref<TestEntity> refTestEntity;
        using (TransactionScope t = session.OpenTransaction())
        {
          TestEntity testEntity = new TestEntity(){IsCompressed = false};
          refTestEntity = (Ref<TestEntity>) testEntity;
          t.Complete();
        }

        // Uncommenting the line below will make the test pass : 
        // Query.All<TestEntity>().ToList();

        using (TransactionScope t = session.OpenTransaction())
        {
          TestEntity testEntity = (TestEntity) refTestEntity;
          testEntity.IsCompressed = true;
          // do not complete
        }
        using (TransactionScope t = session.OpenTransaction())
        {
          TestEntity testEntity = (TestEntity)refTestEntity;
          Assert.IsTrue(testEntity.IsCompressed == false);
          t.Complete();
        }
      }
    }

    private Domain CreateDomain(SessionOptions sessionOptions)
    {
      string connectionString = Configuration.Instance.DataObjectsConnectionString;

      DomainConfiguration config = new DomainConfiguration(connectionString);
      config.UpgradeMode = DomainUpgradeMode.Recreate;
      config.Types.Register(typeof(TestEntity).Assembly);

      SessionConfiguration defaultSessionConfig = config.Sessions.Default;
      if (defaultSessionConfig == null)
      {
        defaultSessionConfig = new SessionConfiguration(WellKnown.Sessions.Default);
        config.Sessions.Add(defaultSessionConfig);
      }

      defaultSessionConfig.Options = sessionOptions;
      ConnectionInfo connectionInfo = new ConnectionInfo(connectionString);
      string sqlConnectionString = new Xtensive.Sql.Drivers.SqlServer.DriverFactory().GetConnectionString(connectionInfo);
      using (System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(sqlConnectionString))
      using (DbCommand command = connection.CreateCommand())
      {
        connection.Open();
        command.CommandText = "Select * From sys.fulltext_catalogs";
        bool ftCatalogExists = false;
        using (DbDataReader reader = command.ExecuteReader())
        {
          ftCatalogExists = reader.HasRows;
        }

        if (!ftCatalogExists)
        {
          // full text catalog does not exists : creates it
          command.CommandText = "CREATE FULLTEXT CATALOG [Default] " + "WITH ACCENT_SENSITIVITY = ON " + "AS DEFAULT " + "AUTHORIZATION [dbo]";
          command.ExecuteNonQuery();

          command.CommandText = "ALTER FULLTEXT CATALOG [Default] REBUILD ";
          command.ExecuteNonQuery();
        }
      }
      return Domain.Build(config);
    }
  }

   [Serializable]
  [HierarchyRoot]
  public class TestEntity : Entity, IHasFullTextIndex
  {
    [Field, Key]
    public long Id { get; private set; }

    [Field]
    public bool IsFtEnabled { get; set; }

    [Field]
    public bool? IsFtIndexUpToDate { get; set; }

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

    [Field]
    public bool IsCompressed { get; set; }
  }

   public interface IHasFullTextIndex : IEntity
  {
    [Field]
    long Id { get; }

    [Field]
    bool IsFtEnabled { get; set; }

    [Field]
    bool? IsFtIndexUpToDate { get; set; }

    [Field(Length = Int32.MaxValue)]
    [FullText("French")]
    string FullText { get; set; }
  }

  public class EntityChangeWatcherFullText : IModule
  {
    public void OnDefinitionsBuilt(Xtensive.Orm.Building.BuildingContext context, Xtensive.Orm.Building.Definitions.DomainModelDef model)
    {

    }

    public void OnBuilt(Domain domain)
    {
      domain.SessionOpen += new EventHandler<SessionEventArgs>(domain_SessionOpen);
    }

    private void domain_SessionOpen(object sender, SessionEventArgs e)
    {
      // a session has been created
      e.Session.SystemEvents.TransactionOpened += new EventHandler<TransactionEventArgs>(Session_TransactionOpened);
      e.Session.SystemEvents.TransactionCommitting += new EventHandler<TransactionEventArgs>(Session_TransactionCommitting);
      e.Session.SystemEvents.EntityCreated += new EventHandler<EntityEventArgs>(Session_EntityCreated);
      e.Session.SystemEvents.EntityVersionInfoChanged += new EventHandler<EntityVersionInfoChangedEventArgs>(Session_EntityVersionInfoChanged);
    }

    private void Session_TransactionOpened(object sender, TransactionEventArgs e)
    {
      // transaction opened : create our list to track entity changes
      Transaction transaction = e.Transaction;
      if (transaction.IsNested)
        return;
      Session session = transaction.Session;
      if (session.IsDisconnected)
        return;

      session.Extensions.Set(new EntityChangeWatcherInfo());
    }

    private void Session_EntityCreated(object sender, EntityEventArgs e)
    {
      // entity created
      MarkEntityInSessionListOfEntitiesToIndex(e.Entity);
    }

    private void Session_EntityVersionInfoChanged(object sender, EntityVersionInfoChangedEventArgs e)
    {
      // entity has been modified, or a sub object in an entitysert has been changed
      MarkEntityInSessionListOfEntitiesToIndex(e.Entity);
    }

    private void MarkEntityInSessionListOfEntitiesToIndex(Entity entity)
    {
      // entity to list of entity to mark fot ft indexing for current session
      // (only if necessary)
      try
      {
        Session session = entity.Session;
        if (session.IsDisconnected)
          return;

        EntityChangeWatcherInfo info = session.Extensions.Get<EntityChangeWatcherInfo>();
        if (info == null)
          return;
        if (info.IgnoreThisSession)
          return;

        if (!info.EntitiesToMarkForFullTextIndexing.Contains(entity.Key))
        {
          info.EntitiesToMarkForFullTextIndexing.Add(entity.Key);
        }
      }
      catch
      {
      }
    }

    private void Session_TransactionCommitting(object sender, TransactionEventArgs e)
    {
      // Just before transaction commit, read the list of entities changed for session
      // and for each entity set IsFtIndexUpToDate to false, so that we can asynchronously refresh it
      Transaction transaction = e.Transaction;
      if (transaction.IsNested)
        return;
      Session session = transaction.Session;
      if (session.IsDisconnected)
        return;

      EntityChangeWatcherInfo info = session.Extensions.Get<EntityChangeWatcherInfo>();
      if (info == null)
        return;
      if (info.IgnoreThisSession)
        return;

      foreach (Key key in info.EntitiesToMarkForFullTextIndexing)
      {
        Entity entity = session.Query.SingleOrDefault(key);
        // entity may have been removed
        if (entity != null)
        {
          IHasFullTextIndex ftObject = entity as IHasFullTextIndex;
          if (ftObject != null && ftObject.IsFtIndexUpToDate != false)
          {
            ftObject.IsFtIndexUpToDate = false;
          }
        }
      }
      info.EntitiesToMarkForFullTextIndexing.Clear();
    }
  }

  class EntityChangeWatcherInfo
  {
    public bool IgnoreThisSession { get; set; }

    public HashSet<Key> EntitiesToMarkForFullTextIndexing = new HashSet<Key>();
  }
}

asked Aug 30 '16 at 09:42

Benoit%20Nesme's gravatar image

Benoit Nesme
19131519

edited Aug 30 '16 at 09:46


One Answer:

Hello Benoit Nesme

Thank you for the bug report. I successfully reproduced the problem.

Update: As I remember I gave you my recommendations in one of my responses to your emails. Now here, they are the same. You need to unsubscribe form Session.Events.TransactionCommiting event and subscribe to Session.Events.TransactionPreCommiting event. Probably this code example is from DO 4.6.x times but in 5.0.x the order of events was changed so now we have persist operation before Session.Events.TransactionCommiting event triggered. Changing entities inside of Session.Events.TransactionCommiting event handler leads to very bad results now. We will add some restrictions to prevent such persistent objects' changes between persist and actual SQL transaction commit, otherwise data loss may occur.

answered Aug 31 '16 at 06:13

Alexey%20Kulakov's gravatar image

Alexey Kulakov
52715

edited Jun 01 at 13:39

Nice, let me know when you have a fix

(Aug 31 '16 at 06:47) Benoit Nesme Benoit%20Nesme's gravatar image

Hi Alexey, Have you made any progress on this issue ? This is blocking us to migrate to DO5

(Sep 20 '16 at 05:16) Benoit Nesme Benoit%20Nesme's gravatar image

Hi Alexey, I have tried the latest build 5.0.13 of march 2017, and the bug is still here. We are quite disapointed. Any news about the fix ?

(Jun 01 at 05:01) Benoit Nesme Benoit%20Nesme's gravatar image

Hi Benoit Nesme, I've updated my answer. Please, read 'Update' part. I remember we discussed this problem outside the forum. I gave you my recommendations and you didn't reply anything so I decided you solved the problem.

(Jun 01 at 13:42) Alexey Kulakov Alexey%20Kulakov's gravatar image

Hi Alexey, we are going to migrate to DO5 in the incoming days, I will post new bugs, if we find any, in new support tickets. We are testing the new SessionOptions according to our needs. There is an impact because we were using AutoTransactionOpenMode, but it's better to remove it and force ourselves to open transactions. NonTransactionalReads has unexpected behaviours to us. More to follow soon

(Jun 02 at 03:56) Benoit Nesme Benoit%20Nesme'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

Subscription:

Once you sign in you will be able to subscribe for any updates here

Tags:

×4
×4
×1

Asked: Aug 30 '16 at 09:42

Seen: 1,624 times

Last updated: Jun 02 at 03:57

powered by OSQA