nhibernate / NHibernate-Caches

NHibernate Cache Providers
http://nhibernate.info/
GNU Lesser General Public License v2.1
40 stars 31 forks source link

Collection Cache is not removed from Redis when the child entity record is deleted or new one added #126

Open EngSayed opened 11 months ago

EngSayed commented 11 months ago

I have Contact Entity and MailingAddress Entity. Contact has a bag of MailingAddresses which is cached inside Redis but when a MailingAddress is deleted, the association bag of MailingAddress inside Redis is not updated so when we try to load the contact again we are getting an exception

NHibernate.ObjectNotFoundException: No row with the given identifier exists[Entity.MailingAddress#14448]

Here is the definition of entities:

  <class name="MailingAddress" table="MailingAddress">
    <cache include="all" usage="read-write" region="community" />
    <id name="ID" type="System.Int64" column="ID">
      <generator class="identity" />
    </id>
    <property name="Address1" column="Address1" />
    <property name="StreetName" column="StreetName" />
    <property name="Obsolete" column="Obsolete" />
    <many-to-one name="Contact" cascade="none" lazy="proxy" fetch="select" class="Entity.Contact" column="ContactID" />
  </class>

  <class name="Contact" table="Contacts" discriminator-value="0">
    <cache include="all" usage="read-write" region="community" />
    <id name="ID" type="System.Int64" column="ID">
      <generator class="identity" />
    </id>
    <discriminator column="ContactType" type="int" insert="false"/>
    <property name="ContactType" column="ContactType" />
    <property name="DisplayName" column="DisplayName" />
    <property name="GID" column="GID" />
    <property name="Obsolete" column="Obsolete" />
    <bag name="Addresses" inverse="true" cascade="all-delete-orphan" fetch="select" lazy="true">
      <cache include="all" usage="read-write" region="community" />
      <key column="ContactID" />
      <one-to-many class="Entity.MailingAddress" />
    </bag>    
  </class>

If I update MailingAddress record then if I refresh Contact view then I get the updated value and I also see the key value updated. The issue happens if I delete MailingAddress then Contact.Addresses key inside Redis is not removing the deleted MailingAddress and then I am getting.

Everything is done inside transactions.

EngSayed commented 11 months ago

I think same issue happens if I add new mailing address then Contact.Addresses collection are not updated with new mailing address id.

How to fix this issue?

EngSayed commented 11 months ago

@fredericDelaporte, could you please help me fix this issue (if possible) ? or there is no fix for this one?

gliljas commented 11 months ago

What do you mean by "add new mailing address"? Added to the bag?

EngSayed commented 11 months ago

Added separately (with linked ContactID) and saved without saving Contact.

EngSayed commented 11 months ago

Btw, it was saved by NHibernate ORM not by SQL

EngSayed commented 11 months ago

Here is a sample code:

Create MailingAddress:

        private static void CreateMailingAddress(ISessionFactory sessionFactory)
        {
            Console.WriteLine("Enter MailingAddress Name");
            var name = Console.ReadLine();

            Console.WriteLine("Enter Student ID");
            var id = Console.ReadLine();
            long studentId = long.Parse(id);

            var session = sessionFactory.OpenSession();
            using (var tx = session.BeginTransaction())
            {
                var stdnt = session.Get<Student>(studentId);
                MailingAddress mailingAddress = new MailingAddress()
                {
                    Name = name,
                    StudentID = studentId,
                    Student = stdnt
                };
                session.Save(mailingAddress);
                session.Flush();
                tx.Commit();
            }

            session.Dispose();
        }

Read MailingAddress:

        private static void ReadStudent(ISessionFactory sessionFactory)
        {
            var session = sessionFactory.OpenSession();
            using (var tx = session.BeginTransaction())
            {
                long id = 4;
                Student stdnt = session.Get<Student>(id);

                var defaultColor = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                foreach (var address in stdnt.Addresses.OrderBy(a => a.ID))
                {
                    Console.WriteLine($"Mailing Address {address.Name}");
                }
                Console.ForegroundColor = defaultColor;
                Console.ReadLine();
                session.Flush();
                tx.Commit();
            }            

            session.Clear();
            session.Dispose();
        }

Here is a screenshot of running sample console app. Steps are: 1- Read Student with mailing address 2- Create new mailing address linked to student without saving student 3- Read again same student (new mailing address is not added) image

And here is the factory settings:

            cfg.DataBaseIntegration(x =>
            {
                x.ConnectionString = @"Data Source=SQLDEV2012;Initial Catalog=AdventureWorks;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
                x.Driver<SqlClientDriver>();
                x.Dialect<MsSql2012Dialect>();
                x.LogSqlInConsole = true;
            });

            Dictionary<string, string> props = new Dictionary<string, string>
            {
                { "generate_statistics", "true" },
                { "cache.use_second_level_cache", "true" },
                { "cache.provider_class", "NHibernate.Caches.StackExchangeRedis.RedisCacheProvider, NHibernate.Caches.StackExchangeRedis" },
                { "cache.use_query_cache", "true" },
                { "cache.use_sliding_expiration", "true" },
                { "cache.default_expiration", "1800" },
                { "cache.use_minimal_puts", "false" },
                { "cache.configuration", "localhost:6379,allowAdmin=true,defaultDatabase=15,abortConnect=False,syncTimeout=60000,connectTimeout=60000" },
                { "cache.strategy", "NHibernate.Caches.StackExchangeRedis.DefaultRegionStrategy, NHibernate.Caches.StackExchangeRedis" },
                { "cache.region_strategy.two_layer_cache.use_pipelining", "true" },
                { "cache.region_strategy.default.retry_times", "5" },
                { "cache.key_prefix", "LazyApp" }
            };
            cfg.AddProperties(props);
            cfg.AddAssembly(Assembly.GetExecutingAssembly());

            var serializer = new JsonCacheSerializer();
            serializer.RegisterType(typeof(CacheKeyInvalidationMessage), "ckim");
            RedisCacheProvider.DefaultCacheConfiguration.Serializer = serializer;

            var sessionFactory = cfg.BuildSessionFactory();

If I disable second level cache then it works fine.