Mapping Dictionary with entity based key using Fluent Nhibernate - nhibernate

I have the following NHibernate HBM that works as expected, but it makes my code brittle as the classes may change and I want to do the mapping in FNH, but try as I might, I cannot seem to get it to work, especially how you get the index-many-to-many:
<map name="Permissions" table="PermissionsBySet" cascade="all">
<key column="PermissionSet_id" />
<index-many-to-many class="Picomole.ReadModel.Permission, Picomole.ReadModel" column="PermissionId" />
<element column="PermissionType" type="Picomole.ReadModel.PermissionType, Picomole.ReadModel" not-null="true" />
</map>
Given the following classes:
public class PermissionSet : DomainObject
{
public virtual PermissionSet Defaults { get; set; }
public virtual IDictionary<Permission, PermissionType> Permissions { get; set; }
}
public class Permission : DomainObject
{
public virtual string Controller { get; set; }
public virtual string Action { get; set; }
}
public enum PermissionType
{
None,
Read,
Write,
Grant
}

public class PermissionSetMap : ClassMap<PermissionSet>
{
public PermissionSetMap()
{
HasManyToMany(ps => ps.Permissions)
.AsEntityMap("permissions_id")
.Element("PermissionType");
}
}

Thanks Firo for point me in the right direction. The final answer for what I was attempting to do was:
HasManyToMany(x => x.Permissions)
.AsEntityMap("PermissionId", "PermissionLevel")
.Element("PermissionLevel", x => x.Type<PermissionLevel>())
.Table("PermissionsBySet");
And because of some issues I was having with a strange error about the session connection being closed, I had to add:
.Fetch.Join()
.Not.LazyLoad()

Related

NHibernate: discriminator without common base class?

Is it possible to map two classes to the same property without them sharing a common base class?
For example, a situation like this:
class Rule
{
public virtual int SequenceNumber { get; set; }
public virtual ICondition Condition { get; set; }
}
interface ICondition
{
}
class ExpressionCondition : ICondition
{
public virtual string Expression { get; set; }
}
class ThresholdCondition : ICondition
{
public virtual int Threshold { get; set; }
}
I also cannot add some empty abstract class that both conditions inherit from because the two ICondition implementations exist in different domains that are not allowed to reference each other. (Please no responses telling me that this situation should not occur in the first place - I'm aware of it and it doesn't help me.)
using FluentNHibernate
ReferencesAny(x => x.Condition)
.EntityIdentifierColumn("cond_Id")
.EntityTypeColumn("condType")
.IdentityType<int>()
.AddMetaValue<ExpressionCondition>("express")
.AddMetaValue<ThresholdCondition >("threshold");
using xml
<any id-type="System.Int32" meta-type="System.String" name="Condition">
<meta-value value="express" class="Namespace.ExpressionCondition" />
<meta-value value="threshold" class="Namespace.ThresholdCondition" />
<column name="condType" />
<column name="cond_Id" />
</any>

When using DTOs, Automapper & Nhibernate reflecting changes in child collections of DTO in domain object being updated

