Click here to Skip to main content
Click here to Skip to main content

DynamicData Many to Many FieldTemplate

By , 15 Oct 2008
Rate this:
Please Sign up or sign in to vote.

Introduction

In Dynamic Data, when you have a database with an n-to-n or many-to-many relationship like this...

tables.jpg

... the default DynamicData setup will be three table edits, but wouldn't it be easier if in the Table1 edit, you get a multiple select input like so:
editscreen2.JPG

The attached zip file gives you that multiple select option for DynamicData items.

Using the Code

I've only implemented it for the manytomany table without extra fields (so only the two foreign keys themselves) and these have to be of type int in my example.

The dynamic data setup does a lot for you but the setup for many to many relations isn't really useful (the extra table instead of editing it in the correct table). So if you add the following UIHint attribute to your manytomany column...

[UIHint("MultipleSelect")]

... and put the multipleselect_edit source in the FieldTemplates folder, you get the MultipleSelect listbox for your many to many relations.

What I did was rely a bit heavily on reflection. To populate the listbox, I use the OnDataBinding and OnPreRender events.

protected override void OnDataBinding( EventArgs e )
{
	base.OnDataBinding(e);

	//find the foreignkeycolumn not pointing to the current table 
         //so the foreignforeign table			
	MetaForeignKeyColumn mfk = (MetaForeignKeyColumn)
		ChildrenColumn.ChildTable.Columns.Single<MetaColumn>
		(c => c is MetaForeignKeyColumn && c.Name != 
		this.Column.Table.EntityType.Name);
	if (mfk != null)
	{
		LinqDataSource lds = new LinqDataSource();
		lds.ContextTypeName = 
			ChildrenColumn.ChildTable.DataContextType.FullName;
		lds.TableName = mfk.ParentTable.Name;

		_ddlItems.DataSource = lds;
		_ddlItems.DataTextField = mfk.ParentTable.DisplayColumn.Name;
		_ddlItems.DataValueField = mfk.ParentTable.PrimaryKeyColumns[0].Name;
		_ddlItems.DataBind();

		//when we set the ListItem.Selected to true here it doesn't work, 
		//so it is deferred to the 
		//prerender and temporarily stored in a string list
		selectedValues.Clear();
		if (!IsPostBack)
		{
			IList a = FieldValue as IList;
			foreach (var b in a)
			{
				object val = ( (PropertyInfo)b.GetType().GetProperty
				(mfk.ForeignKeyNames[0]) ).GetValue(b, null);
				selectedValues.Add(val.ToString());
			}
		}
	}
}

protected override void OnPreRender( EventArgs e )
{
	foreach (string s in selectedValues)
	{
		if (_ddlItems.Items.FindByValue(s) != null)
		{
			_ddlItems.Items.FindByValue(s).Selected = true;
		}
	}
	base.OnPreRender(e);
}

For some reason, setting the Selected = true in the OnDataBinding will not work, so I store the selectedValues in a temp list and set the selected property in the PreRender method.

To save all this, I use the ExtractValues method with an IsPostback check. Don't know if this is the correct place but it seems to work. What the save method does is remove all entities for the ID that is being edited, and then add the items that are selected in the listbox.

protected override void ExtractValues( IOrderedDictionary dictionary )
{
	if (IsPostBack)
	{
		//find the foreignkey pointing to this table
		MetaForeignKeyColumn mfkThis = (MetaForeignKeyColumn)
			ChildrenColumn.ChildTable.Columns.Single<metacolumn />
			(c => c is MetaForeignKeyColumn && c.Name == 
			this.Column.Table.EntityType.Name);
		//find the foreignkey pointing to the other table
		MetaForeignKeyColumn mfk = (MetaForeignKeyColumn)
			ChildrenColumn.ChildTable.Columns.Single<metacolumn />
			(c => c is MetaForeignKeyColumn && c.Name != 
			this.Column.Table.EntityType.Name);
		if (mfk != null)
		{
			//get a new context for the updates of the many to many table
			Type t = Type.GetType(Table.DataContextType.FullName);
			System.Data.Linq.DataContext c = 
			 Activator.CreateInstance(t) as System.Data.Linq.DataContext;

			//get the manytomany table object
			ITable o = c.GetType().GetProperty
				(ChildrenColumn.ChildTable.Name).GetValue
				(c, null) as ITable;
			if (o != null)
			{
				//the current edited id
				int currentID = Convert.ToInt32
					(Request.QueryString
					[Table.PrimaryKeyColumns[0].Name]);
				//first delete all items
				foreach (ListItem li in _ddlItems.Items)
				{
					//create the entity to delete
					object entity = Activator.CreateInstance
							(o.ElementType);
					o.ElementType.GetProperty
						(mfkThis.ForeignKeyNames[0]).
						SetValue(entity, currentID, null);
					o.ElementType.GetProperty
						(mfk.ForeignKeyNames[0]).
						SetValue(entity, Convert.ToInt32
						(li.Value), null);
					//has to be attached to be deleted
					o.Attach(entity);
					o.DeleteOnSubmit(entity);
				}
				c.SubmitChanges();
				//then add selected items
				foreach(ListItem li in _ddlItems.Items)
				{
					if (li.Selected)
					{
						//create entity to add
						object entity = 
						  Activator.CreateInstance
						  (o.ElementType);
						o.ElementType.GetProperty
						  (mfkThis.ForeignKeyNames[0]).
						  SetValue(entity, currentID, 
						  null);
						o.ElementType.GetProperty
						  (mfk.ForeignKeyNames[0]).
						  SetValue(entity, 
                                                         Convert.ToInt32(li.Value), 
						  null);
						o.InsertOnSubmit(entity);
					}
				}
				c.SubmitChanges();
			}
		}
	}
}

As you can see, lots of reflection here but that makes it reusable for other types. I also submit the deletes and inserts separately as it didn't work in one go. The ID's of the primarykeys are converted to Int32 so for now only those work. But if you have other types, you can easily make a copy for that specific type if you want. Just change the conversion. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Rooc
Web Developer
Netherlands Netherlands
Developer since 1998 and mainly focused on web development. Currently employed as technical architect.

Comments and Discussions

 
QuestionMultipleSelect in insert mode and View mode PinmemberMember 44640198-Dec-11 22:20 
GeneralGreat Article. Very informative. PinmemberSuperYeti42025-Feb-11 9:57 
GeneralMy vote of 5 PinmemberSuperYeti42025-Feb-11 9:51 
AnswerNot working for me PinmemberMember 361610713-May-09 2:31 
AnswerRe: Not working for me PinmemberJorrit NL23-Jun-09 23:47 
GeneralUsing field template PinmemberMember 73302116-Dec-08 3:36 
I use your great template but i have a problem in ExtraValues on row
 
Type t = Type.GetType(Table.DataContextType.FullName);
 
My problem is that after execution, t variable is null.
 
Thanks
Renzo
AnswerRe: Using field template PinmemberJorrit NL23-Jun-09 23:01 
GeneralMany to many field template Pinmemberrajeshshaw24-Oct-08 18:35 
GeneralRe: Many to many field template Pinmemberkapros30-Nov-08 3:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 15 Oct 2008
Article Copyright 2008 by Rooc
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid