none
Managing relations on pending entities RRS feed

  • Question

  • Hi.

     

    I am using LINQ to SQL (sql desktop database 3.5 ) in a desktop application. I have implemented an infrastructure that uses the DataContext as a singleton to represent the data access layer.

    This works quite ok, though I am aware that some might say that this is not the intended use of the DC...

     

    Ok, my problem is this.

    When the user creates a new object through the UI I need the possibility to add default values as well as defualt object(s) to relationships and let the user interact with this newly created object like an object that has been fetched from the database.

     

    So, Where do I "plug-in" my initialization code for object relations? An example illustrated on the Northwind db; After creating an order entity from the UI, I need to create a new order item and add this to the EntitySet that holds the order items for this order. Please remember that I can not Submit the order first, get it again and do this...

    The constructor cannot be used since this is called when creating objects from db as well. I can sort of do this if I convert all relation-collections to my Collection class that implements BindingList. Then I can provide extra initialization after inserting the entity in the OnAddingNew method.

    But what about EntitySet´s?

    What i´m looking for is a "After Insert" method that I cam override or extend. That is, a point in execution where an entity has been added to an entityset and inserted, but still not "Submitted" and then access its properties where relationship entitysets might be represent some of these properties.

     

    I might be able to work around this by converting each entityset to the collection class that I described earlier but I´d rather not.

     

    Thanks for any help.

     

    Monday, March 10, 2008 1:26 PM

Answers

  • I still have no idea how to hook into EntitySet but I still don't understand why.

    Let say you have Northwind with Customer and Orders you can shown the orders of the selected customer in a second datagridview without having to do a select.

    There are two BindingSources which are generated as follows:

    Code Snippet

    //

    // customerBindingSource

    //

    this.customerBindingSource.DataSource = typeof(WindowsFormsApplication1.Customer);

    //

    // ordersBindingSource

    //

    this.ordersBindingSource.DataMember = "Orders";

    this.ordersBindingSource.DataSource = this.customerBindingSource;

     

     

    and the code looks like:

    Code Snippet

    public partial class Form1 : Form

    {

    private NorthWindDataContext DC = new NorthWindDataContext();

    public Form1()

    {

    InitializeComponent();

    ordersBindingSource.AddingNew += new AddingNewEventHandler(ordersBindingSource_AddingNew);

    }

    void ordersBindingSource_AddingNew(object sender, AddingNewEventArgs e)

    {

    var newOrder = new Order();

    newOrder.OrderDate = DateTime.Today;

    newOrder.ShipAddress = "Somewhere";

    e.NewObject = newOrder;

    }

    private void Form1_Load(object sender, EventArgs e)

    {

    var qry = from c in DC.Customers select c;

    customerBindingSource.DataSource = qry;

    }

    }

     

     

    This will set new Orders with the default for OrderDate and ShipAddress and CustomerID = CustomerID of the selected customer.

    Why do you want to hook into EntitySet<Order>? It seams that I overlooked something in what you have written.

     

    regards

    Philipp

    Monday, March 10, 2008 5:23 PM

