Inserting values in a m:n relation

Posts   
 
    
Dordanov
User
Posts: 10
Joined: 28-Apr-2004
# Posted on: 29-Apr-2004 16:40:30   

I read in the help file about inserting values in a m:n relationship (in the "How do I... ?" section)

It was stated there that the following code should be used to insert such a value:

DepartmentEntity department = new DepartmentEntity(1);
EmployeeEntity newEmployee = new EmployeeEntity();
newEmployee.Name = "John Doe";
DepartmentEmployeesEntity departmentEmployees = new DepartmentEmployeesEntity();
departmentEmployees.Department = department;
departmentEmployees.Employee = employee;
// save recursively
departmentEmployees.Save(true);

Hower, regarding OO programming and such, I would find it a lot better if I could achieve the same result using the following code:

DepartmentEntity department = new DepartmentEntity(1);
EmployeeEntity newEmployee = new EmployeeEntity();
newEmployee.Name = "John Doe";

department.Employee.Add(newEmployee);

department.Save()

Ideally the newly created relation would ofcourse only be saved if the department.Save() method was called.

Is there any way to add items in a relation like this ? I could not find a way to do this, but I may have overlooked something ofcourse.

Thanks in advance.

Regards, Dordanov

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 29-Apr-2004 17:45:13   

It's done hte verbose way for the following reason: Say you use the nonverbose way (department.Employees.Add(myEmployee)). This perhaps works fine for a while, and your code is progressing. However, then a new column is added to the DepartmentEmployees table: StartDate, the date the employee started to work for the department. This means that the relation department-employee suddenly is 'objectified', i.e. becomes a separate entity. This also means that your code department.Employees.Add(myEmployee) will not work, because you have to specify StartDate.

This thus implies that a 'hidden' entity suddenly becomes visible and requires you to add more lines of code than just a myDepartmentEmployees.StartDate = date.

To keep everything clear and consistent, we decided that every entity is visible and doesn't pop up as 'visible' during development all of a sudden.

Frans Bouma | Lead developer LLBLGen Pro
obzekt
User
Posts: 60
Joined: 29-Apr-2004
# Posted on: 29-Apr-2004 19:54:30   

An idea is (in SelfServ/TwoClass scenario) to create a method encapsulating the 'objectifiable' relationship like this:

DepartmentEntity department = new DepartmentEntity(1);
EmployeeEntity newEmployee = new EmployeeEntity();
newEmployee.Name = "John Doe";
department.AddEmployee(newEmployee);

AddEmployee() can later be updated with a StartDate input param.

Alternatively, an HRManager class can be used to encapsulate all Department/Employee functions.

Dordanov
User
Posts: 10
Joined: 28-Apr-2004
# Posted on: 03-May-2004 12:54:05   

Otis wrote:

It's done hte verbose way for the following reason: Say you use the nonverbose way (department.Employees.Add(myEmployee)). This perhaps works fine for a while, and your code is progressing. However, then a new column is added to the DepartmentEmployees table: StartDate, the date the employee started to work for the department. This means that the relation department-employee suddenly is 'objectified', i.e. becomes a separate entity. This also means that your code department.Employees.Add(myEmployee) will not work, because you have to specify StartDate.

I'm sorry but I have to disagree with you on this point. Most of the time you will be adding such a new Employee wich already exists. (Was maybe a bad example of my side to create a new employee, instead of fetching an existing one) The only thing that has to be done while saving a relation is adding a primary key to a linktable, or updating the foreign key in a table as far as I know. So the add method will not need to know nor use any of the other columns of the added entity.

On a side note, if this sort of functionality is not added on purpose to prevent code breakage: if you ask me the code would break in both cases the way I gave code, because in neither cases the date is specified.

Just the way I see this, i can give an example of how we managed to do this by a one-to-many relationship using the other OR mapper we tried to let you see what I mean:

Template:

<#  
    foreach(IRelation relation in Table.ParentRelations)
    { 
        string tableName = relation.ParentColumns[0].Table.Name;
        string methodName = "GetBy" + SharedUtils.GetColumnListName(relation.ChildColumns);
        ##>

    /// <summary>
    /// This method is added to the RapTier Template by Johan and Nico.
    /// Use this method to acces a related Model directly instead of locating it with the specified ID value.
    /// Allso setting the related model can be done by providing the model,
    /// the Primary key of the provided Model is automatically set as Foreign Key correctly
    /// </summary>
    public <#= tableName #> <#= tableName #>
    {
        get
        {               
            using(<#= GetDbClassName() #> db = new <#= GetDbClassName() #>())
            {
                <#= tableName #> record = db.<#= tableName #>Collection.GetByPrimaryKey(_<# WriteCallMethodParameterList(relation.ChildColumns, null, false); #>);
                return record;
            }
        }

        set { _<# WriteCallMethodParameterList(relation.ChildColumns, null, false); #> = value.<#= relation.ParentColumns[0].Name #>; }
    } 
<#
    } ##>

When this piece of template code was used to make a User model with the fields ID, Name, Password and GroupID (FK referencing a group) the following code was generated:

public Group Group
{
    get
    {
        using ( JohNic db = new JohNic() )
        {
            Group record = db.GroupCollection.GetByPrimaryKey(_groupID);
            return record;  
        }
    }
    
    set { _groupID = value.GroupID; }
}

I know the code looks weird, but that's because it's made to work with a different OR mapper, but the basics are lined out and should be understandible I hope wink

This is a very neat way to work with relations, as it enables you to get a group model using myGroup = User.Group; or set a group using myUser.Group = myGroup;

Way less coding for us lazy programmers wink

Otis wrote:

This thus implies that a 'hidden' entity suddenly becomes visible and requires you to add more lines of code than just a myDepartmentEmployees.StartDate = date.

To keep everything clear and consistent, we decided that every entity is visible and doesn't pop up as 'visible' during development all of a sudden.

Not sure what you mean by this, adding a date column and regenerating code will just result in an updated employee class, not in a seperate new one I presume ??

Anyway, this is the way we would like to see it work, if needed we will try to modify the templates ourselves to be able to use it.

Regards, Dordanov

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 03-May-2004 13:35:12   

I think we're talking about 2 different things. simple_smile

You can load / retrieve related entities over a m:n border already, like myEmployee.Departments which then uses DepartmentEmployees to load the related departments.

You can't SAVE an m:n related pair of entities directly, you have to specify the intermediate entity as well, as there is no difference between hidden intermediate entities and non-hidden intermediate entities.

Like the m:n relation between order and product. This relation is established by the intermediate entity OrderDetails. However OrderDetails represents an 'objectified' relation, i.e.: it contains non-pk attributes as well.

I therefore CAN'T save an Order-Product m:n relation directly, I have specify the OrderDetails entity (the intermediate entity) as well.

Now, it is inconsistent to have 'some' m:n relations hidden and 'some' non-hidden. It requires that the developer knows which relations are hidden and which are not so the developer can take a 'shorter' route to save the m:n relation.

To add an employee to department 5: EmployeeEntity newEmployee = new EmployeeEntity(); newEmployee.Name = "Frans Bouma"; DepartmentEntity department = new DepartmentEntity(5); DepartmentEmployeesEntity mnObject = new DepartmentEmployeesEntity(); mnObject.Department = department; mnObject.Employee = newEmployee; mnObject.Save(true);

This code is consistent with all the rest of the m:n relations in the application. That's why it is implemented this way, to be consistent.

I can derive all employees of department 5 this way: DepartmentEntity department = new DepartmentEntity(5); EmployeeCollection employees = department.Employees;

This uses the m:n relation.

Frans Bouma | Lead developer LLBLGen Pro
Dordanov
User
Posts: 10
Joined: 28-Apr-2004
# Posted on: 03-May-2004 13:52:50   

Otis wrote:

Like the m:n relation between order and product. This relation is established by the intermediate entity OrderDetails. However OrderDetails represents an 'objectified' relation, i.e.: it contains non-pk attributes as well.

I therefore CAN'T save an Order-Product m:n relation directly, I have specify the OrderDetails entity (the intermediate entity) as well.

Ah, this is the part I didn't get in your last post, hence I didn't understand why it should be impossible.

I understand it's not possible to let it work this way with link tables that contain more info then just 2 foreign keys.

The database we are currently using however only has many to many relations through "dedicated" link tables wich only contain of 2 foreign key fields.

Isn't it possible to check for the number of fields in a m:n relationship and only generate the code if there are 2 fields ?

If so, could you please give an example of how to implement this into a template ? then we will try to implement it in our template, as it will make our programming quite a bit easier.

Regards, Dordanov