LINQ and N-Tier support for disconnected entities
Posted @ Apr 23, 2008 09:11 PM | Permalink
I've been playing with LINQ for a while and one of the major issues I encountered was the support of disconnected updates for N-Tier scenario. It seems that LINQ was designed to be tightly coupled with the UI (well this is what I think). And, most of the samples found in the Internet were using LINQ in the UI which is only good for demo purposes. What is really missing is a good example for disconnected updates or others call it "disconnected entities" for N-Tier.

Here are some workarounds or solutions that others came up:

1. Add a time stamp field in your table.

2. Create a base class for your entities with a method that will clone the properties.

Solution #1 is a hassle. 50 tables means 50 additional fields not to mention you also have to setup your entities in your dbml file. I don't want to do this one by one, do you? The second solution is kind of neat but the Reflection part is something that I am hesitant about but for now, since there is no other way, this is the best solution. By the way, if you have a better solution then I would really love to hear about it.

This is what my base class looks like with basic CRUD functionalities:

1
2 public abstract class ProviderBase<TEntity>
3 where TEntity : class
4 {
5 /// <summary>
6 /// Define the lamda expression for fetching
7 /// single obejcts.
8 /// </summary>
9 protected abstract Expression<Func<TEntity, bool>>
10 PredicateExp(params object[] keys);
11
12 public virtual void Insert(TEntity entity) {
13 using (DataContext db = DataContext.Instance()) {
14 db.GetTable<TEntity>().InsertOnSubmit(entity);
15 db.SubmitChanges();
16 }
17 }
18
19 public virtual void Update(
20 TEntity entity,
21 TEntity originalEntity
22 ) {
23 using (DataContext db = DataContext.Instance()) {
24 db.GetTable<TEntity>().Attach(entity, originalEntity);
25 db.SubmitChanges();
26 }
27 }
28
29 public virtual void Delete(object id) {
30 TEntity entity = this.Get(id);
31
32 using (DataContext db = DataContext.Instance()) {
33 db.GetTable<TEntity>().DeleteOnSubmit(entity);
34 db.SubmitChanges();
35 }
36 }
37
38 public virtual TEntity Get(object id) {
39 using (DataContext db = DataContext.Instance()) {
40 return db.GetTable<TEntity>().Single(PredicateExp(id));
41 }
42 }
43
44 public virtual IList<TEntity> GetList() {
45 using (DataContext db = DataContext.Instance()) {
46 var q = from t in db.GetTable<TEntity>()
47 select t;
48
49 return q.ToList();
50 }
51 }
52 }
53
54 public class DataUtils
55 {
56 public static TEntity CloneEntity<TEntity>(
57 TEntity updatedEntity
58 ) {
59 Type entityType = updatedEntity.GetType();
60 object instance = Activator.CreateInstance(entityType);
61
62 PropertyInfo[] properties = entityType.GetProperties();
63
64 foreach (PropertyInfo property in properties) {
65 object value = property.GetValue(updatedEntity, null);
66
67 PropertyInfo propertyToSet =
68 instance.GetType().GetProperty(property.Name);
69
70 if (property.PropertyType.IsGenericType) {
71 Type genericType =
72 property.PropertyType.GetGenericTypeDefinition();
73
74 // Skip EntitySet<> types at the moment
75 if (genericType == typeof(EntitySet<>)) {
76 continue;
77 }
78 }
79
80 propertyToSet.SetValue(instance, value, null);
81 }
82
83 return (TEntity)instance;
84 }
85 }
86


To use it just inherit from ProviderBase and specify the entity that you are going to use. Also set up the PredicateExp for your PK lookup. For now, the PredicateExp only supports tables with 1 primary key but you can always improve it if you want to. Sample code below:

1
2 public class Employee : ProviderBase<EmployeeEntity>
3 {
4 protected override Expression<Func<EmployeeEntity, bool>>
5 PredicateExp(params object[] keys) {
6 return x => x.ID == Convert.ToInt32(keys[0]);
7 }
8 }
9


And here is a sample code on how to update an entity:

1
2 Employee employee = new Employee();
3
4 EmployeeEntity originalEmployeeEntity = employee.Get(employeeID);
5 EmployeeEntity employeeEntity =
6 DataUtils.CloneEntity<EmployeeEntity>(originalEmployeeEntity);
7
8 // update values
9 employeeEntity.FirstName = "Chris Adrian";
10 employeeEntity.Ongsuco = "Ongsuco";
11 employeeEntity.Birthday = new DateTime(1977, 12, 14);
12
13 // and so on....
14
15 employee.Update(
16 employeeEntity,
17 originalEmployeeEntity
18 );
19


The fetching and cloning part is only for supporting N-Tier or "disconnected entities". If you want to code LINQ in your UI (tightly coupled) then you don't need to clone anything.

Hope this helps.


5 Comments

Rod
Apr 24, 2008 07:21 AM
Great provider base class! Galing chris!
Sorenaa
Oct 15, 2008 03:00 AM
DataContext.Instance()
DataContext class does not have "Instance()" method !
Chris Ongsuco
Oct 15, 2008 04:45 AM
Hi Sorenaa,

DataContext is a concrete class that I created with a static method, in this case, Instance().
Sorenaa
Oct 15, 2008 06:00 AM
Thanks for reply
It's OK, but now, I receive "Cannot add an entity with a key that is already in use." error in update method. How can i solve this?
Andre
Nov 30, 2008 09:04 AM
Brilliant! Just what I was after.

Thanks for sharing.

Leave a Comment

(optional)

4 + 3 =
Enter the sum of the 2 numbers above.