All replies

  • Hi,

     

    you described the possible solutions. I started with building a new class with inherits from BindingList<T>. This gives me the chance to customize new object and I can add the calls to DeleteOnSubmit and InsertOnSubmit in Add and Remove methods. This saves a some coding in the application.

    Then I read a blog entry (sorry but I don't remember where) that describes that Queries implements the IListSource interface. Using the .Net reflector from Lutz Roeder I discover that IListSource will return a SortableBindingList<TEntity> for anonymous objects or a  DataBindingList<TEntity>. Both contains most of the logic I used in own class.

    Now I use the approach

    BindingList<Customer> myList = ((IListSource)myQuery).GetList();

    CustomerBindingSource.DataSource = myList;

    The class BindingList supports an event AddingNew and I use this for customizing my new entities.

    myList.AddingNew += yourEventHandler;

    From my point of view this is the best way. I got all the stuff from BindingList and I can use the event to set the default specific to each form where needed.

     

    regards

    Philipp

    Monday, March 10, 2008 2:19 PM
  • Thanks for your reply.

     

    Ok, it seems that I do sort of the same today already. I have a base collection class Collection<T> that inherits from BindingList<T> and where T must be of a certain base class type. In this I have implemented OnAddingNew() and RemoveItem() to manage custom needs for certain objects.

     

    My entity classes, i.e. Customer inherits from the base class mentioned above and is thus qualified to be contained in my Collection class.

     

    A common scenario is then to load such a collection with a linq query by converting the result into an instance of my Collection class (much like you described above).

     

    BUT, my problem is further down the road. Say that this Collection class is an instance of Collection<Customer> and has been successfully loaded and bound to a UI control. Fine so far, but in some other UI control I show the i.e. Orders connected to the customer selected in the first grid. This control must of course have its datasource set to the property called Orders on the Customer object (entity). And with LINQ to SQL this would be of type EntitySet<Order>.

     

    Now the user adds an order through the UI and I want:

     

    1. The relationship to be populated the correct way and this is Ok since the implementation in the Customer class has been done according to what is generated by the dbml code generator with the attach/detach helper actions and so on. That is, the Order will be added to the Orders property (of type EntitySet<Order>) and will also have its Customer property to be set correctly.

     

    2. The possibility to control the insert (or removal) through my Collection class. And of course this will not happen since the property is of type EntitySet<Order>, not Collection<Order>.

     

    I could add an additional property on the Customer class of type Collection<Order> and convert from/to EntitySet<Order>. But then I will have to duplicate all the code that LINQ handles for me in the first place. That is, if I add a new Order to a Customer I would have to somehow let LINQ know and let it manage the relations as before. I don´t know how to do that. 

    Perhaps there is a nice way to "trigger" the onAdd and onDelete for EntitySet´s in code? If so I might go for this solution...

     

     

    Hoping that you or someone else knows something that I don´t and that will handle this problem in a suitable manner. Thanks!

     

    Monday, March 10, 2008 3:22 PM
  • Hmmmm, you are right it won't help if you add orders to the Orders property of a customer.

    But if you have two controls where the first displays all customer and the second one displays the orders for the selected customer in the first one, then you have two bindingsources too. What about using the AddingNew event of the second bindingsource to set the defaults?

     

    regards

    Philipp

    Monday, March 10, 2008 3:53 PM
  • Yes, but I populate through properties on selected objects in the first control. Going with what you suggest I would have to do I separate query on the UID of the selected object. That is, select all Orders from the Order table where CustomerID_FK == CustomerID of the selected object.

    Even so the relational issues would still be there even though it might work since going from that direction it would be a EntityRef instead.

     

    Well, perhaps I am giving you this from the wrong angle. Lets say that I would like to remain with the EntitySet property navigation and still want to do custom initiation on relations, how could I do that? I mean, given that I have a Customer entity and the user adds an order to its Orders property (EntitySet<Order>), is there a way to stick my nose in at some point after the insert has been done.

    I must be honest that I am not sure when this takes place at all. If I, through my control, gets a Customer.Orders.Add(),

    where and how is this triggered and is there a way to access the entity after insert?

     

    Again, thanks.

    Monday, March 10, 2008 4:10 PM
  • I still have no idea how to hook into EntitySet but I still don't understand why.

    Let say you have Northwind with Customer and Orders you can shown the orders of the selected customer in a second datagridview without having to do a select.

    There are two BindingSources which are generated as follows:

    Code Snippet

    //

    // customerBindingSource

    //

    this.customerBindingSource.DataSource = typeof(WindowsFormsApplication1.Customer);

    //

    // ordersBindingSource

    //

    this.ordersBindingSource.DataMember = "Orders";

    this.ordersBindingSource.DataSource = this.customerBindingSource;

     

     

    and the code looks like:

    Code Snippet

    public partial class Form1 : Form

    {

    private NorthWindDataContext DC = new NorthWindDataContext();

    public Form1()

    {

    InitializeComponent();

    ordersBindingSource.AddingNew += new AddingNewEventHandler(ordersBindingSource_AddingNew);

    }

    void ordersBindingSource_AddingNew(object sender, AddingNewEventArgs e)

    {

    var newOrder = new Order();

    newOrder.OrderDate = DateTime.Today;

    newOrder.ShipAddress = "Somewhere";

    e.NewObject = newOrder;

    }

    private void Form1_Load(object sender, EventArgs e)

    {

    var qry = from c in DC.Customers select c;

    customerBindingSource.DataSource = qry;

    }

    }

     

     

    This will set new Orders with the default for OrderDate and ShipAddress and CustomerID = CustomerID of the selected customer.

    Why do you want to hook into EntitySet<Order>? It seams that I overlooked something in what you have written.

     

    regards

    Philipp

    Monday, March 10, 2008 5:23 PM
  • Hi again.

     

    I understand what you are getting at but my circumstances are, as you mention, a bit different.

     

    First of all, I have no problems setting such default values. Given your example, what I want to do is to create a default order object when creating a new customer. Such that in the design model each customer have one or more orders, but never zero.

    So, when adding a customer, an order object should be created and attached to that customer. And here comes the second difference to your example.

    My "Adding New" is implemented in my base collection class, as mentioned in my previous post. The reason for this is that I can have one single implementation for all adds and removals of all business objects to any collection. But I still think that there is no real practical difference to what you have shown above concerning this difference in implementation.

     

    A few hours ago it struck me that perhaps a solution was right in front of me since I might use the Action delegates defined by dbml generator to add this code. So continuing with the NorthWind ex., in the "attach_Customer(Customer entity)" method (of some entity class holding an entityset of customers) I can create a new Order entity and add it to the customer´s orders entityset.

    I tried this and it seems to work but now the problem is that the order doesn´t show in the UI...

     

    But here I am wondering if I might need to mix a little bit of your suggestions (the bindingsources) into it all. We use third part components in the UI and bind collections (and then entitysets) directly to the datasource of the controls. So, do you know what actually goes on behind the curtains when assigning an entityset to a datasource? Could there be a difference there that makes an entityset that is loaded from db show up ok, but at the same time "hide" it if the objects inside it are assigned to the entityset, hence not loaded from the db? (The IsLoaded property == false for these)

     

    Ok, I can see that this might be a bit too confusing for you since I don´t have a clear picture of where the problem is. But if the entityset class doesn´t work directly against the datasource property when assigning objects in code, then it might be a good idea for me to try the way that you have described using BindingSources.

    I will try this out tomorrow or maybe wednesday and then post my results here.

     

    If you have some input based on what I wrote here, please keep trying with your help :-) It is much appreciated!

     

    Thanks 

     

    Monday, March 10, 2008 8:07 PM
  • Ok, so I have tried this "the bindingsource way", and thanks for remembering me about that, because it makes the UI code so much more slim.

     

    But, I still have the problem that enteties added to a relation on a newly created entity does not show up. They are there, so if I save and reload everything from the db it will show up, but this is not what I want to do and it should not be neccessary either.

    Could this be a bug? Something close to this:

    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2066202&SiteID=1

    and this:

    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2028843&SiteID=1

     

     

    Should be fixed in the release but;

    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2571102&SiteID=1

     

     

    Hmm, this is really annoying and as mentioned in the threads above perhaps I must do some ugly workarounds...

    Tuesday, March 11, 2008 8:46 AM
  • I'm using vs2008 rtm and I build two small tables in my Sql Server 2005. One named parent and one named child. Each have just one nvarchar field named name and there is a relationship defined (parent can havew multiple childs).

    In VS2008 I defined two datagridview which are linked to my two bindingsources.

    Code Snippet

    //

    // parentBindingSource

    //

    this.parentBindingSource.DataSource = typeof(WindowsFormsApplication1.parent);

    //

    // childsBindingSource

    //

    this.childsBindingSource.DataMember = "childs";

    this.childsBindingSource.DataSource = this.parentBindingSource;

     

     

    The code of my form follows

    Code Snippet

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Windows.Forms;

    namespace WindowsFormsApplication1

    {

    public partial class Form1 : Form

    {

    private DatabaseDataContext DC = new DatabaseDataContext();

    public Form1()

    {

    InitializeComponent();

    var qry = from p in DC.parents orderby p.pid select p;

    parentBindingSource.DataSource = qry;

    parentBindingSource.AddingNew += new AddingNewEventHandler(parentBindingSource_AddingNew);

    }

    void parentBindingSource_AddingNew(object sender, AddingNewEventArgs e)

    {

    var np = new parent();

    np.name = "def value";

    var nc = new child();

    nc.name = "def ch1";

    np.childs.Add(nc);

    var nc1 = new child();

    nc1.name = "def ch2";

    np.childs.Add(nc1);

    e.NewObject = np;

    }

    private void parentBindingNavigatorSaveItem_Click(object sender, EventArgs e)

    {

    Validate();

    childsBindingSource.EndEdit();

    parentBindingSource.EndEdit();

    try

    {

    DC.SubmitChanges();

    }

    catch (Exception ex)

    {

    MessageBox.Show(ex.Message);

    }

    }

    }

    }

     

     

    If I hit the 'Add New' button in navigator I get a new parent with default value and two attached new childs with default values. I save them (without doing any modifications to them) and they are stored in the database.

    regards

    Philipp

    Tuesday, March 11, 2008 9:43 AM
  • I can´t get this to work.

     

    My latest shot was to hide the EntitySet properties and expose these through properties that return these but in the shape of IBindingList's using GetNewBindingList(). Before this, using the AddingNew event rendered a "index 'x' does not have a value" IndexOutOfRange exception in my control !

    Now with the GetNewBindingList solution this does not crash but still the second control does not show the related object.

     

    I tried a simple set on the 'Name' property on the object created in AddingNew, this doesn´t show either. What the h*ll is going on here? I can honestly say that there are no real difference to what you have done, but still there must be some I guess. 

    Wednesday, March 12, 2008 7:26 AM
  • Ok, seems like have been looking for the problem in the wrong place. I tried what you did above with these kind of controls and voila! It works, so, something must be going wrong in my third part controls and espescially the navigator.

    Thanks for proving to me that LINQ is not to be blamed for this one!

     

     

    Wednesday, March 12, 2008 7:43 AM
  • Yeah, Linq is great.

    I migrate my application to Linq To SQL. I love to deal with objects and have not to work with offline database caches like Datasets. A lot is far more easier to program.

    Wednesday, March 12, 2008 9:23 AM
  • Just one more question on your example (not related to subject but since we are here discussing...)

     

    Using navigators on the bindingsources works just fine as long as I don´t remove all objects in the parent collection. When loading the form I set the DataMembers of each BindingSource like you showed.

     

    Lets say that I have three items in my parent collection and repeatedly press 'Remove' on the navigator. When removing the last item I get a "DataMember property 'Items' cannot be found on the DataSource" exception.

     

    Ok, I have sort of fixed this by calling resetbindings() on the binding sources and setting datamember values to null. But why must I do that !?

    Anyway, I still get a problem if I subsequently add a new "first" item to the parent binding source. Here the bindingsource seems out of sync with the underlying list or something.

     

    So, my question is, is there a standard way of managing this situation? I thought that by declaring the bindingsource as typeof(Parent), it would manage this by knowing that "ok, the list is empty, but I still know the structure of what i´m supposed to hold"...sort of.

     

    Thanks again.

     

    Wednesday, March 12, 2008 2:52 PM
  • Hi,

     

    I wrote a short test sample using Linq To SQL and all the binding stuff (bindingSource, Navigator, Datagridview (2 for parent and child)).

    I can use navigator to delete all rows step by step until the datagridview is empty and then I can add new rows with default values for the parent row and attached default child row by hitting the 'Add New' button of the navigator. I don't encounter any problem.

     

    regards

    Philipp

    Wednesday, March 12, 2008 4:47 PM
  • How do you generate your bindingsources? From toolbox or by code and in that case where?

     

     

    Wednesday, March 12, 2008 5:40 PM
  • I'm a lazy one. I used the Data Sources Window( add new datasource wizard). Then I drag and drop from this datasource window to my form designer and VS will generate all the stuff (bindingsource, navigator and datagridview).

     

    regards

    Philipp

    Wednesday, March 12, 2008 6:15 PM
  • I have not used the data source config wizard before so I have to ask you if i´m doing something wrong.

     

    I open the data source config wizard and choose "object".

    Then I select my BL class that would correspond to the "Parent" or "Customer" class in the examples here.

     

    Ok, fine so far.

     

    Then I drag a BindingSource to my form and selects the data source above.

    BUT, no properties shows up here as selectable for the datamember property!? I know that given our examples the datamember should be set on the "Child"/"Order" bindingsource. But still I would expect the properties to be selectable, or am I wrong here.

     

    I created the corresponding "child/order" bindingsource and selected the "parent/order" bindingsource. Still no properties to select from...I have tried setting the Bindable attribute on the "childs/orders" property in the "parent/customer" class.

     

     

     

    EDIT:

     

    Here it goes, in the bas class for all my business objects I have implemented ICustomTypeDescriptor to enable automatic globalization through attributes on my properties. Removing this implementation gives me the expected behaviour in the bindingsource config.

    I have to check this implementaiotn and see what it does wrong. Gahh, I hate these kind of problems where it is so difficult to find the real issue. But hey, thanks a lot!

     

     

     

    Thursday, March 13, 2008 7:22 AM
  • You are right about generating the DataSource.

    After generating the Data Source go to the Form Designer. If not available open the Data Sources window. The way the nodes in the Data Sources window are displayed will change if Form Designer is active.

    If you select the nodes in the Data Sources window they will change to comboboxes. Selecting an object will allow you switch between DataGridView and Details and selecting a field will allow to choose the generated control for this field (TextBox, ComboBox, Label,...).

    If you drag on node and drop it into your form Visual Studio will generate some stuff for you. The first time you do this VS generates a BindingSource, NavigatorBar and the controls to display the field or all the fields of the object. All data bindings are already set.

    Now you can customize the controls as you like.

     

    If you generated a Data Source for say Customer you will see the Data Source window that it contains a field named Orders which too allows to be set to DataGridView or Detail.

    If you leave everything as DataGridView, drop the Customer node to you form and drop the subnode (field) Orders of node (object) Customer to your form VS generates the BindingSource for Master-Detail-Relationship (what I called Parent-Child). It's important to know that for doing this you don't have to generate a Data Source for Order. All you need is the one for Customer (you use Order only as Detail part of the Master-Detail_Relationship).

     

    That's the fastest way of generating database applications in VS.

     

    By the way, one thing you have to do. In the NavigatorBar (Form Designer) go the Save Button, choose 'Enabled' form the context menu, double click the button and include the SubmitChanges() call in the event handler.

     

    regards

    Philipp

    Thursday, March 13, 2008 10:15 AM
  •  

    As I mentioned in my last post, everything is working now...

     

    But, also, I have this issue that I described in another post as well in the forms databinding forum. That is, the implementation of ICustomTypeDescriptor messed this up but still I would like to have it. A bug perhaps?

    Friday, March 14, 2008 2:11 PM
  • Hi,

    can you give me a link to this other post?

    thanks

    Philipp

     

    Friday, March 14, 2008 2:59 PM
  • If I want a quick and dirty solution I will drop it from Data Sources Window, since this will generate all the stuff for me.

    In all other cases I use the BindingSource from the toolbox. This will give me the support to define bindings using the property editor of VS.

     

    regards

    Philipp

    Monday, March 17, 2008 12:44 PM
  • Hmm, sure, but that does not help me in this case since I´m already using it from the toolbox. The problem is that when having implemented ICustomTypeDescriptor, the properties does not show up in the property editor. Ok, so lets just code it you might say...well, that´s ok but it is still annoying not to be able to benefit from the designer.

    Monday, March 17, 2008 2:38 PM
  • I never implemented the interface ICustomTypeDescriptor in my objects, I'll try as soon as I will find some time.

    Philipp

    Monday, March 17, 2008 3:40 PM