views:

867

answers:

2

Using LINQ-to-SQL, I would like to automatically create child records when inserting the parent entity. Basically, mimicking how an SQL Insert trigger would work, but in-code so that some additional processing can be done.

The parent has an association to the child, but it seems that I cannot simply add new child records during the DataContext's SubmitChanges().

For example,

public partial class Parent 
{
    partial void OnValidate(System.Data.Linq.ChangeAction action)
    {
        if(action == System.Data.Linq.ChangeAction.Insert)
        {
            Child c = new Child();
            ... set properties ...
            this.Childs.Add(c);
        }
    }
}

This would be ideal, but unfortunately the newly created Child record is not inserted to the database. Makes sense, since the DataContext has a list of objects/statements and probably doesn't like new items being added in the middle of it.

Similarly, intercepting the partial void InsertParent(Parent instance) function in the DataContext and attempting to add the Child record yields the same result - no errors, but nothing added to the database.

Is there any way to get this sort of behaviour without adding code to the presentation layer?

Update: Both the OnValidate() and InsertParent() functions are called from the DataContext's SubmitChanges() function. I suspect this is the inherent difficulty with what I'm trying to do - the DataContext will not allow additional objects to be inserted (e.g. through InsertOnSubmit()) while it is in the process of submitting the existing changes to the database.

Ideally I would like to keep everything under one Transaction so that, if any errors occur during the insert/update, nothing is actually changed in the database. Hence my attempts to mimic the SQL Trigger functionality, allowing the child records to be automatically inserted through a single call to the DataContext's SubmitChanges() function.

+2  A: 

The Add method only sets up a link between the two objects: it doesn't mark the added item for insertion into the database. For that, you need call InsertOnSubmit on the Table<Child> instance contained within your DataContext. The trouble, of course, is that there's no innate way to access your DataContext from the method you describe.

You do have access to it by implementing InsertParent in your DataContext, so I'd go that route (and use InsertOnSubmit instead of Add, of course).

EDITED I assumed that the partial method InsertParent would be called by the DataContext at some point, but in looking at my own code that method appears to be defined but never referenced by the generated class. So what's the use, I wonder?

Ben M
My thoughts exactly - the "child" objects need to be added to the DataContext by means of "InsertOnSubmit" in order to be stored when SubmitChanges() is called.
marc_s
Ben: a partial method will be called - if it's actually implemented. As long as it's only just defined (but not implemented), nothing happens.
marc_s
Right, but there still has to be a call point. In my example DataContext, I can't find any calls to these partial methods by the generated code.
Ben M
Ben: ok, I understand what you mean - I assume these calls are made inside the Linq-to-SQL runtime (not the generated code). As long as the partial methods are not implemented, these calls will be optimized out during compile/link - but once the partial method is implemented, it's being called.
marc_s
Did some investigating with Reflector. The partial declarations are there only to hint at the correct method signature; the L2S runtime uses reflection to find and invoke them. An interesting use of `partial`!
Ben M
Thanks guys - yes, the InsertParent is called by the DataContext assuming it's implemented, as Marc mentioned. In fact, both the InsertParent and OnValidate functions are called during the DataContext SubmitChanges(), which is where the problem lies - InsertOnSubmit() cannot be called during a call to SubmitChanges(), because the DataContext is already in the process of making the changes to the database. I will update the original question and try to clarify this.
BrandonB
+4  A: 

If you want it to happen just before it is saved; you can override SubmitChanges, and call GetChangeSet() to get the pending changes. Look for the things you are interested in (for example, delta.Inserts.OfType<Customer>(), and make your required changes.

Then call base.SubmitChanges(...).

Here's a related example, handling deletes.

Marc Gravell
Marc - thanks a ton. That's exactly what I was looking for and it works perfectly!
BrandonB
Marc, this (and your related link) is an excellent suggestion and was very helpful to me too, thank you!
Funka