1

Suppose I have the following model

public class Customer
{
    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
}

public class ActivityLog
{
    public virtual Guid Id { get; set; }
    public virtual Guid CustomerId { get; set; }
    public virtual Customer Customer { get; set; }
    public virtual DateTime ActivityDate { get; set; }
}

I would like to be able to remove customer and all corresponding ActivityLog items, by calling

 session.Delete(customer);

What I don't want to is to have a property List<ActivityLog> Logs in my Customer class. Is it possible to achieve in Nhibernate? I have tried EF, and it works there, but in HN getting reference constraint exception.

My mappings:

public class CustomerMap : ClassMapping<Customer>
{
    public CustomerMap()
    {
        Id(x => x.Id, map => map.Generator(Generators.GuidComb));
        Property(x => x.Name);

    }
}

public class ActivityLogMap : ClassMapping<ActivityLog>
{
    public ActivityLogMap()
    {
        Id(x => x.Id, map => map.Generator(Generators.GuidComb));
        Property(x => x.ActivityDate);
        ManyToOne(x => x.Customer, mapping =>
        {

            mapping.Class(typeof(Customer));
            mapping.Column("CustomerId");

        });
    } 
}

Maybe possible to have some extension hook and inspect mapping and do it manually for Nhibernate?

Edit: Here how it works with EF

public class CustomerEFMap : EntityTypeConfiguration<Customer>
{
    public CustomerEFMap()
    {
        ToTable("Customer");
        HasKey(x => x.Id);
        Property(x => x.Name);
    }
}

public class ActivityLogEFMap : EntityTypeConfiguration<ActivityLog>
{
    public ActivityLogEFMap()
    {
        ToTable("ActivityLog");
        HasKey(x => x.Id);
        HasRequired(x => x.Customer).WithMany().HasForeignKey(x => x.CustomerId);
    }
}

using (var context = new ObjectContext())
        {
            var customer = context.Set<Customer>().Find(id);
            context.Set<Customer>().Remove(customer);

            context.SaveChanges();
        }

Having Customer and corresponding ActivityLog in DB, removes both

petrov.alex
  • 1,089
  • 2
  • 12
  • 20

2 Answers2

0

I would like to be able to remove customer and all corresponding ActivityLog items, by calling

session.Delete(customer);

What I don't want to is to have a property List Logs in my Customer class. Is it possible to achieve in Nhibernate?

NO. Impossible, not intended, not supported. NHibernate is ORM tool and will/can care about relations. Without relations, no care (no cascading)

But, we can always hide that List Property by making it protected

public class Customer
{
    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
    // HIDDEN in fact from consumers, managed by NHibernate
    protected virtual IList<ActivitLog> { get; set; }
}

and the mapping with cascade in place - will do what is required.

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Well, EF is also ORM, but i could achieve it there. I want to develop something like plugin system, so I do not want to modify Customer when new plugin is developed – petrov.alex Jan 22 '16 at 07:39
  • You are asking about NHibernate not about EF, and my answer does target NHibernate – Radim Köhler Jan 22 '16 at 07:40
0

As Radim Köhler states, I don't think there's any simple way to map the behaviour of a cascade without actually having the relationship. But as your reason for this is a plugin-based architecture, making use of a partial class might be what you need.

You can include the collection of ActivityLogs as a protected collection as part of a partial class in the plugin DLL. If your mapping then maps the collection, deleting the Customer will delete the ActivityLogs.

The following example should work - note that I'm using FluentNhibernate for the mapping.

This solution does rely on you be able to mark the Customer class as partial.

using FluentAssertions;
using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Mapping;
using NHibernate.Linq;
using NHibernate.Tool.hbm2ddl;
using NUnit.Framework;
using System;
using System.Collections.Generic;

namespace MapTest
{
    public partial class Customer
    {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
    }

    public class ActivityLog
    {
        public virtual Guid Id { get; set; }
        public virtual Customer Customer { get; set; }
        public virtual DateTime ActivityDate { get; set; }
    }

    public class CustomerMap : ClassMap<Customer>
    {
        public CustomerMap()
        {
            Id(x => x.Id).GeneratedBy.Guid();
            Map(x => x.Name);

            HasMany<ActivityLog>(Reveal.Member<Customer>("ActivityLogs")).Cascade.All();
        }
    }

    public class ActivityLogMap : ClassMap<ActivityLog>
    {
        public ActivityLogMap()
        {
            Id(x => x.Id).GeneratedBy.Guid();
            Map(x => x.ActivityDate);
            References(x => x.Customer);
        }
    }

    // Part of your plugin DLL
    public partial class Customer
    {
        protected virtual IList<ActivityLog> ActivityLogs { get; set; }
    }

    [TestFixture]
    public class PartialClassCascade
    {
        [Test]
        public void RunOnceToSetupDb()
        {
            Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2012.ConnectionString(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=MapTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>())
                .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
                .BuildSessionFactory();
        }

        [Test]
        public void DeletingCustomerWithActivityLogs()
        {
            var sessionFactory = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2012.ConnectionString(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=MapTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>())
                .BuildSessionFactory();

            using (var session = sessionFactory.OpenSession())
            using (var tx = session.BeginTransaction())
            {
                session.CreateQuery("delete ActivityLog a").ExecuteUpdate();
                session.CreateQuery("delete Customer c").ExecuteUpdate();

                tx.Commit();
            }

            var homerId = default(Guid);
            using (var session = sessionFactory.OpenSession())
            using (var tx = session.BeginTransaction())
            {
                var homer = new Customer
                {
                    Name = "Homer Simpson"
                };
                var monty = new Customer
                {
                    Name = "Monty Burns"
                };
                session.Save(homer);
                session.Save(monty);

                homerId = homer.Id;

                var activityLog1 = new ActivityLog
                {
                    Customer = homer,
                    ActivityDate = DateTime.Now,
                };
                var activityLog2 = new ActivityLog
                {
                    Customer = monty,
                    ActivityDate = DateTime.Now.AddDays(1),
                };
                session.Save(activityLog1);
                session.Save(activityLog2);

                tx.Commit();
            }

            using (var session = sessionFactory.OpenSession())
            using (var tx = session.BeginTransaction())
            {
                var customer = session.Get<Customer>(homerId);
                session.Delete(customer);

                tx.Commit();
            }

            using (var session = sessionFactory.OpenSession())
            using (var tx = session.BeginTransaction())
            {
                var customers = session.Query<Customer>();
                var activityLogs = session.Query<ActivityLog>();

                customers.Should().HaveCount(1);
                activityLogs.Should().HaveCount(1);
            }
        }
    }
}
Community
  • 1
  • 1
ngm
  • 7,277
  • 1
  • 53
  • 62
  • You can not define partial class in a separate DLL – petrov.alex Jan 24 '16 at 11:47
  • Oh, I didn't actually know that. You're correct though: http://stackoverflow.com/q/3858649/206297. It may be possible to follow a similar strategy to this, but making use of inheritance in your separate DLL. I'm not certain if it will work in your scenario though. – ngm Jan 25 '16 at 08:32