I am implementing a relatively simple model of user management using Castle Active Record with NHibernate on top of MySql, and I have ran into an issue.
Let us say, I have two tables _users and _passwords described by the following SQL create statements
CREATE TABLE _users ( id bigint(20) NOT NULL AUTO_INCREMENT, username char(32) NOT NULL, PRIMARY KEY (id), UNIQUE KEY username_UQ (username), ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE _passwords ( id bigint(20) NOT NULL AUTO_INCREMENT, creation_date datetime NOT NULL, user_id bigint(20) NOT NULL, password_hash char(64) NOT NULL, valid_end_date datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY user_id_password_UQ (user_id,password_hash), KEY user_passwords_FK (user_id), CONSTRAINT user_passwords_FK FOREIGN KEY (user_id) REFERENCES _users (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The idea is to keep a primitive password history, therefore, the passwords are kept in a separate table _passwords, which has many-to-one relation with the _users table.
Now, the following C# code models this structure using Castle Active Records
namespace DataEntities
{
[ActiveRecord("_users")]
public class User : ActiveRecordBase<User>
{
[PrimaryKey(PrimaryKeyType.Identity, "id", Access = PropertyAccess.NosetterLowercase)]
public ulong Id
{
get;
set;
} // Id
[Property("username", ColumnType = "String", NotNull = true, Unique = true)]
[ValidateIsUnique]
public string Username
{
get;
set;
} // Username
[HasMany(typeof(Password))]
public IList<Password> Passwords
{
get;
set;
} // Passwords
public string ValidPasswordHash
{
get
{
DateTime l_dtNow = DateTime.Now;
if (Passwords.Count != 1 || Passwords[0].ValidFrom >= l_dtNow || Passwords[0].ValidUntil <= l_dtNow)
{
throw new Exception();
}
return Encoding.UTF8.GetString(Passwords[0].PasswordHash);
}
} // ValidPasswordHash
public static User FindByCredentials(string i_sUsername, string i_sHashedPassword)
{
return FindOne(Restrictions.Eq("Username", i_sUsername), Restrictions.Eq("ValidPasswordHash", i_sHashedPassword));
} // FindByCredentials
} // User
[ActiveRecord("_passwords")]
public class Password : ActiveRecordBase<Password>
{
[PrimaryKey(PrimaryKeyType.Identity, "id", Access = PropertyAccess.NosetterLowercase)]
public ulong Id
{
get;
set;
} // Id
[BelongsTo("user_id", NotNull = true, UniqueKey = "_passwords_UQ1")]
public ulong UserId
{
get;
set;
} // UserId
[Property("password_hash", ColumnType = "UInt64", NotNull = true, UniqueKey = "_passwords_UQ1")]
public byte[] PasswordHash
{
get;
set;
} // PasswordHash
[Property("creation_date", ColumnType = "DateTime", NotNull = true)]
public DateTime ValidFrom
{
get;
set;
} // ValidFrom
[Property("valid_end_date", ColumnType = "DateTime", NotNull = true)]
public DateTime ValidUntil
{
get;
set;
} // ValidUntil
} // Password
} // DataEntities
and on my application start the framework is initialized
try
{
ActiveRecordStarter.Initialize(ActiveRecordSectionHandler.Instance, typeof(Password),
typeof(User));
}
catch (Exception l_excpt)
{
// handle exception
}
At the end, when this code runs, it generates the following exception:
Castle.ActiveRecord.Framework.ActiveRecordException: ActiveRecord tried to infer details about the relation User.Passwords but it could not find a 'BelongsTo' mapped property in the target type DataEntities.Password at Castle.ActiveRecord.Framework.Internal.SemanticVerifierVisitor.VisitHasMany(HasManyModel model) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\SemanticVerifierVisitor.cs:line 544 at Castle.ActiveRecord.Framework.Internal.AbstractDepthFirstVisitor.VisitNodes(IEnumerable nodes) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\AbstractDepthFirstVisitor.cs:line 45 at Castle.ActiveRecord.Framework.Internal.AbstractDepthFirstVisitor.VisitModel(ActiveRecordModel model) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\AbstractDepthFirstVisitor.cs:line 59 at Castle.ActiveRecord.Framework.Internal.SemanticVerifierVisitor.VisitModel(ActiveRecordModel model) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\SemanticVerifierVisitor.cs:line 122 at Castle.ActiveRecord.Framework.Internal.AbstractDepthFirstVisitor.VisitNodes(IEnumerable nodes) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\Internal\Visitors\AbstractDepthFirstVisitor.cs:line 45 at Castle.ActiveRecord.ActiveRecordStarter.RegisterTypes(ISessionFactoryHolder holder, IConfigurationSource source, IEnumerable`1 types, Boolean ignoreProblematicTypes) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\ActiveRecordStarter.cs:line 927 at Castle.ActiveRecord.ActiveRecordStarter.Initialize(IConfigurationSource source, Type[] types) in c:\daten\dev\External\Castle\AR2.0\ActiveRecord\Castle.ActiveRecord\Framework\ActiveRecordStarter.cs:line 202 at Global.Application_Start(Object sender, EventArgs e) in C:\Projects Code\xyz\Global.asax.cs:line 22
Well, I have stared endlessly at the UserId
property of the Password
class, I have googled, and now I am quite lost. So the community is my last hope... Can anybody help me understanding what causes this exception and how to fix it?
Thank you all in advance for your replies and comments.
You should have a User User { get; set; }
reference property instead of a foreign key.
The official docs are a good place to start.