1

Ok, this has been a rather frustrating experience :) I am trying to work with an interface based domain model and I can get ALMOST everything working with the exception of the ability to actually save data

Here is what I have so far.

public interface IEntity {
     Guid Id {get;set;}
}

public interface IProduct : IEntity {
     string Name {get;set;}
     string SKU {get;set;}
     decimal MSRP {get;set;}
}

public class Product : IProduct {
     public virtual Guid Id {get;set;}
     public virtual string Name {get;set;}
     public virtual string SKU {get;set;}
     public virtual decimal MSRP {get;set;}
}

public interface IOrder : IEntity {
     DateTime CreatedOn {get;set;}
     IList<IOrderLine> Lines {get;set;}
}

public class Order : IOrder {
     public virtual Guid Id {get;set;}
     public virtual DateTime CreatedOn {get;set;}
     public virtual IList<IOrderLine> Lines {get;set;}
}

public interface IOrderLine : IEntity {
     IProduct Product {get;set;}
     int Qty {get;set;}
     decimal Price {get;set;}
}

public class OrderLine : IOrderLine {
     public virtual Guid Id {get;set;}
     public virtual IProduct Product {get;set;}
     public virtual int Qty {get;set;}
     public virtual decimal Price {get;set;}
}

public class ProductMap : ClassMap<IProduct>
{
     public ProductMap() {
         Id(x => x.Id);

         Map(x => x.Name);
         Map(x => x.SKU);
         Map(x => x.MSRP);
     }
}

public class OrderMap : ClassMap<IOrder>
{
     public OrderMap() {
         Id(x => x.Id);

         Map(x => x.CreatedOn);

         HasMany(x => x.Lines)
            .KeyColumns.Add("Id");
     }
}

public class OrderLineMap : ClassMap<IOrderLine>
{
     public OrderLineMap() {
         Id(x => x.Id);

         Map(x => x.Qty);
         Map(x => x.Price);

         References(x => x.Product)
             .PropertyRef(x => x.Id)
             .ForeignKey("productId");
     }
}

public static class NHibernateUtils
{
    public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer persistenceConfigurer)
    {           
        return Fluently.Configure()
            .Database(persistenceConfigurer)
            .Mappings(m =>
            {
                m.FluentMappings.Add<ProductMap>();
                m.FluentMappings.Add<OrderLineMap>();
                m.FluentMappings.Add<OrderMap>();
            })
            .ExposeConfiguration(c => new SchemaExport(c).Create(false, true))
            .BuildConfiguration()
            .BuildSessionFactory();
    }
}

This works to create a valid scheme (I am testing sing Sqlite and can see the db created correctly). The issue is when I try to save an actual entity.

var p = new Product() { Name = "Product Sample", SKU = "12345", MSRP = 1.0 };
var sessionFactory = NHibernateUtils.CreateSessionFactory(...);

using (var session = sesionFactory.OpenSession())
{
    using (var transaction = session.BeginTransaction())
    {                   
        session.Save(p);

        transaction.Commit();
    }
}

This fails with an error "no persister for Product"

ok, I can see that -- so next I tried this

session.Save((IProduct)p);

and that gave me the error "no persister for IProduct"

So what am I missing?

Note: Here is a link to a gist that actually shows the issue in a running console app https://gist.github.com/ravensorb/14193136002adbb3ec2fac07c026f921

2 Answers2

3

It seems I've made it. Try to use this overload object Save(string entityName, object obj);.

Something like this:

var p = new Product() { Name = "Product Sample" + i, SKU = "12345", MSRP = 1.0m };
session.Save(typeof(IProduct).FullName, p);

I investigated the source code of NHibernate persister and found that persisters are stored in Dictionary<string, IEntityPersister> where keys comes from Configuration.ClassMappings.EntityName(interface names in your cases). But when you call just Save(p) your entityName will be equal to your entity class name, not interface.

I hope that is clearly explains:

no persister for Product

Roman Koliada
  • 4,286
  • 2
  • 30
  • 59
  • That did it! I had tried using the shortname and didn't even think about using the long name. Thanks! – Shawn Anderson Feb 16 '18 at 15:02
  • Or strange update -- the code is "working" however there is nothing being added to the database and no errors are being thrown. Going to dig into the logging to see whats going on. – Shawn Anderson Feb 16 '18 at 16:27
  • @ShawnAnderson Make sure you have called `transaction.Commit()`. – Roman Koliada Feb 16 '18 at 18:18
  • Thanks :) I noticed that without transactions nothing gets written, So after I added the transaction I not get an error about dyhdrating the Id property of the Product object when trying to store an orderline. – Shawn Anderson Feb 17 '18 at 18:51
0

Here is working mappings:

public class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        Id(x => x.Id).GeneratedBy.Guid();

        Map(x => x.Name);
        Map(x => x.SKU);
        Map(x => x.MSRP);
    }
}

public class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Id(x => x.Id).GeneratedBy.Guid();

        Map(x => x.CreatedOn);

        HasMany<OrderLine>(x => x.Lines).Cascade.SaveUpdate();
    }
}

public class OrderLineMap : ClassMap<OrderLine>
{
    public OrderLineMap()
    {
        Id(x => x.Id).GeneratedBy.Guid();

        Map(x => x.Qty);
        Map(x => x.Price);

        References<Product>(x => x.Product);
    }
}

Some notes:

  1. You need to use a concrete class as a generic type parameter in you mappings.
  2. You have to explicitly specify the concrete class in References and HasMany mappings.
  3. You don't need to cast to to an interface when saving in this scenario.

Minor things:

  1. It's easier to add all mappings from the assembly: .AddFromAssemblyOf<ProductMap>().
  2. I've added guid generator and cascade options to mapping(probably you don't want them).
  3. There is no such overload for .KeyColumns.Add(x => x.Id)(at least in latest Fluent Nhibernate version).
  4. You forgot to call transaction.Commit().

Take a look at the very similar question.

Here is how I tested saving:

    using (var transaction = session.BeginTransaction())
    {
        for (int i = 0; i < 10; i++)
        {
            Product p = new Product() { Name = "Product Sample" + i, SKU = "12345", MSRP = (1.0m + i) };
            session.Save(p);

            Order order = new Order
            {
                CreatedOn = DateTime.Now,
                Lines = new List<IOrderLine> {
                    new OrderLine{ Price = 12, Qty = 2, Product = p}
                }
            };
            session.Save(order);
        }

        transaction.Commit();
    }
Roman Koliada
  • 4,286
  • 2
  • 30
  • 59
  • Thanks for this -- that hard part is, in my actual scenario I do not have the concrete types as they are actually provided by through DI/IoC pattern. I have implemented an interceptor to handle this during reading -- its the writing that is the issue, which if you think about it is a bit odd. From writing there is no need for a concrete class reference as everything that is needed is on the interface. – Shawn Anderson Feb 15 '18 at 15:13
  • It's strange, because I also use 2.0.3 version. But I have this: http://prntscr.com/if5n1z – Roman Koliada Feb 15 '18 at 15:23
  • In regards to the KeyColumns.Add -- its a typo on my part -- should be KeyColoumns.Add("id"). – Shawn Anderson Feb 15 '18 at 15:26
  • @ShawnAnderson Do you have a repo? I just wanted to see how you implemented reading, especially DI part. It must be interesting – Roman Koliada Feb 15 '18 at 15:27
  • I just added a gist of this sample that shows the issue. Once I get it all working, I'll post a full blog article :) – Shawn Anderson Feb 15 '18 at 15:47