Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
EngSayed opened this issue Nov 23, 2023 · 6 comments

Comments

@EngSayed
Copy link

EngSayed commented Nov 23, 2023

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 EngSayed changed the title Collection Cache is not updated when the child entity is deleted Collection Cache is not removed when the child entity is deleted Nov 23, 2023
@EngSayed EngSayed changed the title Collection Cache is not removed when the child entity is deleted Collection Cache is not removed from Redis when the child entity record is deleted Nov 23, 2023
@EngSayed
Copy link
Author

EngSayed commented Nov 23, 2023

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 EngSayed changed the title Collection Cache is not removed from Redis when the child entity record is deleted Collection Cache is not removed from Redis when the child entity record is deleted or new one added Nov 23, 2023
@EngSayed
Copy link
Author

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

@gliljas
Copy link
Member

gliljas commented Nov 27, 2023

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

@EngSayed
Copy link
Author

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

@EngSayed
Copy link
Author

Btw, it was saved by NHibernate ORM not by SQL

@EngSayed
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants