1
votes

Given a parent child relationship between User and FailedLogin where a user has many failed logins. I'd like to map this into

class User
{
    public virtual FailedLogin LastFailedLogin { get; set; }
}

class FailedLogin
{
    public virtual User User { get; set; }
    public virtual DateTime AttemptOn { get; set; }
}

So that the LastFailedLogin property contains the FailedLogin with the most recent AttemptOn date and time. If LastFailedLogin is set it should save that FailedLogin to the database (i.e. .Cascade.SaveUpdate()) Note, I do not want to map a collection of all FailedLogins for this user for performance reasons.

I have been totally unable to write a fluent mapping for this. How do I map this using Fluent NHibernate?

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        // What goes here for LastFailedLogin?
    }
}

public class FailedLoginMap: ClassMap<FailedLogin>
{
    public FailedLoginMap()
    {
        References(x => x.User).Not.Update();
        Map(x => x.AttemptOn).Not.Update();
    }
}
2

2 Answers

0
votes

i cant see a direct solution but there would be with an internal list:

class User
{
    internal virtual IList<FailedLogin> LastFailedLogins { get; set; }
    public virtual FailedLogin LastFailedLogin { get { LastFailedLogins.FirstOrDefault(); } set { LastFailedLogins.Insert(0, value); } }
}

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        HasMany(u => u.LastFailedLogins)
            .OrderBy("AttemptOn desc");
    }
}
0
votes

The solution I settled on, which I'm not entirely happy with, is:

public class User
{
    private readonly IList failedLogins;
    public virtual FailedLogin LastFailedLogin
    {
        get
        {
            return failedLogins.FirstOrDefault();
        }
        set
        {
            failedLogins.Clear();
            failedLogins.Add(value);
        }
    }
}

public class UserMap : ClassMap<User>
{
    HasMany(Reveal.Member<User, IEnumerable<FailedLogin>>("failedLogins"))
        .Where("FailedLoginID=(SELECT TOP 1 a.FailedLoginID FROM FailedLogin AS a " + 
                "WHERE a.UserID = UserID ORDER BY a.AttemptOn DESC)")
        .Cascade.SaveUpdate().Access.CamelCaseField().Inverse();
}

This solution ensures that only a single FailedLogin is loaded with the User. It also handles correctly saving a new failed login that is associated to the user. Finally it ensures that the implementation is hidden from consumers of the User class. Unfortunately, this still imposes persistence concerns on the User class.