NHibernate.QueryException: could not resolve property - nhibernate

I'm using FluentNHibernate and Linq To Nhibernate, with these entities (only the relevant parts):
public class Player : BaseEntity<Player>
{
private readonly IList<PlayerInTeam> allTeams = new List<PlayerInTeam>();
public IEnumerable<Team> Teams
{
get
{
return from playerInTeam in allTeams
where playerInTeam.Roster.Match == null
select playerInTeam.Roster.Team;
}
}
}
public class PlayerInTeam : BaseEntity<PlayerInTeam>
{
public int PlayerNumber { get; set; }
public Player Player { get; set; }
public Position Position { get; set; }
public Roster Roster { get; set; }
}
public class Roster : BaseEntity<Roster>
{
private readonly IList<PlayerInTeam> players = new List<PlayerInTeam>();
public Team Team { get; set; }
public IEnumerable<PlayerInTeam> Players { get { return players; } }
}
public class Team : BaseEntity<Team>
{
private readonly IList<Roster> allRosters = new List<Roster>();
public Team(string name, Sex sex)
{
allRosters.Add(new Roster(this));
}
public Roster DefaultRoster
{
get { return allRosters.Where(r => r.Match == null).First(); }
}
}
and the matching mappings:
public class PlayerMap : ClassMap<Player>
{
public PlayerMap()
{
HasMany<PlayerInTeam>(Reveal.Member<Player>("allTeams"))
.Inverse()
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
public class PlayerInTeamMap : ClassMap<PlayerInTeam>
{
public PlayerInTeamMap()
{
References(pit => pit.Player)
.Not.Nullable();
References(pit => pit.Roster)
.Not.Nullable();
}
}
public class RosterMap : ClassMap<Roster>
{
public RosterMap()
{
References(tr => tr.Team)
.Not.Nullable();
HasMany<PlayerInTeam>(Reveal.Member<Roster>("players"))
.Inverse()
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
HasMany<Roster>(Reveal.Member<Team>("allRosters"))
.Inverse()
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
I have this repository method:
public IEnumerable<Player> PlayersNotInTeam(Team team)
{
return from player in Session.Linq<Player>()
where !player.Teams.Contains(team)
select player;
}
Which gives me this exception: NHibernate.QueryException: could not resolve property: Teams of: Emidee.CommonEntities.Player [.Where(NHibernate.Linq.NhQueryable`1[Emidee.CommonEntities.Player], Quote((player, ) => (Not(.Contains(player.Teams, p1, )))), )]
I've looked inside the hbm file, generated by Fluent NHibernate, concerning Player, and here is what I get:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Emidee.CommonEntities.Player, Emidee.CommonEntities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=65c7ad487c784bec" table="Players">
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Id" />
<generator class="increment" />
</id>
<bag access="field.camelcase" cascade="all-delete-orphan" inverse="true" name="allTeams" mutable="true">
<key>
<column name="Player_id" not-null="true" />
</key>
<one-to-many class="Emidee.CommonEntities.PlayerInTeam, Emidee.CommonEntities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=65c7ad487c784bec" />
</bag>
</class>
</hibernate-mapping>
Indeed, I don't have a Teams property for Player in that file.
But shouldn't my mapping take care of this?
Where do you think the problem is?
Thanks in advance
Mike

Technically, it's not possible to look inside a method body in C-sharp. So FluentNhibernate can not do that as well. You have to apply a different strategy to model your entities and/or generate the mapping yourself.

The hbm.xml file is your mapping. So, as long as you do not inform NHibernate that your Player class has a Teams property, NHibernate doesn't know anything about it.
Next to that, why do you have an hbm.xml mapping file, and a mapping using Fluent ?
(It seems that the hbm.xml file has priority/precedes the Fluent NH mapping).

Related

Many-To-Many relationships with two natural keys

I have the following mapping
UserProfile.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="AngusBook.Domain"
namespace="AngusBook.Domain">
<class name="UserProfile">
<id name="UserId" type="int">
<generator class="identity" />
</id>
<natural-id mutable="false">
<property name="UserName" />
</natural-id>
<set name="Companies" table="Users_Companies">
<key column="UserId"/>
<many-to-many column="CompanyId" class="Company" />
</set>
</class>
</hibernate-mapping>
Company.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AngusBook.Domain" namespace="AngusBook.Domain" >
<class name="Company">
<id name="Id">
<generator class="hilo" />
</id>
<natural-id mutable="false">
<property name="CompanyName" />
</natural-id>
<set name="Users" table="Users_Companies">
<key column="CompanyId"/>
<many-to-many column="UserId" class="UserProfile" />
</set>
</class>
</hibernate-mapping>
Table design
With this mapping I have, I can have two identical rows in Users_Companies table (ie: two rows with the same pair of foreign keys that belong to the UserProfile and Company table). Using mappings, how do I cause NHibernate or SQL to throw an error/exception when an attempt is made to insert a pair of foreign keys into Users_Companies that already exist in the table? I would like each row in Users_Companies to be unique and not have repetitive data.
I found out the problem. I didn't implement the Equals() and GetHashCode() correctly.
I ended up refactored my entities to use the following base entity class and that fixed the issue. No error is thrown when a duplicated entity is added to the set but the set will simply not add the duplicated entity.
Entity.cs
public abstract class Entity
{
public virtual TId Id { get; protected set; }
protected virtual int Version { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
private static bool IsTransient(Entity<TId> obj)
{
return obj != null &&
Equals(obj.Id, default(TId));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(Entity<TId> other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) &&
!IsTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(TId)))
return base.GetHashCode();
return Id.GetHashCode();
}
}
public abstract class Entity : Entity<Guid>
{
}
UserProfile.cs
public class UserProfile : Entity<int>
{
public UserProfile()
{
Companies = new HashedSet<Company>();
}
public virtual string UserName { set; get; }
public virtual ISet<Company> Companies { set; get; }
}
Company.cs
public class Company : Entity<int>
{
public Company()
{
Users = new HashedSet<UserProfile>();
}
public Company(string name) :this()
{
this.CompanyName = name;
}
public virtual string CompanyName { set; get; }
public virtual ISet<UserProfile> Users { set; get; }
}

Fluent NHibernate - mixing table-per-subclass and table-per-class-hierarchy

Give the following structure,
MyBaseClass {
public int Id {get; private set;}
}
MySubclassWithDiscriminator : MyBaseClass {
}
MySubclass : MyBaseClass {
public string SomeThing {get; set;}
}
How would I use Fluent NH to map these correctly, using a combination of table-per-subclass and table-per-class-hierarchy? I've tried a custom AutomappingConfiguration, but seem to be going around in circles:
public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type.Namespace.Contains("Entities");
}
public override bool IsDiscriminated(Type type)
{
// only classes with additional properties should be
// using the table-per-subclass strategy
if ((type.IsAssignableFrom(typeof(MyBaseClass)) ||
type.IsSubclassOf(typeof(MyBaseClass)) &&
type.GetProperties(BindingFlags.Public |
BindingFlags.FlattenHierarchy)
.Count() <= 1))
{
return true;
}
return false;
}
}
public class SubclassConvention : ISubclassConvention
{
public void Apply(ISubclassInstance instance)
{
// Use the short name of the type, not the full name
instance.DiscriminatorValue(instance.EntityType.Name);
}
}
It seems to me from my investigation that the use of the Discriminator is a binary choice when using FNH, while an HBM has the ability to have a Discriminator column and a subclass at the same time.
EDIT - 2011-05-12
I rewrote this post to try to address James Gregory's comments.
Here's the HBM that I'm trying to achieve:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
xmlns="urn:nhibernate-mapping-2.2"
name="Mixed_Parent"
abstract="true"
table="`Mixed_Parent`">
<id name="Id" type="System.Int32">
<generator class="identity" />
</id>
<discriminator type="String">
<column name="discriminator" />
</discriminator>
<subclass
name="Mixed_TPCH_Child"
discriminator-value="Mixed_TPCH_Child" />
<subclass
name="Mixed_TPS_Child"
discriminator-value="Mixed_TPS_Child">
<join table="`Mixed_TPS_Child`" >
<key column="Id" />
<property name="Description" type="String">
<column name="Description" />
</property>
</join>
</subclass>
</class>
</hibernate-mapping>
So, what I've seen is that the HBM that's generated is EITHER a <joined-subclass> or <subclass> without the <join> sub-element, never a combination of the two. Am I missing something here?
Here's a failing test that can be added to the SubclassPersistenceModelTests to illustrate:
namespace MixedTablePerSubclassWithTablePerClassHierarchy
{
public class Mixed_Parent
{
public virtual int Id { get; set; }
}
public class Mixed_TPCH_Child
{
}
public class Mixed_TPS_Child
{
public virtual string Description { get; set; }
}
public class Mixed_ParentMap : ClassMap<Mixed_Parent>
{
public Mixed_ParentMap()
{
Id(x => x.Id);
DiscriminateSubClassesOnColumn("discriminator");
}
}
public class Mixed_TPCH_ChildMap : SubclassMap<Mixed_TPCH_Child>
{ }
public class Mixed_TPS_ChildMap : SubclassMap<Mixed_TPS_Child>
{
public Mixed_TPS_ChildMap()
{
Map(x => x.Description);
}
}
}
[Test]
public void ShouldAllowMixedTablePerSubclassWithTablePerClassHierarchy()
{
var model = new PersistenceModel();
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_ParentMap());
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPCH_ChildMap()
);
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPS_ChildMap());
var classMapping = model.BuildMappings()
.First()
.Classes.First();
// WHAT SHOULD THIS NUMBER BE (0, 1 or 2)?
classMapping.Subclasses.Count().ShouldEqual(1);
classMapping
.Subclasses
.First()
.Type
.ShouldEqual(
typeof(
MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPS_Child)
); // WHICH OF THE CHILDREN WOULD BE FIRST?
}

Nhibernate mapping

I am trying to map Users to each other. The senario is that users can have buddies, so it links to itself
I was thinking of this
public class User
{
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string Password { get; set; }
public virtual DateTime? DateCreated { get; set; }
**public virtual IList<User> Friends { get; set; }**
public virtual bool Deleted { get; set; }
}
But am strugling to do the xml mapping.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyVerse.Domain"
namespace="MyVerse.Domain" >
<class name="User" table="[User]">
<id name="Id">
<generator class="guid" />
</id>
<property name="FirstName" />
<property name="LastName" />
<property name="EmailAddress" />
<property name="Password" />
<property name="DateCreated" />
<property name="Deleted" />
<set name="Friends" table="UserFriend">
<key foreign-key="Id"></key>
<many-to-many class="User"></many-to-many>
</set>
</class>
</hibernate-mapping>
something like
<bag name="Friends" table="assoc_user_table" inverse="true" lazy="true" cascade="all">
<key column="friend_id" />
<many-to-many class="User,user_table" column="user_id" />
</bag>
Consider using the repository pattern. Create a Repository contract and a base abstract class that takes one of your entities as a type (your mapped class)
Open the session when the repository is initialized and close when destroyed. (implement IDisposable).
Then make sure all of your access to the session happens within the using statement:
[pseudo-code]:
using(var repository = RepositoryFactory<EntityType>.CreateRepository())
{
var entity = repository.get(EntityID);
foreach (somesubclass in entity.subclasscollection)
{
//Lazy loading can happen here, session is still open with the repository
... Do Something
}
}
I use a base abstract class for my Repositories. This one is for my readonly repository but you'll get the drift. They key is to keep your units of work small, open the session only when you have something to do with the database, then let it close on the dispose. Here's the base class, disclaimer YMMV:
public interface IEntity
{
int Id { get; set; }
}
public interface IRORepository<TEntity> : IDisposable where TEntity : IEntity
{
List<TEntity> GetAll();
TEntity Get(int id);
}
public abstract class RORepositoryBase<T> : IRORepository<T> where T : IEntity
{
protected ISession NHibernateSession;
protected RORepositoryBase()
{
NHibernateSession = HibernateFactory.OpenSession();
NHibernateSession.DefaultReadOnly = true;
}
public ISession Session { get { return NHibernateSession; } }
public void Dispose()
{
NHibernateSession.Flush();
NHibernateSession.Close();
NHibernateSession.Dispose();
}
public virtual List<T> GetAll()
{
return NHibernateSession.Query<T>().ToList();
}
public virtual T Get(int id)
{
return NHibernateSession.Get<T>(id);
}
}

FluentNHibernate: can't change key column name for a one-to-many relationship

I have an entity:
public class Message:Entity
{
public virtual IList`<`Message> ReplayMessages { set; get; }
public virtual Message ParentMessage { set; get; }
}
I try to override the mapping:
mapping.HasMany(x => x.ReplayMessages)
.AsSet()
.KeyColumnNames.Add("ParentId");
but in the hbm.xml file I get:
<set name="ReplayMessages">
<key column="MessageFk" />
<one-to-many class="Edi.Core.Model.Message, Edi.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</set>
Why the column key name didn't change?
When I use HasMany it looks like this:
HasMany(x => x.OrderItems)
.KeyColumn("OrderId")
.AsSet()
.Cascade.All();
I don't know what KeyColumnNames is used for...

How can I use a composite-id with a class as the id field in fluent nhibernate?

I've got a mapping file like this:
<class name="Resource" table="resource" discriminator-value="null">
<composite-id name="Key" class="Models.Bases.ClientKey, Models">
<key-property name="Id" column="ID" type="int"/>
<key-property name="SiteId" column="clientID" type="int"/>
</composite-id>
<property name="Name" type="String">
<column name="`name`" length="500" sql-type="varchar" not-null="false"/>
</property>
</class>
which works just fine and here's the id class:
public class ClientKey
{
public int Id { get; set; }
public int ClientId { get; set; }
}
public class Resource
{
public virtual ClientKey Key { get; set; }
public virtual string Name { get; set; }
}
How can I remap this using FluentNhibernate? This code doesn't work:
WithTable("resource");
UseCompositeId()
.WithKeyProperty(x => x.Key.Id, "ID")
.WithKeyProperty(x => x.Key.ClientId, "clientID");
Map(x => x.Name);
It throws this error:
Could not find a getter for property 'Id' in class 'Models.Resource'
Thanks!!!
I'm afraid you can't fix it without modifying Resource class. I have checked with Fluent NHibernate's source - here's what code that outputs composite-id part looks like:
XmlElement element = classElement.AddElement("composite-id");
foreach( var keyProp in keyProperties )
{
keyProp.Write(element, visitor);
}
What is missing is "name" attribute, which should be set to "Key". Without this attibute, NHibernate fallbacks to default property name = "Id". As your class doesn't have Id property, NHibernate doesn't know what to do and throws an exception.
If you can't modify Resource class, you would have to use hbm mapping for this class or create a patch to fluent nhibernate (it is possible that this is known issue and someone's working on it - refer to fluent nhibernate's issue tracker).