I'm not massively familiar with this design but I am hoping to get some guidance.
I have a backend service that sends out DTOs to a WPF smart client. On the WPF smart client the user will change,delete and modify items and then the changes are sent back (client --> server). As an example, currently I am working on the Customer details form and the user has the ability to add,remove and change categories belonging to a customer in a datagrid. When the DTO is sent back to the server I would like to load in the domain object that is related to the ID in the DTO and apply the changes made on the DTO to the domain object, including all the child collections.
I have made an attempt at doing something similar to this in the code below with the UpdateCustomer method. However, I think I am way off the mark. When the code runs instead of ending up with a list of {Individual,Company,NGO,Government} I end up with a list of {Individual,B2B,Company,NGO,Government} as it has clearly not deleted the B2B entry from the original list.
One option that has occurred to me is to loop through the DTO collection and compare it to the collection from the domain object and add, remove and update dependent on what has been modified. However, this seemed really cumbersome.
What do I need to do to apply the changes from the DTO to the child collections in my domiain object?
Thank you very much for any assistance it will be thoroughly appreciated
Alex
public class Customer
{
public virtual int Id { get; set; }
public virtual IList<Category> Categories { get; private set; }
public virtual string Code { get; set; }
public virtual string Description { get; set; }
public Customer()
{
Categories = new List<Category>();
}
public virtual void AddCategory(string categoryName)
{
Categories.Add(new Category(categoryName));
}
}
public class Category
{
public virtual string CategoryName { get; private set; }
public virtual Customer Customer {get;set;}
public virtual int Id { get; set; }
protected Category(){}
public Category(string name)
{
CategoryName = name;
}
}
}
public void SetUpAutoMapper()
{
Mapper.CreateMap<Category, CategoryDto>();
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CategoryDto, Category>();
Mapper.CreateMap<CustomerDto, Customer>();
Mapper.AssertConfigurationIsValid();
}
public void SaveCustomer()
{
var customer = new Customer{Code="TESTCUST",Description="TEST CUSTOMER"};
customer.AddCategory("Individual");
customer.AddCategory("B2B");
customer.AddCategory("Healthcare");
customer.AddCategory("NGO");
repository.Save(customer);
}
public CustomerDto GetCustomer(int customerId)
{
var customer = repository.GetCustomer(customerId);
var customerDto = Mapper.Map<Customer,CustomerDto>(customer);
return customerDto;
}
public void UpateCustomer(CustomerDto customerToUpdate)
{
/*imagine that the dto incoming has had the following operations performed on it
-----add new category----
customerToUpdate.Categories.Add(new CategoryDto {CategoryName = "Government"});
---update existing category---
customerToUpdate.Categories[2].CategoryName = "Company";
---remove category---
customerToUpdate.Categories.RemoveAt(1);*/
var customer = repository.GetCustomer(customerToUpdate.Id);
/* How in this bit do I ensure that the child collection changes are
propogated into the underlying customer object retrieved from the database*/
var customer = Mapper.Map<CustomerDto,Customer>(customerToUpdate);
repository.Save(customer);
}
public class CustomerDto
{
public int Id { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public List<CategoryDto> Categories { get; set; }
}
public class CategoryDto
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Customer" table="Customer">
<id name="Id" column="CustomerId">
<generator class="native"/>
</id>
<property name="Code" />
<property name="Description" />
<bag name="Categories" table="Categories" cascade="all" inverse="false">
<key column="FK_CustomerID" />
<one-to-many class="Category"/>
</bag>
</class>
<class name="Category" table="Categories">
<id name="Id" column="CategoryId">
<generator class="native"/>
</id>
<many-to-one name="Customer" column="FK_CustomerId" not-null="true" class="Customer"></many-to-one>
<property name="CategoryName" />
</class>
</hibernate-mapping>
I recently did something similar but with EF as the datatier. I don't know nhibernate to know if the same approach would work.
Basic steps were
Ensure the destination collection is loaded from db and attached to the object graph for change tracking
.ForMember(dest => dest.Categories, opt => opt.UseDestinationValue())
Then create a custom IObjectMapper for mapping IList<> to IList<T> where T : Entity
The custom IObject mapper used some code from http://groups.google.com/group/automapper-users/browse_thread/thread/8c7896fbc3f72514
foreach (var child in source.ChildCollection)
{
var targetChild = target.ChildCollection.SingleOrDefault(c => c.Equals(child)); // overwrite Equals or replace comparison with an Id comparison
if (targetChild == null)
{
target.ChildCollection.Add(Mapper.Map<SourceChildType, TargetChildType>(child));
}
else
{
Mapper.Map(child, targetChild);
}
}
Finally one last piece of logic to check all Id's in targetCollection exist in sourceCollection and delete them if they don't.
It wasn't all that much code in the end and is reusable in other actions.
Mapper.CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.Categories, opt => opt.MapFrom(src =>src.Categories));
or
Mapper.CreateMap<IList<Category>, IList<CategoryDto>>();
something like this to tell automapper to map the list, too.

NHibernate Automapping problem

