How to Perform CRUD Operations in ADO.NET Data Services Using Ajax Client
How to Perform CRUD Operations in ADO.NET Data Services Using Ajax Client
In the last post in
the ADO.NET data
services series I wrote
about how you can
consume data services
using Ajax client. In this post I’m going to explain how to perform CRUD
operations on a data service using an Ajax client.
DataServiceContext and Sys.Data.DataService
Before starting to understand how to perform CRUD operations with Ajax
client first I want to discuss the main difference between using
DataServiceContext, like I did in previous posts, and using
Sys.Data.DataService. When we use the DataServiceContext on the server
side the context caches all the CRUD operations that we make and the only
way to submit these changes is by using the SaveChanges method. In the
Sys.Data.DataService we don’t have this unit of work functionality by default.
Every operation that we perform on the Sys.Data.DataService object is
immediately submitted to the server. There is a way to cache the operations
using an action sequence but I’ll describe it in the next post.
Another difference is that in the Ajax situation we are using the JSON
functionality in order to build the objects on the client side
and submit them to the server. We can’t use the server classes because the
client doesn’t know them.
The Example Data Service Context
In the example I’m using the same data service context I built in the post
How to Perform CRUD Operations in ADO.NET Data Services Using a Custom Provider.
I changed the code that I wrote a little by adding the functionality to the
ResetResource method and the code looks like:
[DataServiceKey("CourseID")]
public class Course
{
#region Properties
/// <summary>
/// The course ID
/// </summary>
public int CourseID { get; set; }
/// <summary>
/// The course title
/// </summary>
public string Title { get; set; }
/// <summary>
/// The duration in days of the course
/// </summary>
public int Days { get; set; }
/// <summary>
/// The course academic credit
/// </summary>
public int Credit { get; set; }
#endregion
#region Methods
/// <summary>
/// Return a fixed list of courses
/// </summary>
/// <returns>A list of courses</returns>
public static List<Course> GetCourses()
{
return new List<Course> {
new Course() { CourseID = 1, Credit = 3, Days = 3 , Title = "Algorithms"},
new Course() { CourseID = 2, Credit = 5, Days = 5 , Title = "Data Structures"},
new Course() { CourseID = 3, Credit = 5, Days = 5 , Title = "Databases"}
};
}
#endregion
}
public class CoursesDataContext : IUpdatable
{
#region Members
private static List<Course> _courses;
#endregion
#region Properties
/// <summary>
/// Return the courses in a IQueryable format
/// </summary>
public IQueryable<Course> Courses
{
get
{
return _courses.AsQueryable();
}
}
#endregion
#region Ctor
/// <summary>
/// Construct a new CoursesDataContext object
/// </summary>
static CoursesDataContext()
{
_courses = Course.GetCourses();
}
#endregion
#region IUpdatable Members
public object CreateResource(string containerName, string fullTypeName)
{
// create the object using reflection
var objType = Type.GetType(fullTypeName);
var resourceToAdd = Activator.CreateInstance(objType);
// add the course to the courses in-memory list
_courses.Add((Course)resourceToAdd);
return resourceToAdd;
}
public void DeleteResource(object targetResource)
{
// remove the course form the courses list
_courses.Remove((Course)targetResource);
}
public object GetResource(IQueryable query, string fullTypeName)
{
object result = null;
var enumerator = query.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Current != null)
{
result = enumerator.Current;
break;
}
}
if (fullTypeName != null &&
!fullTypeName.Equals(result.GetType().FullName))
{
throw new DataServiceException();
}
return result;
}
public object GetValue(object targetResource, string propertyName)
{
// get the property info using reflection
var targetType = targetResource.GetType();
var targetProperty = targetType.GetProperty(propertyName);
// retrun the value of the property
return targetProperty.GetValue(targetResource, null);
}
public void SetValue(object targetResource, string propertyName, object propertyValue)
{
// get the property info using reflection
Type targetType = targetResource.GetType();
PropertyInfo property = targetType.GetProperty(propertyName);
// set the property value
property.SetValue(targetResource, propertyValue, null);
}
public object ResolveResource(object resource)
{
// nothing to do just return the resource
return resource;
}
public void SaveChanges()
{
// object in memory - do nothing
}
public object ResetResource(object resource)
{
var resourceType = resource.GetType();
// get the list of attributes of the type
object[] attributes = resourceType.GetCustomAttributes(true);
// get the data service key attribute if exists
var keyAttribute = attributes.First<object>(
new Func<object, bool>(
attribute => attribute.GetType().Equals(typeof(DataServiceKeyAttribute))));
if (keyAttribute != null)
{
var resourceKeyNames = ((DataServiceKeyAttribute)keyAttribute);
foreach (var propertyInfo in resourceType.GetProperties())
{
if (!resourceKeyNames.KeyNames.Contains(propertyInfo.Name))
{
// if the property name isn't part of the keys colletion
// then set it to default value
var propType = propertyInfo.PropertyType;
object defaultValue =
propType.IsValueType
? Activator.CreateInstance(propType)
: null;
propertyInfo.SetValue(resource, defaultValue, null);
}
}
return resource;
}
// no key
return new Course();
}
public void SetReference(object targetResource, string propertyName, object propertyValue)
{
throw new NotImplementedException();
}
public void ClearChanges()
{
throw new NotImplementedException();
}
public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
{
throw new NotImplementedException();
}
public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
{
throw new NotImplementedException();
}
#endregion
}
I made that change because in the update operation of Ajax client the method
resets the data in the resource in order to fill it with the updated data.
The data service code stays the same.
Performing CRUD Operations on Data Service Using Ajax Client
I added to the example from the last post more functionality in order to show
how to perform the CRUD operations. I added three buttons to the web form
I built and it’s code is now:
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Name="MicrosoftAjaxDataService.js" />
</Scripts>
</asp:ScriptManager>
<div>
<select id="ddlCourses">
</select>
<span id="spanError" style="display: none;"></span>
<br />
<input id="btnFill" type="button" value="Fill Drop Down List" onclick="Fill();" />
<input id="btnInsert" type="button" value="Insert Course" onclick="Create();" />
<input id="btnUpdate" type="button" value="Update Course" onclick="Update();" />
<input id="btnDelete" type="button" value="Delete Course" onclick="Delete();" />
</div>
<script type="text/javascript">
var proxy = new Sys.Data.DataService("/Services/CoursesService.svc");
function Fill() {
proxy.query("Courses", OnSuccess, OnFailure);
}
function OnSuccess(result, context, operation) {
if (result != null) {
var select = $get("ddlCourses");
var option;
for (i = 0; i < result.length; i++) {
option = new Option(result[i].Title, result[i].CourseID);
select.options.add(option);
}
}
}
function OnFailure(error, context, operation) {
var span = $get("spanError");
span.innerHTML = BuildErrorString(operation);
span.style.display = "inline";
}
function BuildErrorString(operation) {
var sb = new Sys.StringBuilder("Error occurred while performing operation ");
sb.append(operation);
sb.append(".");
return sb.toString();
}
function Clear() {
var select = $get("ddlCourses");
for (i = select.length; i >= 0; i--) {
select.remove(i);
}
}
function OnOperationSuccess(result, context, operation) {
if (result != null) {
Clear();
Fill();
}
}
function Create() {
var course = { Title: "Introduction to Computer science", Days: 5, Credit: 5 };
proxy.insert(course, "Courses", OnOperationSuccess, OnFailure);
}
function Update() {
proxy.query("Courses(1)", OnQueryForUpdateSuccess, OnFailure);
}
function Delete() {
proxy.remove(null, "Courses(1)", OnOperationSuccess, OnFailure);
}
function OnQueryForUpdateSuccess(result, context, operation) {
if (result != null) {
result.Title = "Introduction";
proxy.update(result, "Courses(1)", OnOperationSuccess, OnFailure);
}
}
</script>
</form>
Create
When we want to create an object and add it, we use the
Sys.Data.DataService object’s insert method.
The insert method gets six parameters:
proxy.insert(course, "Courses", OnOperationSuccess, OnFailure, context, true);
The parameters are:
- The inserted object
- The resource set Uri
- The success callback function
- The failure callback function
- The user context
- A flag to indicate if we want a return data which will return in the
success callback. The default value is true so you can omit that variable
if you want the inserted data to be returned.
Behind the scenes the HTTP POST method will be called to perform the create
operation. In my example the Create method creates a new course and add it to
the Courses list on the server.
Update
When we want to update a resource we need to have two things – the
resource that we want to update and the resource Uri. Without those two
you won’t be able to update the resource.
We use the Sys.Data.DataService object’s update method.
The update method gets the same six parameters like the insert method:
proxy.update(result, "Courses(1)", OnOperationSuccess, OnFailure, context, true);
Using the update method will cause an HTTP PUT request to the server.
In my example the Update method first searches a known resource and then
performs the update.
Delete
When we want to delete a resource we need to have one of two things -
the resource to delete or it’s resource Uri.
We use the Sys.Data.DataService object’s remove method.
The remove method take five parameters instead of six:
proxy.remove(course, "Courses(1)", OnOperationSuccess, OnFailure, context);
The parameters are:
- The removed object
- The resource set Uri of the removed object
- The success callback function
- The failure callback function
- The user context
The two parameters are exclusively optional and you can use the first alone,
the second alone or both of them.
Using the remove method will cause an HTTP DELETE request to the server.
In my example the Delete method removes the first resource from the list
on the server. If you’ll press the delete button again it will cause an exception
because that resource was omitted.
Summary
Lets sum up, in the post I explained how you can perform CRUD operations
from the client side using Ajax. These operations are part of the
Sys.Data.DataService’s API and are very useful if you want to manipulate
your data from client side. As I wrote, the operations are performed immediately
after you call them and therefore you don’t have a unit of work functionality.
In the next post in the series I’m going to show how to cache and submit the
operations you perform using an action sequence