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!