Recently I came across a strange behavior in Automapping of Fluent NHibernate. I have the following class structure (some properties cut off for the sake of brewity).
public class UserGroup
{
public virtual UserGroup ParentGroup { get; set; }
public virtual UserGroupMember Manager { get; protected set; }
public virtual ISet<UserGroupMember> Members { get; protected set; }
}
and
public class UserGroupMember : BaseEntity
{
public virtual User User { get; set; }
public virtual UserGroup Group { get; set; }
}
The mapping for UserGroup:
public class UserGroupMap : IAutoMappingOverride<UserGroup>
{
public void Override(AutoMapping<UserGroup> mapping)
{
mapping.HasMany(el => el.Members)
.Cascade
.AllDeleteOrphan().Inverse().LazyLoad();
}
}
The automapping creates two column (both of which are foreign keys) in the UserGroupMember table to reflect the relation between UserGroup and UserGroupMembers. I've found out that the generated mapping contains wrong column (as seen below):
<set cascade="all-delete-orphan" inverse="true" lazy="true" name="Members" mutable="true">
<key>
<column name="Parent_Id" />
</key>
<one-to-many class="Groups.Data.UserGroupMember, Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</set>
which results in wrong queries:
While insert in UserGroupMember - Group_Id is used (which is right), not using Parent_Id
While select in UserGroupMember - Parent_Id is used
Group_Id is the column in UserGroupMember mapping file which reflects the Group property in UserGroupMember.
I tried to modify the mapping adding .KeyColumn("Group_Id") and it is solves the problem. But is there any way to make Fluent NHibernate 'think the right way'?
This is from memory, as I don't have test code ready.
When using bidirectional many-to-many, you sometimes have to help FHN figure columns names, if they're not "alike" on both sides.
For example this should map correcly
public class User
{
public IList<Group> Groups { get; set; }
}
public class Group
{
public IList<User> Users { get; set; }
}
While this would not
public class User
{
public IList<Group> BelongsTo { get; set; }
}
public class Group
{
public IList<User> Contains { get; set; }
}
As a rule of thumb, if automapping (with or without conventions) doesn't generate right columns names, especially for non trivial cases, do not hesitate to put an override to set those column names manually.

NHibernate.Linq System.Nullable throws ArgumentException, the value "" is not type

