Given these classes:
using System.Collections.Generic;
namespace FluentMappingsQuestion
{
public class Entity
{
public virtual int Id { get; set; }
public virtual IDictionary<string, Property> Properties { get; set; }
}
public class Property
{
public virtual Entity OwningEntity { get; set; }
public virtual string Name { get; set; }
public virtual int Value { get; set; }
public virtual decimal OtherValue { get; set; }
}
}
How can I map them using NHibernate (preferably fluent flavor) so that doing this is possible:
[Test]
public void EntityPropertyMappingTest()
{
using (var session = _factory.OpenSession())
{
var entity = new Entity();
// (#1) I would *really* like to use this
entity.Properties["foo"] = new Property { Value = 42, OtherValue = 42.0M };
session.Save(entity);
session.Flush();
// (#2) I would like the entity below to "update itself"
// on .Save or .Flush. I got it to work with .Load()
Assert.AreEqual(42, entity.Properties["foo"].Value);
Assert.AreEqual(42.0M, entity.Properties["foo"].OtherValue);
Assert.AreEqual("foo", entity.Properties["foo"].Name);
Assert.AreEqual(entity, entity.Properties["foo"].Owner);
}
}
I have almost managed to do this with these mappings:
// class EntityMap : ClassMap<Entity>
public EntityMap()
{
Id(x => x.Id);
HasMany(x => x.Properties)
.Cascade.AllDeleteOrphan()
.KeyColumn("EntityId")
.AsMap(x => x.Name);
}
// class PropertyMap : ClassMap<Property>
public PropertyMap()
{
Id(x => x.Id);
References(x => x.OwningEntity).Column("EntityId");
Map(x => x.Name).Length(32);
Map(x => x.Value);
{
The problems I have:
If I make Entity.Properties .Inverse(), it starts breaking
If I don't make it .Inverse() then NHibernate does: INSERT(Entity), INSERT(Property), UPDATE(Property) instead of just INSERT(Entity), INSERT(Property)
If I make Property.Name .Not.Nullable(), it starts breaking
If I don't make it .Not.Nullable(), I have a hole in my db schema
How should I change my mappings?
I worked around this by specifying this mapping:
HasMany<Property>(Reveal.Member<Entity>("InternalProperties"))
.AsMap(p => p.Name)
.Cascade.AllDeleteOrphan()
.Inverse();
and creating two properties of type IDictionary<string, Property>: Properties and InternalProperties. The first one is a proxy dictionary over the second one, and deals with setting the OwningEntity and Name properties for the Property entries.
Related
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
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.
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);
}
}
}
}
Hi i am stuck with one problem while dealing with fluent nhibernate.
My user entity is below
public class UserEntity
{
public virtual int Userid { get; set; }
public virtual CompanyEntity CompanyId { get; set; }
public virtual string Name { get; set; }
public virtual string Email { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set;}
public virtual decimal MinLimit { get; set;}
public virtual decimal MaxLimit { get; set;}
public virtual DateTime Birthdate { get; set;}
public virtual RoleEntity Roleid { get; set; }
public virtual int CreatedBy { get; set;}
public virtual DateTime CreatedDate { get; set;}
public virtual bool Active { get; set;}
}
}
and mapping class is as below
public class UserMap : ClassMap
{
public UserMap()
{
Id(x => x.Userid);
References(x => x.CompanyId).Column("CompanyId").Cascade.All();
Map(x => x.Name);
Map(x => x.Email);
Map(x => x.Username);
Map(x => x.Password);
Map(x => x.MinLimit);
Map(x => x.MaxLimit);
Map(x => x.Birthdate);
References(x => x.Roleid).Column("Roleid").Cascade.All();
Map(x => x.CreatedBy);
Map(x => x.CreatedDate);
Map(x => x.Active);
Table("tblUsers");
}
}
Now when ever i am trying to execute my program it gives me error like.
Could not determine type for: ProductPO.Models.Entites.UserEntity, ProductPO, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, for columns: NHibernate.Mapping.Column(CreatedBy)
My helper class is as below
private static ISessionFactory _sessionFactory;
private static ISessionFactory sessionFactory
{
get
{
if (_sessionFactory == null)
{
initialisationFactory();
}
return _sessionFactory;
}
}
private static void initialisationFactory()
{
try
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(#"Server=10.10.10.10;Database=Product;uid=sa;pwd=12345;Trusted_Connection=false;").ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<CompanyEntity>().ExportTo("d:\\"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<ModuleEntity>().ExportTo("d:\\"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<RoleEntity>().ExportTo("d:\\"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserEntity>().ExportTo("d:\\"))
.ExposeConfiguration(cfg => new SchemaExport(cfg))
.BuildSessionFactory();
}
catch (Exception e)
{
throw;
}
}
public static ISession OpenSession()
{
return sessionFactory.OpenSession();
}
Thanks in advance
Not sure if this will fix the problem but your mapping class should pass the model type into the base class like this:
public class UserMap : ClassMap<UserEntity>
Also does the CreatedBy column exist in the underlying table?
Could you post the inner exception if there is one....
The most likely cause is that the assembly that is set your UserEntity is not the same as it is defined your UserMap.
Configure your mappings:
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString("{OMITED}").ShowSql())
.Mappings(m => {
AddFromAssemblyOf<UserMap>()
})
.ExposeConfiguration( c =>
new SchemaExport(c)
.Execute(false, true, false)
)
No need to add one to one mapping. This way the Fluent NHibernate search the entire assemblie classes that inherit from ClassMap
I have a class MoneyCompositeUserType : ICompositeUserType
Which I use like so in a mapping:
public InvoiceMap()
{
Table("Invoices");
Id(x => x.Id);
Map(x => x.Customer);
Map(x => x.Number);
Map(x => x.TotalValue)
.CustomType(typeof(MoneyCompositeUserType))
.Columns.Clear()
.Columns.Add("TotalValue_Amount", "TotalValue_Currency");
}
And here is the class:
public class Invoice
{
public virtual int Id { get; set; }
public virtual int Number { get; set; }
public virtual string Customer { get; set; }
public virtual Money TotalValue { get; set; }
}
I thought that the value would be lazy loaded, that's the point of the virtual right? But the NullSafeGet method of the composite user type is called when the item is loaded. Here is my failing test:
using (var session = NHibernateHelper.OpenSession())
{
var fromDb = session.Get<Invoice>(invoice.Id);
Assert.IsFalse(NHibernate.NHibernateUtil.IsPropertyInitialized(fromDb, "TotalValue"));
}
Why is that property not being lazy loaded?
I thought that the value would be lazy loaded, that's the point of the virtual right?
Not exactly -- NHibernate needs your properties to be virtual so that it can use a proxy class in place of your class to enable lazy loading. Lazy loading is not enabled just because a property is marked virtual.
I believe all you should have to do is mark the individual property with .LazyLoad in your mapping (see lazy properties for more information):
Map(x => x.TotalValue)
.LazyLoad() // <-----
.CustomType(typeof(MoneyCompositeUserType))
.Columns.Clear()
.Columns.Add("TotalValue_Amount", "TotalValue_Currency");
I attempted to extract some common properties to a base class and map with Fluent Nhibernate. In addition, I also attempted to add a second level of inheritance.
//Base entity class
public class EntityBase : IEntityBase
{
public EntityBase()
{
CreatedDate = DateTime.Now;
}
public virtual DateTime? CreatedDate { get; set; }
public virtual int Id { get; set; }
public virtual int Version { get; set; }
}
//Base Entity Mapping
public class EntityBaseMap: ClassMap<EntityBase>
{
public EntityBaseMap()
{
UseUnionSubclassForInheritanceMapping();
Id(x => x.Id);
Version(x => x.Id);
Map(x => x.CreatedDate);
}
}
//first sub class of EntityBase
public class Actuate : EntityBase, IActuate
{
public virtual DateTime? ActivatedOn { get; set; }
}
//Actuate Mapping class
public class ActuateMap : SubclassMap<Actuate>
{
public ActuateMap()
{
Map(x => x.ActivatedOn);
}
}
//Sub class entity
public class Item : Actuate
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual decimal UnitPrice { get; set; }
public virtual ItemStatus Status { get; set; }
public virtual Store Store { get; set; }
}
//Item Mapping class
public class ItemMap : SubclassMap<Item>
{
public ItemMap()
{
Abstract();
Map(x => x.Name);
Map(x => x.Description);
Map(x => x.UnitPrice);
Map(x => x.Status);
References(x => x.Store);
}
}
The entity I have discovered has a problem (other relationship issues might exists)
//Store entity Does not inherit from EntityBase or Actuate
public class Store
{
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual string Name { get; set; }
public virtual IEnumerable<Item> Items { get; set; }
}
//Store mapping class
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id).GeneratedBy.Assigned();
Version(x => x.Version);
Map(x => x.Name);
HasMany(x => x.Items);
}
}
Problem
If I try to run the following query:
//store = is the Store entity I have retrieved from the database and I am trying
//trying to return the items that are associated with the store and are active
store.Items != null && store.Items.Any(item => item.Status == ItemStatus.Active);
I get the following error:
ERROR
Nhibernate.Exceptions.GenericADOException: could not initialize a collection: [SomeDomain.Store.Items#0][SQL: SELECT items0_.StoreId as StoreId1_, items0_.Id as Id1_, items0_.Id as Id10_0_, items0_.CreatedDate as CreatedD2_10_0_, items0_.ActivatedOn as Activate1_11_0_, items0_.Name as Name12_0_, items0_.Description as Descript2_12_0_, items0_.UnitPrice as UnitPrice12_0_, items0_.Status as Status12_0_, items0_.StoreId as StoreId12_0_ FROM [Item] items0_ WHERE items0_.StoreId=?]"}
Inner Exception
"Invalid object name 'Item'."
Now, if I take out the base classes and Item doesn't inherit, and the
Id, Version
columns are part of the Item entity and are mapped in the ItemMap mapping class (with the ItemMap class inheriting from ClassMap<Item> instead, everything works without issue.
NOTE
I have also attempted to add on the StoreMap class unsuccessful.
HasMany(x => x.Items).KeyColumn("Id");
Any thoughts?
if entityBase is just for inheriting common properties then you do not need to map it at all
public class EntityBaseMap<TEntity> : ClassMap<TEntity> where TEntity : EntityBase
{
public EntityBaseMap()
{
Id(x => x.Id);
Version(x => x.Version);
Map(x => x.CreatedDate);
}
}
public class ActuateMap : EntityBaseMap<Actuate> { ... }
Notes:
Versionmapping should map Version property not Id
Version should be readonly in code so nobody accidently alters it
.KeyColumn("Id") is wrong because the column is from the Items table and then it's both the autogenerated id and foreign key. That's not possible nor usefull
usually only classes which are abstract should containt Abstract() in the mapping
I have read this in order to compile NH spatial for Nhibernate 3.1
http://build-failed.blogspot.it/2012/02/nhibernate-spatial-part-2.html
I have also read this
NHibernate.Spatial and Sql 2008 Geography type - How to configure
but the same code for me don't compile... I have this
using NetTopologySuite.Geometries;
namespace Core
{
public class District
{
public virtual int Id { get; set; }
public virtual Polygon Area { get; set; }
public virtual string Name { get; set; }
}
}
using Core;
using FluentNHibernate.Mapping;
namespace TestNHSpatial
{
public class DistrictMap : ClassMap<District>
{
public DistrictMap()
{
ImportType<NetTopologySuite.Geometries.Point>();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.Area).CustomType<Wgs84GeographyType>();
}
}
}
and this
[Serializable]
public class Wgs84GeographyType : MsSql2008GeographyType
{
protected override void SetDefaultSRID(GeoAPI.Geometries.IGeometry geometry)
{
geometry.SRID = 4326;
}
}
finally
var cfg = new OrderingSystemConfiguration();
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connString)
.Dialect<MsSql2008GeographyDialect>())
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<District>(cfg)))
.BuildConfiguration();
var exporter = new SchemaExport(configuration);
exporter.Drop(false, true);
exporter.Create(true, true);
i have this error...
NHibernate.MappingException : An association from the table District refers to an unmapped class: NetTopologySuite.Geometries.Polygon
can anyone help me?
thanks
UPDATE:
The code has some issues, namely:
You're using AutoMappings. You need to use custom mappings
You're using the wrong assembly when searching for the mappings
The export schema code is incorrect.
I'm the author of the blog post that you refer.
Change the type from Polygon (from NetTopologySuite) to IPolygon(from GeoAPI).
Should be something like this:
using GeoAPI.Geometries;
public class District
{
public virtual int Id { get; set; }
public virtual IPolygon Area { get; set; }
public virtual string Name { get; set; }
}
Anyway, if this doesn't work, send me a zip with a test project and I'll check it out.