Introduction
The goal of this post is to create a simple example of an application with an architecture domain-driven using Entity Framewrok 4.0 and POCO, the RIA services and Silverlight.
Installation Prerequisites
Microsoft Visual Studio 2010 Version 10.0.21006.1 B2Rel
Microsoft .NET Framework Version 4.0.21006 B2Rel
WCF RIA Services
Microsoft WCF RIA Services simplifies the traditional n-tier application pattern by bringing together the ASP.NET and Silverlight platforms. The RIA Services provides a pattern to write application logic that runs on the mid-tier and controls access to data for queries, changes and custom operations. It also provides end-to-end support for common tasks such as data validation, authentication and roles by integrating with Silverlight components on the client and ASP.NET on the mid-tier.
Silverlight 4 Beta Tools for Visual Studio 2010
This will install the developer runtime of Silverlight 4 Beta, the Visual Studio project support and the Silverlight 4 SDK. If you are developing Silverlight 4 Beta applications, this will be the minimum you want to install!
For more information: http://silverlight.net/getstarted/silverlight-4-beta/
Architecture Overview
Data Access Layer
The data access layer is simply constituted by a project of type "Class Library" which contains only the "ADO.NET Entity Data Model.
Business Layer
For the business layer, we create 2 projects of type "Class Library":
- "MyNamespace.Business" which contains the template that is to say the business objects, the data context and some interfaces of my repositories.
- "MyNamespace.Business.EF" which contains the "EntityFramework" implementation of my interfaces.
Service Layer
The service layer is a Web project that contains the "RIA Services," which is the point of entry for the Silverlight application.
UI Layer
The user interface consists of a Silverlight project with a .NET RIA Services to the Website.
Sample
In our example we use the "Northwind" database downloadable from:
http://www.microsoft.com/downloads/details.aspx?familyid=06616212-0356-46a0-8da2-eebc53a68034&displaylang=en
Data Layer
Creating a new "Class Library", then we add the "ADO.NET Entity Data Model" in our example Northwind.edmx. Then in order to use Entity Framework with POCO (Plain Old CLR Object) should remove the "Custom Tool" as:
In our case, we use only the table "Category" and "Product". Our model is as follows:
Business Layer
For the business layer we use 2 "Class Library":
- A "Class Library" which contains the interface and the model.
- A "Class Library" which contains the implementation EntityFramework (which might be of NHibernate, this will be the subject of an article soon)
Having the interface and implementation separately allows me to make an inversion of control (IoC), which allows me to decouple the dependencies between objects. (In my case I use Castle Windsor but we could use others)
Here is how to effectively present the business section:
Business interface
The business section is composed of "Repository" interfaces :
public interface IRepository<T>
{
IQueryable<T> GetQuery();
IEnumerable<T> GetAll();
IEnumerable<T> Find(Func<T, bool> where);
T Single(Func<T, bool> where);
T First(Func<T, bool> where);
void Delete(T entity);
void Add(T entity);
void Attach(T current);
void Attach(T current, T original);
void ApplyCurrentValues(T current);
void SaveChanges();
}
And the model is composed of a set of business objects presented as follows:
public class Category
{
public virtual int CategoryID { get; set; }
public virtual string CategoryName { get; set; }
public virtual string Description { get; set; }
public virtual Byte[] Picture { get; set; }
public virtual IList<Product> Products { get; set; }
public Category()
{
}
}
Business implementation
The implementation of business consists of implementing a context in Entity Framework:
public class NorthwindContext : ObjectContext
{
#region [Fields]
private ObjectSet<Category> _categories;
private ObjectSet<Product> _products;
#endregion
#region [Properties]
public ObjectSet<Category> Categories
{
get
{
return _categories;
}
}
public ObjectSet<Product> Products
{
get
{
return _products;
}
}
#endregion
#region [Constructors]
public NorthwindContext()
: base("name=NorthwindEntities", "NorthwindEntities")
{
_categories = CreateObjectSet<Category>();
_products = CreateObjectSet<Product>();
}
#endregion
}
It also contains the implementation of the "Repsitories" in Entity Framework:
public class EFRepository<T> : IRepository<T> where T : class
{
#region [Fields]
private ObjectContext _context;
private IObjectSet<T> _objectSet;
public ObjectContext Context
{
get
{
if (_context == null)
{
_context = new NorthwindContext();
}
return _context;
}
}
private IObjectSet<T> ObjectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this.Context.CreateObjectSet<T>();
}
return _objectSet;
}
}
#endregion
#region [Constructors]
public EFRepository(ObjectContext context)
{
_context = context;
}
#endregion
#region IRepository<T> Members
public IQueryable<T> GetQuery()
{
return ObjectSet;
}
public IEnumerable<T> GetAll()
{
return GetQuery().ToList();
}
public IEnumerable<T> Find(Func<T, bool> where)
{
return this.ObjectSet.Where<T>(where);
}
public T Single(Func<T, bool> where)
{
return this.ObjectSet.Single<T>(where);
}
public T First(Func<T, bool> where)
{
return this.ObjectSet.First<T>(where);
}
public void Delete(T entity)
{
this.ObjectSet.DeleteObject(entity);
}
public void Add(T entity)
{
this.ObjectSet.AddObject(entity);
}
public void ApplyCurrentValues(T current)
{
_context.ApplyCurrentValues<T>(typeof(T).Name, current);
}
public void Attach(T current)
{
this.ObjectSet.Attach(current);
}
public void Attach(T current, T original)
{
throw new NotImplementedException();
}
public void SaveChanges()
{
this.Context.SaveChanges();
}
#endregion
}
Service Layer
The service layer is a website containing the entry point for the Silverlight application, the RIA Services and a set of DTO (Data Transfer Object).
Here is an example of DTO:
[Serializable()]
public class CategoryDTO
{
[Key]
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public Byte[] Picture { get; set; }
public IList<ProductDTO> Products { get; set; }
public CategoryDTO()
{
}
}
And here is the corresponding “Domain Service”:
[EnableClientAccess()]
public class CategoryDomainService : DomainService
{
#region [Fields]
private ICategoryRepository CategoryRepository;
#endregion
#region [Constructors]
public CategoryDomainService()
{
IocContainer iocContainer = new IocContainer("DomainDriven.Business.cfg.xml");
CategoryRepository = iocContainer.Resolve<ICategoryRepository>();
}
#endregion
#region [Methods]
public List<CategoryDTO> GetCategories()
{
var query = from category in CategoryRepository.GetAll()
select new CategoryDTO()
{
CategoryID = category.CategoryID,
CategoryName = category.CategoryName,
Description = category.Description,
Picture = category.Picture
//Products = (from product in category.Products
// select new ProductDTO()
// {
// ProductID = product.ProductID,
// Discontinued = product.Discontinued,
// ProductName = product.ProductName,
// QuantityPerUnit = product.QuantityPerUnit,
// ReorderLevel = product.ReorderLevel,
// SupplierID = product.SupplierID,
// UnitPrice = product.UnitPrice,
// UnitsInStock = product.UnitsInStock,
// UnitsOnOrder = product.UnitsOnOrder,
// }).ToList()
};
if (query != null)
return query.ToList();
return null;
}
[Insert]
public void InsertCategory(CategoryDTO category)
{
CategoryRepository.Add(MapCategory(category));
}
[Update]
public void UpdateCategory(CategoryDTO category)
{
Category cat = MapCategory(category);
CategoryRepository.Attach(new Category { CategoryID = category.CategoryID });
CategoryRepository.ApplyCurrentValues(cat);
}
[Delete]
public void DeleteCategory(CategoryDTO category)
{
Category cat = new Category { CategoryID = category.CategoryID };
CategoryRepository.Attach(cat);
CategoryRepository.Delete(cat);
}
public override bool Submit(ChangeSet changeSet)
{
bool test = base.Submit(changeSet);
CategoryRepository.SaveChanges();
return test;
}
private Category MapCategory(CategoryDTO category)
{
return new Category()
{
CategoryID = category.CategoryID,
CategoryName = category.CategoryName,
Description = category.Description,
Picture = category.Picture
};
}
#endregion
}
There are just two things to note:
- The instantiation of my object "CategoryRepository" through Windsor Castle. (In the constructor)
- Overloading of method "Submit" which allows me to do "SaveChanges in the background.
UI Layer
The UI layer is relatively simple. I use the RIA controls in conventional manner. I let you discover the source code by yourself.
Here are the results:
Conclusion
The purpose of this post was here using the RIA Services in the context of a Domain Driven architecture containing a service layer and DTO. I’m waiting for your comments to improve this architecture.
You can download the full source code here: