0

I am having some trouble with what seems to be a relatively simple cascading operation, but which I simply cannot get to work. I have four (simplified) classes:

class Class
{
    uint?  ID; //IDs are null when they are created, the database automatically generates IDs. IDs are never null when retrieved from Database.
    Classroom Classroom;
    List<ClassStudent> Students;
}
class Student
{
    uint? ID; //IDs are null when they are created, the database automatically generates IDs. IDs are never null when retrieved from Database.
    string Name;
    Classroom Homeroom;
}
class Classroom
{
    uint? ID; //IDs are null when they are created, the database automatically generates IDs. IDs are never null when retrieved from Database.
    string FloorNumber;
    string RoomNumber;
}
class ClassStudent
{
    Class Class;
    uint SeatNumber;
    Student Student;
}

When I save a Class, I want to be able to just type in personal and address information without worrying about whether these Students and Classrooms already exist in the database, and I want the Cascade logic to automatically persist new Classrooms and Students, and reference existing ones based not on their IDs, but on their information. For example, saving a Classroom with FloorNumber: "69", RoomNumber: 420 while an identical Classroom exists in the database should result in a reference to the Classroom.ID instead of persisting a new Classroom. Student works the exact same way.

(Oh, and, of course this is a silly way to handle School information; the classes are stand-ins. I just find it easier to work with tangible examples rather than foos and bars)

However, I cannot get the Cascade to work properly.

The first thing I have tried is of course just to have my ClassMaps' references Cascade.SaveUpdate(), for example:

class ClassesMap: ClassMap<Class>
{
    public ClassesMap()
    {
        Id(x => x.ID)
            .GeneratedBy.Native();
        References(x => x.Classroom)
            .Cascade.SaveUpdate();
        HasMany(x => x.ClassStudent)
            .KeyColumn("ClassID")
            .Cascade.All();
            
    }
}
class ClassStudentMap: ClassMap<ClassStudent>
{
    public ClassStudentMap()
    {
        CompositeId()
            .KeyReference(x => x.Class, "ClassID")
            .KeyProperty(x => x.SeatNumber)
        References(x => x.Student)
            .Cascade.SaveUpdate();
    }
}
class StudentMap: ClassMap<Student>
{
    public StudentMap()
    {
        Id(x => x.ID);
            .GeneratedBy.Native();
        Map(x => x.Name);
        References(x => x.Homeroom)
            .Cascade.SaveUpdate();
    }
}
class ClassroomMap: ClassMap<Classroom>
{
    public ClassroomMap()
    {
        Id(x => x.ID);
            .GeneratedBy.Native();
        Map(x => x.FloorNumber);
        Map(x => x.RoomNumber);
    }
}

The result of this is that there is no prevention of duplicate Classrooms and Students; inserting a new Class will also insert its Classroom and Students, regardless of whether identical Classrooms and Students already exist in the database.

So, I tried to declare the Students' and Classrooms' information as Unique

class StudentMap: ClassMap<Student>
{
    public StudentMap()
    {
        Id(x => x.ID);
            .GeneratedBy.Native();
        Map(x => x.Name).UniqueKey("UniqueStudent");
        References(x => x.Homeroom)
            .Cascade.SaveUpdate()
            .UniqueKey("UniqueStudent");
    }
}
class ClassroomMap: ClassMap<Classroom>
{
    public ClassroomMap()
    {
        Id(x => x.ID);
            .GeneratedBy.Native();
        Map(x => x.FloorNumber)
            .UniqueKey("UniqueClassroom");
        Map(x => x.RoomNumber)
            .UniqueKey("UniqueClassroom");
    }
}

However, this resulted in Classes being unable to save to the database, because it could not save its Students and Classroom because they conflict with existing Unique entries.

Third thing I tried was a custom Cascade Logic. I replaced the Cascade.SaveUpdate()s with Cascade.None(), and rewrote my code so I would GetOrCreate each Classroom, and then GetOrCreate each Student, and only then save the Class Example of GetOrCreate as applies to Classroom (Student has one just like it):

Classroom GetOrCreate(Classroom newClassroom)
{
    Classroom? extant = GetFromDatabase(newClassroom);
    if(extant != null)
        return extant;
    SaveToDatabase(newClassroom);
    return GetFromDatabase(newClassroom);
}

But to no avail; now when I save a Class it complains that it references a transient Classroom (and if I skip the Classroom, ClassStudent references a transient Student), and that I must persist it to the Database - despite the fact that the transient Classroom it references was retrieved from the Database, and I therefor know it is persistent.

I need help figuring out what to do, please.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
GalaxyGuy
  • 11
  • 3

1 Answers1

0

remove .Cascade.SaveUpdate() and use session.Merge if newClassroom can be some detached but persisted entity. Example:

ISession session = ;
using (var tx = sess.BeginTransaction())
{
    Classroom someDeserializedClassroom = ;
    Student student = ;
    
    // merge will update the existing class if present otherwise insert a new one
    student.AddToClass(session.Merge(someDeserializedClassroom), seatNumber);
    
    tx.Commit();
}

however if you have the id of the classroom only then

ISession session = ;
using (var tx = sess.BeginTransaction())
{
    var classroomId = ;
    Student student = ;
    
    // Load will create a lazyloading proxy which is only used to satisfy the room here
    student.AddToClass(session.Load(classroomId), seatNumber);
    
    tx.Commit();
}
Firo
  • 30,626
  • 4
  • 55
  • 94