I have a class of type MetadataRecord:
public class MetadataRecord {
public virtual long? IntegerObject { get; set; }
public virtual string ClassName { get; set; }
public virtual DateTime? DateObject { get; set; }
public virtual double? DecimalObject { get; set; }
public virtual long MetadataId { get; set; }
public virtual long MetadataLabelId { get; set; }
public virtual long ObjectId { get; set; }
public virtual string StringObject { get; set; }
public virtual Asset Asset { get; set; }
}
and a matching mapping file as follows:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ActiveMediaDataAccess"
namespace="ActiveMediaDataAccess.Entities">
<class name="MetadataRecord" table="WM_META_DATA" lazy="true">
<id name="MetadataId" column="META_DATA_ID">
<generator class="seqhilo" />
</id>
<property name="MetadataLabelId" column="META_DATA_LABEL_ID" />
<property name="ObjectId" column="OBJECT_ID" />
<property name="ClassName" column="CLASS_NAME" />
<property name="IntegerObject" column="INTEGER_OBJECT" />
<property name="DecimalObject" column="DECIMAL_OBJECT" />
<property name="DateObject" column="DATE_OBJECT" />
<property name="StringObject" column="STRING_OBJECT" />
<many-to-one name="Asset" column="OBJECT_ID" not-null="true" />
</class>
</hibernate-mapping>
I'm running a unit test against this class to check for values returned for IntegerObject which is a nullable type of long, from an instance of MetadataRecord. I'm using NHibernate.Linq (v 1.1.0.1001) to query as follows:
[TestMethod()]
public void IntegerObjectTest() {
var integerObject = _sessionFactory.OpenSession().Linq<MetadataRecord>()
.Where(m => m.ObjectId == 65675L)
.Select(m => m.IntegerObject)
.FirstOrDefault();
Assert.IsNull(integerObject);
}
The INTEGER_OBJECT column from the corresponding table is nullable, and I expect IsNull to be true or false. However, I get the following error:
Test method ActiveMediaMetadataViewerTestProject.MetadataRecordTest.IntegerObjectTest threw exception: NHibernate.Exceptions.GenericADOException: Unable to perform find[SQL: SQL not available] ---> System.ArgumentException: The value "" is not of type "System.Nullable`1[System.Int64]" and cannot be used in this generic collection.
Parameter name: value.
I can't figure out why it's trying to cast a string to a nullable type. Is there another way in which I should be opening the session, decorating the class, even constructing the mapping file, ..... where am I going wrong here? I could resort to using Criteria, but I was much enjoying the intellisense and "refactorability" with Linq.
Better solution (translated to SQL in whole):
[TestMethod()]
public void IntegerObjectTest() {
var integerObject = _sessionFactory.OpenSession().Linq<MetadataRecord>()
.Where(m => m.ObjectId == 65675L)
.Select(m => new long?(m.IntegerObject))
.FirstOrDefault();
Assert.IsNull(integerObject);
}
My workaround:
[TestMethod()]
public void IntegerObjectTest() {
var integerObject = _sessionFactory.OpenSession().Linq<MetadataRecord>()
.Where(m => m.ObjectId == 65675L)
.Select(m => m.IntegerObject)
.AsEnumerable()
.FirstOrDefault();
Assert.IsNull(integerObject);
}
For some reason, NHibernate.Linq does not like calling First(), FirstOrDefault() (and I'm guessing Single() and SingleOrDefault()) on nullable types, and throws the above error if the field is null. It works fine if the nullable type actually has a value. If I push the results into an in-memory collection via AsEnumerable(), ToArray(), ToList(), etc, then it plays nice and returns my nullable type.

FluentNHibernate - AutoMappings producing incorrect one-to-many column key

I'm new to NHibernate and FNH and am trying to map these simple classes by using FluentNHibernate AutoMappings feature:
public class TVShow : Entity
{
public virtual string Title { get; set;}
public virtual ICollection<Season> Seasons { get; protected set; }
public TVShow()
{
Seasons = new HashedSet<Season>();
}
public virtual void AddSeason(Season season)
{
season.TVShow = this;
Seasons.Add(season);
}
public virtual void RemoveSeason(Season season)
{
if (!Seasons.Contains(season))
{
throw new InvalidOperationException("This TV Show does not contain the given season");
}
season.TVShow = null;
Seasons.Remove(season);
}
}
public class Season : Entity
{
public virtual TVShow TVShow { get; set; }
public virtual int Number { get; set; }
public virtual IList<Episode> Episodes { get; set; }
public Season()
{
Episodes = new List<Episode>();
}
public virtual void AddEpisode(Episode episode)
{
episode.Season = this;
Episodes.Add(episode);
}
public virtual void RemoveEpisode(Episode episode)
{
if (!Episodes.Contains(episode))
{
throw new InvalidOperationException("Episode not found on this season");
}
episode.Season = null;
Episodes.Remove(episode);
}
}
I'm also using a couple of conventions:
public class MyForeignKeyConvention : IReferenceConvention
{
#region IConvention<IManyToOneInspector,IManyToOneInstance> Members
public void Apply(FluentNHibernate.Conventions.Instances.IManyToOneInstance instance)
{
instance.Column("fk_" + instance.Property.Name);
}
#endregion
}
The problem is that FNH is generating the section below for the Seasons property mapping:
<bag name="Seasons">
<key>
<column name="TVShow_Id" />
</key>
<one-to-many class="TVShowsManager.Domain.Season, TVShowsManager.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
The column name above should be fk_TVShow rather than TVShow_Id. If amend the hbm files produced by FNH then the code works.
Does anyone know what it's wrong?
Thanks in advance.
Have you stepped through the auto map in the debugger to make sure your convention is being called?
Assuming you have it wired up correctly you may need to implement the Accept interface for ReferenceConvention