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 Nesme
43●20●20●24