Log in to like this post! Infragistics XamGantt Control - Using Entity Framework Code First to Persist Projects in a Database [Infragistics] Mihail Mateev / Sunday, October 21, 2012 Infragistics Gantt Control helps to create own project planning applications, that can contain most of the Microsoft Project features. One of the most common scenario is to store project data in a database (Microsoft SQL Server or other). Developers are always looking for the easiest way for the implementation of the solution. In this case, it is reasonable to use an ORM framework. This article will demonstrate how to use Entity Framework with Code First approach. Entity Framework 4.x and 5.x contains both the Code First approach and the DbContext API. This API provides a more productive surface for working with the Entity Framework and can be used with the Code First, Database First, and Model First approaches. Entity Framework Code First allows you to create data model classes prior to creating a database. When you run the application for the first time, a database is created for you on the fly based on the classes you created. Database initializers allow you to decide a strategy of database creation and seed data generation. Code First provides its own set of database initializer classes and also allows you to create your own. This approach is a very convenient when you need to distribute your application. Customers need only to have a SQL Server installed. Let’s create a simple WPF application and add XamGantt control instance inside it. You can start with the samples from my previous post: “How to Start Using Infragistics XamGantt Control” , "Data Exchange with Infragistics XamGantt Control - Using XML (Part 1)" or “Data Exchange with Infragistics XamGantt Control - Import/Export to Microsoft Project (Part 2)”. Add buttons to call import from and export to project. If you want to create a Silverlight simple you can use the same snippets, mentioned for WPF application. What you need in advance: To have some experience in the use of Entity Framework 4.x or 5.x To install NetAdvantage for WPF vol. 12.2 or NetAdvantage for Silverlight Vol. 12.2 (these products are also included in NetAdvantage for .NET and NetAdvantage Ultimate) To install EntityFramework, run the following command in the Package Manager Console PM> Install-Package EntityFramework Create the Model Let’s define a very simple model using classes. You can start with the models samples from my previous post “Data Exchange with Infragistics XamGantt Control - Import/Export to Microsoft Project (Part 2)”. Create a Context The simplest way to start using the classes for data access is to define a context that derives from System.Data.Entity.DbContext and exposes a typed DbSet<TEntity> for each class in my model. You need to add a reference to the EF assembly Project -> Add Reference… Select the “.NET” tab Select “EntityFramework” from the list Click “OK” You’ll also need a reference to the System.Data.Entity assembly: Project -> Add Reference… Select the “.NET” tab Select “System.Data.Entity” from the list Click “OK” Let’s create a derived context: Add a derived context below the existing classes that we’ve defined. You need to inherit System.Data.Entity.DbContext. By default when you run the application your database should be created if it doesn’t exist. 1: public class DbProjectViewModel : DbContext, INotifyPropertyChanged 2: { 3: 4: #region SelectedTask 5: private TaskModel _selectedTask; 6: public TaskModel SelectedTask 7: { 8: get 9: { 10: return _selectedTask; 11: } 12: set 13: { 14: if (value != null) 15: { 16: _selectedTask = value; 17: } 18: 19: NotifyPropertyChanged("SelectedTask"); 20: } 21: } 22: #endregion SelectedTask 23: 24: #region Tasks 25: private DbSet<TaskModel> _tasks; 26: public DbSet<TaskModel> Tasks {get; set;} 27: 28: #endregion //Tasks 29: 30: #region NotifyPropertyChanged 31: public event PropertyChangedEventHandler PropertyChanged; 32: protected void NotifyPropertyChanged(String info) 33: { 34: if (PropertyChanged != null) 35: { 36: PropertyChanged(this, new PropertyChangedEventArgs(info)); 37: } 38: } 39: #endregion NotifyPropertyChanged 40: 41: } If you do not want to generate a database at all, as you already have a database, you can add the following to the DbProjectViewModel constructor: 1: #region Constructor 2: public DbProjectViewModel() 3: : base() 4: { 5: Database.SetInitializer<DbProjectViewModel>(null); 6: } 7: 8: public DbProjectViewModel(String connection) 9: : base(connection) 10: { 11: Database.SetInitializer<DbProjectViewModel>(null); 12: } 13: #endregion Constructor Your task model will be the same as in previous posts. 1: public class TaskModel : ObservableModel 2: { 3: public TaskModel():base() 4: { 5: } 6: 7: public TaskModel(TaskModel oldTask) 8: : base() 9: { 10: ConstraintType = oldTask.ConstraintType; 11: ConstraintDate = oldTask.ConstraintDate; 12: Id = oldTask.Id; 13: DeadlineDate = oldTask.DeadlineDate; 14: TaskName = oldTask.TaskName; 15: Index = oldTask.Index; 16: Id = oldTask.Id; 17: DurationTicks = oldTask.DurationTicks; 18: Start = oldTask.Start; 19: IsMilestone = oldTask.IsMilestone; 20: IsInProgress = oldTask.IsInProgress; 21: } 22: 23: 24: #region ConstraintType 25: private ProjectTaskConstraintType _constraintType; 26: public ProjectTaskConstraintType ConstraintType 27: { 28: get 29: { 30: return _constraintType; 31: } 32: set 33: { 34: if (_constraintType != value) 35: { 36: _constraintType = value; 37: this.NotifyPropertyChanged("ConstraintType"); 38: } 39: } 40: } 41: #endregion ConstraintType 42: 43: #region ConstraintDate 44: private DateTime? _constraintDate; 45: public DateTime? ConstraintDate 46: { 47: get 48: { 49: return _constraintDate; 50: } 51: set 52: { 53: if (_constraintDate != value) 54: { 55: _constraintDate = value; 56: this.NotifyPropertyChanged("ConstraintDate"); 57: } 58: } 59: } 60: #endregion ConstraintDate 61: 62: #region DeadlineDate 63: private DateTime? _deadlineDate; 64: public DateTime? DeadlineDate 65: { 66: get 67: { 68: return _deadlineDate; 69: } 70: set 71: { 72: if (_deadlineDate != value) 73: { 74: _deadlineDate = value; 75: this.NotifyPropertyChanged("DeadlineDate"); 76: } 77: } 78: } 79: #endregion //DeadlineDate 80: 81: #region DurationFormat 82: private ProjectDurationFormat _durationFormat; 83: public ProjectDurationFormat DurationFormat 84: { 85: get 86: { 87: return _durationFormat; 88: } 89: set 90: { 91: if (_durationFormat != value) 92: { 93: _durationFormat = value; 94: this.NotifyPropertyChanged("DurationFormat"); 95: } 96: } 97: } 98: #endregion DurationFormat 99: 100: #region Duration 101: private TimeSpan _duration; 102: 103: //[XmlIgnore] 104: [NotMapped] 105: public TimeSpan Duration 106: { 107: get 108: { 109: return _duration; 110: } 111: set 112: { 113: if (_duration != value) 114: { 115: _duration = value; 116: this.NotifyPropertyChanged("Duration"); 117: } 118: } 119: } 120: #endregion Duration 121: 122: #region DurationTicks 123: 124: // Pretend property for serialization 125: [XmlElement("Duration")] 126: public long DurationTicks 127: { 128: get { return _duration.Ticks; } 129: set { _duration = new TimeSpan(value); } 130: } 131: 132: #endregion DurationTicks 133: 134: #region Id 135: private Int32 _id; 136: 137: 138: public Int32 Id 139: { 140: get 141: { 142: return _id; 143: } 144: set 145: { 146: if (_id != value) 147: { 148: _id = value; 149: this.NotifyPropertyChanged("Id"); 150: } 151: } 152: } 153: #endregion Id 154: 155: #region Index 156: private TimeSpan _index; 157: 158: 159: public TimeSpan Index 160: { 161: get 162: { 163: return _index; 164: } 165: set 166: { 167: if (_index != value) 168: { 169: _index = value; 170: this.NotifyPropertyChanged("Index"); 171: } 172: } 173: } 174: #endregion Index 175: 176: #region IsMilestone 177: private bool _isMilestone = false; 178: public bool IsMilestone 179: { 180: get 181: { 182: return _isMilestone; 183: } 184: set 185: { 186: if (_isMilestone != value) 187: { 188: _isMilestone = value; 189: this.NotifyPropertyChanged("IsMilestone"); 190: } 191: } 192: } 193: #endregion IsMilestone 194: 195: #region IsInProgress 196: private bool _isInProgress = true; 197: public bool IsInProgress 198: { 199: get 200: { 201: return _isInProgress; 202: } 203: set 204: { 205: if (_isInProgress != value) 206: { 207: _isInProgress = value; 208: this.NotifyPropertyChanged("IsInProgress"); 209: } 210: } 211: } 212: #endregion IsInProgress 213: 214: #region IsUndetermined 215: private bool _isUndetermined = false; 216: public bool IsUndetermined 217: { 218: get 219: { 220: return _isUndetermined; 221: } 222: set 223: { 224: if (_isUndetermined != value) 225: { 226: _isUndetermined = value; 227: this.NotifyPropertyChanged("IsUndetermined"); 228: } 229: } 230: } 231: #endregion //IsUndetermined 232: 233: #region Predecessors 234: private string _predecesors; 235: public string Predecessors 236: { 237: get { return _predecesors; } 238: set 239: { 240: if (value != _predecesors) 241: { 242: _predecesors = value; 243: this.NotifyPropertyChanged("Predecessors"); 244: } 245: } 246: } 247: #endregion //Predecessors 248: 249: #region PredecessorsIdText 250: private string _predecessorsIdText; 251: public string PredecessorsIdText 252: { 253: get { return _predecessorsIdText; } 254: set 255: { 256: if (value != _predecessorsIdText) 257: { 258: _predecessorsIdText = value; 259: this.NotifyPropertyChanged("PredecessorsIdText"); 260: } 261: } 262: } 263: #endregion //PredecessorsIdText 264: 265: #region Successors 266: private string _successors; 267: public string Successors 268: { 269: get { return _successors; } 270: set 271: { 272: if (value != _successors) 273: { 274: _successors = value; 275: this.NotifyPropertyChanged("Successors"); 276: } 277: } 278: } 279: #endregion //Successors 280: 281: #region Start 282: private DateTime? _start; 283: public DateTime? Start 284: { 285: get 286: { 287: return _start; 288: } 289: set 290: { 291: if (_start != value) 292: { 293: _start = value; 294: this.NotifyPropertyChanged("Start"); 295: } 296: } 297: } 298: #endregion Start 299: 300: #region Finish 301: private DateTime? _finish; 302: public DateTime? Finish 303: { 304: get 305: { 306: return _finish; 307: } 308: set 309: { 310: if (_finish != value) 311: { 312: _finish = value; 313: this.NotifyPropertyChanged("Finish"); 314: } 315: } 316: } 317: #endregion Finish 318: 319: 320: #region TaskID 321: private string _taskId; 322: public string TaskID 323: { 324: get 325: { 326: return _taskId; 327: } 328: set 329: { 330: if (_taskId != value) 331: { 332: _taskId = value; 333: this.NotifyPropertyChanged("TaskID"); 334: } 335: } 336: } 337: #endregion TaskID 338: 339: #region Tasks 340: private string _tasks; 341: public string Tasks 342: { 343: get 344: { 345: return _tasks; 346: } 347: set 348: { 349: if (_tasks != value) 350: { 351: _tasks = value; 352: this.NotifyPropertyChanged("Tasks"); 353: } 354: } 355: } 356: #endregion Tasks 357: 358: #region TaskName 359: private string _name; 360: [Key] 361: public string TaskName 362: { 363: get 364: { 365: return _name; 366: } 367: set 368: { 369: if (_name != value) 370: { 371: _name = value; 372: this.NotifyPropertyChanged("TaskName"); 373: } 374: } 375: } 376: #endregion TaskName 377: 378: #region ResourceName 379: private string _resourceName; 380: public string ResourceName 381: { 382: get 383: { 384: return _resourceName; 385: } 386: set 387: { 388: if (_resourceName != value) 389: { 390: _resourceName = value; 391: this.NotifyPropertyChanged("ResourceName"); 392: } 393: } 394: } 395: #endregion ResourceName 396: 397: } 398: } Add an instance of your derived context in the sample application. You can use it in the same way like you will bind XamGantt control to a model with custom tasks, demonstrated in the blog "How to Start Using Infragistics XamGantt Control". 1: <Grid.Resources> 2: <models:DbProjectViewModel x:Key="viewmodel" /> 3: </Grid.Resources> You need to cerate a ListBackedProject and a Gantt control instances. ListBackedProject.TaskItemsSource is bound to Tasks.Local that returns ObservableCollection<TaskModel>. 1: <ig:ListBackedProject x:Name="dataProvider" TaskItemsSource="{Binding Tasks.Local}"> 2: <ig:ListBackedProject.TaskPropertyMappings> 3: <ig:ProjectTaskPropertyMappingCollection UseDefaultMappings="True"> 4: 5: <!--Mandatory mappings --> 6: <ig:ProjectTaskPropertyMapping TaskProperty="DataItemId" 7: DataObjectProperty="TaskID" /> 8: <ig:ProjectTaskPropertyMapping TaskProperty="ConstraintType" 9: DataObjectProperty="ConstraintType" /> 10: <ig:ProjectTaskPropertyMapping TaskProperty="ConstraintDate" 11: DataObjectProperty="ConstraintDate" /> 12: <ig:ProjectTaskPropertyMapping TaskProperty="DurationFormat" 13: DataObjectProperty="DurationFormat" /> 14: <ig:ProjectTaskPropertyMapping TaskProperty="Tasks" 15: DataObjectProperty="Tasks" /> 16: <!--End of mandatory mappings --> 17: 18: <!--Manual tasks mapping --> 19: 20: <ig:ProjectTaskPropertyMapping TaskProperty="IsManual" 21: DataObjectProperty="IsUndetermined" /> 22: 23: <!--End of maual tasks mapping --> 24: 25: <!--Task link mapping --> 26: 27: <ig:ProjectTaskPropertyMapping TaskProperty="Predecessors" DataObjectProperty="Predecessors" /> 28: 29: <!--End of task link mapping --> 30: 31: <!--Other tasks mapping --> 32: 33: <ig:ProjectTaskPropertyMapping TaskProperty="IsActive" 34: DataObjectProperty="IsInProgress" /> 35: 36: <ig:ProjectTaskPropertyMapping TaskProperty="Finish" 37: DataObjectProperty="Finish" /> 38: 39: <ig:ProjectTaskPropertyMapping TaskProperty="DurationFormat" 40: DataObjectProperty="DurationFormat" /> 41: 42: 43: <ig:ProjectTaskPropertyMapping TaskProperty="TaskName" 44: DataObjectProperty="TaskName" /> 45: 46: <ig:ProjectTaskPropertyMapping TaskProperty="Start" 47: DataObjectProperty="Start" /> 48: 49: 50: <ig:ProjectTaskPropertyMapping TaskProperty="Duration" 51: DataObjectProperty="Duration" /> 52: 53: 54: <ig:ProjectTaskPropertyMapping TaskProperty="IsMilestone" 55: DataObjectProperty="IsMilestone" /> 56: 57: <ig:ProjectTaskPropertyMapping TaskProperty="Deadline" 58: DataObjectProperty="DeadlineDate" /> 59: 60: <!--End of other tasks mapping --> 61: 62: 63: </ig:ProjectTaskPropertyMappingCollection> 64: </ig:ListBackedProject.TaskPropertyMappings> 65: <ig:ListBackedProject.TaskSettings> 66: <ig:ProjectTaskSettings AllowChangeIndentation="True" 67: AllowDelete="True" 68: AllowDragDeadline="True" 69: AllowDragMilestone="True" 70: AllowDragPercentComplete="True" 71: AllowDragSummary="Always" 72: AllowDragTask="True" 73: AllowInsert="True" 74: AllowResizeSummary="Always" 75: AllowResizeTask="True"/> 76: </ig:ListBackedProject.TaskSettings> 77: 78: </ig:ListBackedProject> 1: <ig:XamGantt Margin="10" x:Name="xamGantt" VerticalAlignment="Top" ActiveCellChanged="xamGantt_ActiveCellChanged" Project="{Binding ElementName=dataProvider}"> 2: <ig:XamGantt.DefaultColumnSettings> 3: <ig:ProjectColumnSettings AllowHide="True" 4: AllowMove="True" 5: AllowResize="True" 6: AllowShow="True" /> 7: </ig:XamGantt.DefaultColumnSettings> 8: </ig:XamGantt> Save project to database Let’s first try to save Gantt project to Microsoft SQL Server database. Create a project with tasks in your XamGantt appellation. To be possible to save tasks with correct data about links you need to add an additional code – here it is implemented in UpdatePredecessorsIdText method. 1: #region btnSave_Click 2: private void btnSave_Click(object sender, RoutedEventArgs e) 3: { 4: var model = this.Root.Resources["viewmodel"] as DbProjectViewModel; 5: if (model != null) 6: { 7: UpdatePredecessorsIdText(xamGantt); 8: SaveGanttProject(model); 9: } 10: } 11: #endregion btnSave_Click Unlike previous blogs here should keep not only information about PredecessorsIdText property but also information about the task sequence number is stored in a property named id. Add this property in your custom task model. When you save and read the model tasks could not be saved/read in the proper number. This property will keep the original task sequence in the Infragistics Gantt project. 1: #region Id 2: private Int32 _id; 3: 4: public Int32 Id 5: { 6: get 7: { 8: return _id; 9: } 10: set 11: { 12: if (_id != value) 13: { 14: _id = value; 15: this.NotifyPropertyChanged("Id"); 16: } 17: } 18: } 19: #endregion Id Add in the UpdatePredecessorsIdText method code that copy the value from the readonly property ProjectTask.Id to your custom tasks before to save the model. 1: #region UpdatePredecessorsIdText 2: private void UpdatePredecessorsIdText(XamGantt gantt) 3: { 4: foreach (ProjectTask task in gantt.Project.RootTask.Tasks) 5: { 6: var customTask = task.DataItem as TaskModel; 7: customTask.PredecessorsIdText = task.PredecessorsIdText; 8: customTask.Id = task.Id; 9: } 10: } 11: #endregion UpdatePredecessorsIdText The real save to your database is a very simple just call SaveChanges() method of your derived context. 1: #region SaveGanttProject 2: private void SaveGanttProject(DbProjectViewModel model) 3: { 4: model.SaveChanges(); 5: } 6: #endregion SaveGanttProject Database settings Connection strings You can tell DbContext to use this connection by passing the connection string name to the DbContext constructor. For example: 1: public class DbProjectViewModel : DbContext, INotifyPropertyChanged 2: { 3: #region Constructor 4: public DbProjectViewModel() 5: : base("name=Northwind_Entities") 6: { 7: Database.SetInitializer<DbProjectViewModel>(null); 8: } 9: //implementation... 10: } Code First Default Connection Factory Connection strings go in the standard connectionStrings element and do not require the new entityFramework section. This configuration section allows you to specify a default connection factory that Code First should use to locate a database to use for a context. The default connection factory is only used when no connection string has been added to the configuration file for a context. In the sample application is used the approach when you have no connection string and use default connection factory settings. When you use default connection factory without changes you will use a .\SQLEXPRESS instance. Where’s My Data? DbContext by convention created a database for you on localhost\SQLEXPRESS. The database is named after the fully qualified name of your derived context, in our case that is “WPfIgGantImportExport.Models.DbProjectViewModel”. We’ll look at ways to change this later in the walkthrough. 1: <entityFramework> 2: <defaultConnectionFactory 3: type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" /> 4: </entityFramework> If you don’t set a default connection factory, Code First uses the SqlConnectionFactory, pointing to .\SQLEXPRESS.SqlConnectionFactory also has a constructor that allows you to override parts of the connection string. If you want to use a SQL Server instance other than .\SQLEXPRESS you can use this constructor to set the server. 1: <entityFramework> 2: <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> 3: <parameters> 4: <parameter value="Data Source=(local);integrated security=True;multipleactiveresultsets=True"/> 5: </parameters> 6: </defaultConnectionFactory> 7: </entityFramework> Entity Framework Code First now uses local Microsoft SQL Server 2012 instance. Read project from database Write a LINQ expression that gets your tasks from data base. place this code in a separate method, named in this sample “ReadGanttProject”. 1: #region btnRead_Click 2: private void btnRead_Click(object sender, RoutedEventArgs e) 3: { 4: var model = this.Root.Resources["viewmodel"] as DbProjectViewModel; 5: if (model != null) 6: { 7: ReadGanttProject(ref model); 8: } 9: } 10: #endregion btnRead_Click Get all tasks in a separate list, ordered by Id. Clear the tasks from your model and add tasks from your list one by one from the smallest Id to the largest. 1: #region ReadGanttProject 2: private void ReadGanttProject(ref DbProjectViewModel model) 3: { 4: 5: var mm = model.Tasks.Where(a => a.TaskName != "").OrderBy(a=>a.Id).ToList(); 6: 7: foreach (var entity in model.Tasks) 8: { 9: model.Tasks.Remove(entity); 10: } 11: 12: //Clear the read tasks 13: model.SaveChanges(); 14: 15: xamGantt.Project.RootTask.Tasks.Clear(); 16: 17: for (int i = 0; i < mm.Count(); i++) 18: { 19: model.Tasks.Add(mm[i]); 20: } 21: 22: foreach (ProjectTask prTask in xamGantt.Project.RootTask.Tasks) 23: { 24: var taskModel = prTask.DataItem as TaskModel; 25: prTask.PredecessorsIdText = taskModel.PredecessorsIdText; 26: } 27: 28: //Update database again 29: model.SaveChanges(); 30: 31: } 32: #endregion ReadGanttProject Now you have all tasks from database in your XamGantt application. Try to delete a specified task, add a new task and save the project. Database is updated without any issues. The ListBackedProject.TaskItemsSource is not bound Tasks (Tasks is from DbSet type and doesn’t support property changed notifications) . It is bound to Tasks.Local that returns ObservableCollection<TaskModel>. <ig:ListBackedProject x:Name="dataProvider" TaskItemsSource="{Binding Tasks.Local}"> In this case all property changed notifications updates the tasks inside the XamGantt control. Conclusions It is very easy to a database (Microsoft SQL Server or other to persist your project data from an application with an Infragistics XamGantt control. This scenario could be used for multi-user systems for project management and planning. The sample application could be modified with a few small changes to support many different projects as a real life application. Using Infragistics NetAdvantage for XAML (WPF and Silverlight) Vol. 12.2 you can create software for project-management teams providing the benefit from the next NetAdvantage updates of and features . Follow news from Infragistics for more information about new Infragistics products. Source code is available here. Sample project is created with Visual Studio 2012, but you can use NetAdvantage 12.2 controls with both: Visual Studio 2012 and Visual Studio 2012. As always, you can follow us on Twitter @mihailmateev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!