Using EntityKey for Updates

Developer
Jun 29, 2010 at 10:07 PM
Edited Jun 29, 2010 at 10:24 PM

Hi, In one of his CSLA 3.8 core series video, Rocky shows a method for avoiding a roundtrip to the database when updating. Basically, he stores an extra Property on the BO named EntityKey.

This EntityKey is "generated" by EF, so when you want to update the BO, you can do something like this:

var data = ctx.ObjectContext.Customers.CreateObject();
WriteKeyData(data); // this method would have to be added, it only writes the Key properties to the entity 
data.EntityKey = Deserialize(ReadProperty(EntityKeyDataProperty)); //See below for Deserialize implementation 
ctx.ObjectContext.Attach(data);
BeforeUpdate(data); 
WriteNonKeyData(data) // this method needs to be added to write all the "Non-Key" properties 
ctx.ObjectContext.SaveChanges(); 
LoadDataToProperties(data); 
AfterUpdate(data);

//
//

private System.Data.EntityKey Deserialize(byte[] data) 
{
   using (var buffer = new System.IO.MemoryStream(data)) 
   { 
      var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 
      return formatter.Deserialize(buffer) as System.Data.EntityKey; 
   } 
}

 

You'd need to "split" the WriteData() method into two complementary methods: WriteKeyData() and WriteNonKeyData()

What do you think ?

Developer
Jun 30, 2010 at 1:24 AM
Edited Jun 30, 2010 at 1:31 AM

Josip,

I don't know how to attach files to these messages, so I'll simply post a diff file for what I was explaining in above message:

Index: CslaExtension.tt
===================================================================
--- CslaExtension.tt	(revision 51647)
+++ CslaExtension.tt	(working copy)
@@ -371,6 +371,19 @@
 
 		#region Properties
 <#+
+		if(!entity.IsItemReadonly)
+		{
+#>			
+		protected static PropertyInfo EntityKeyProperty = RegisterProperty(c => c.EntityKey);
+		public byte[] EntityKey
+		{
+			get { return GetProperty(EntityKeyProperty); }
+			set { SetProperty(EntityKeyProperty, value); }
+		}
+ <#+
+		}
+ #>
+ <#+
 		foreach (Property property in entity.Properties)
 		{
 			///
@@ -758,7 +771,22 @@
 		partial void BeforeReadData(<#= entity.FullName #> data);
 		partial void AfterReadData(<#= entity.FullName #> data);
 
+<#+
+		if(!entity.IsItemReadonly)
+		{
+#>
 		/// 
+		///
+		/// 
+		private void LoadEntityKey(<#= entity.FullName #> data)
+		{
+			//Load EntityKey
+			LoadProperty(EntityKeyProperty, Csla.Serialization.Mobile.MobileFormatter.Serialize(data.EntityKey));
+		}
+<#+
+		}
+#>
+		/// 
 		/// 
 		/// 
 		private void LoadDataToProperties(<#= entity.FullName #> data)
@@ -780,6 +808,100 @@
 		}
 <#+
 		///
+		/// WriteKeyData
+		/// 
+		if (!entity.IsItemReadonly)
+		{
+#>
+
+		/// 
+		/// 
+		/// 
+		private void WriteKeyData(<#= entity.FullName #> data)
+		{
+			BeforeWriteKeyData(data);
+			
+<#+
+		///
+		/// Read Properties values to data
+		/// 
+		foreach (Property property in entity.Properties)
+		{
+			if (property.IsKey && (property.HasGetter || property.HasSetter))
+			{
+				if (property.IsValueType && property.IsNullable)
+				{
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property)<#= property.IsDataNullable ? string.Empty : ".Value" #>;
+<#+
+				}
+				else
+				{						
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property);
+<#+
+				}
+			}
+		}
+#>
+
+			AfterWriteKeyData(data);
+		} // WriteKeyData()
+		
+		partial void BeforeWriteKeyData(<#= entity.FullName #> data);
+		partial void AfterWriteKeyData(<#= entity.FullName #> data);
+<#+
+		} // if (!entity.IsItemReadonly)
+#>
+<#+
+		///
+		/// WriteNonKeyData
+		/// 
+		if (!entity.IsItemReadonly)
+		{
+#>
+
+		/// 
+		/// 
+		/// 
+		private void WriteNonKeyData(<#= entity.FullName #> data)
+		{
+			BeforeWriteNonKeyData(data);
+			
+<#+
+		///
+		/// Read Properties values to data
+		/// 
+		foreach (Property property in entity.Properties)
+		{
+			if (!property.IsKey && (property.HasGetter || property.HasSetter))
+			{
+				if (property.IsValueType && property.IsNullable)
+				{
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property)<#= property.IsDataNullable ? string.Empty : ".Value" #>;
+<#+
+				}
+				else
+				{						
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property);
+<#+
+				}
+			}
+		}
+#>
+
+			AfterWriteNonKeyData(data);
+		} // WriteNonKeyData()
+		
+		partial void BeforeWriteNonKeyData(<#= entity.FullName #> data);
+		partial void AfterWriteNonKeyData(<#= entity.FullName #> data);
+<#+
+		} // if (!entity.IsItemReadonly)
+#>
+<#+
+		///
 		/// WriteData
 		/// 
 		if (!entity.IsItemReadonly)
@@ -871,6 +993,15 @@
 		{
 			BeforeFetch(data);
 			ReadData(data);
+<#+
+		if (!entity.IsItemReadonly)
+		{
+		
+#>
+			LoadEntityKey(data);
+<#+
+		}
+#>
 			AfterFetch(data);				
 		}			
 		partial void BeforeFetch(<#= entity.FullName #> data);
@@ -890,7 +1021,8 @@
 				BeforeInsert(data);					
 				WriteData(data);					
 				ctx.ObjectContext.<#= entity.SetName #>.AddObject(data);					
-				ctx.ObjectContext.SaveChanges();					
+				ctx.ObjectContext.SaveChanges();
+				LoadEntityKey(data);
 				LoadDataToProperties(data);					
 				AfterInsert(data);					
 				FieldManager.UpdateChildren();
@@ -906,10 +1038,14 @@
 			{
 				if (this.IsSelfDirty)
 				{
-					var data = ctx.ObjectContext.<#= entity.SetName #>.Single(<#= entity.ThisQueryLambdaExp #>);
+					var data = ctx.ObjectContext.<#= entity.SetName #>.CreateObject();
+					WriteKeyData(data);
+					data.EntityKey = Csla.Serialization.Mobile.MobileFormatter.Deserialize(ReadProperty(EntityKeyProperty)) as System.Data.EntityKey;
+					ctx.ObjectContext.Attach(data);
 					BeforeUpdate(data);					
-					WriteData(data);
+					WriteNonKeyData(data);
 					ctx.ObjectContext.SaveChanges();
+					LoadEntityKey(data);
 					LoadDataToProperties(data);
 					AfterUpdate(data);
 				}
Coordinator
Jun 30, 2010 at 7:23 AM
Hi Luc, glad to hear from You again. Interesting stuff as always. I will check your code to see how it fits in our design. Thanks!
Developer
Jun 30, 2010 at 10:26 AM

Josip,

I think I have applied my changes to the wrong version of the CslaExtension.tt file. I was working on the one in the CslaExtensionDemo.Library project, and I think it's different than the one in CslaExtension.Template project.

Can you confirm which one is the actual development version ?

Thanks

Coordinator
Jun 30, 2010 at 10:29 AM
The actual version is in the CslaExtension.Template project. When finished, we apply the changes to the Demo project.
Developer
Jun 30, 2010 at 10:41 AM

Ok, I'll modify the one in the CslaExtension.Template project, and re-send the patch

Developer
Jun 30, 2010 at 10:54 AM

Here is patch to apply against CslaExtension.tt in CslaExtension.Template project:

 

Index: CslaGenerator.tt
===================================================================
--- CslaGenerator.tt	(revision 51647)
+++ CslaGenerator.tt	(working copy)
@@ -371,6 +371,19 @@
 
 		#region Properties
 <#+
+		if(!entity.IsItemReadonly)
+		{
+#>			
+		protected static PropertyInfo EntityKeyProperty = RegisterProperty(c => c.EntityKey);
+		public byte[] EntityKey
+		{
+			get { return GetProperty(EntityKeyProperty); }
+			set { SetProperty(EntityKeyProperty, value); }
+		}
+ <#+
+		}
+ #>
+ <#+
 		foreach (Property property in entity.Properties)
 		{
 			///
@@ -773,7 +786,23 @@
 		partial void BeforeReadData(<#= entity.FullName #> data);
 		partial void AfterReadData(<#= entity.FullName #> data);
 
+<#+
+		if(!entity.IsItemReadonly)
+		{
+#>
 		/// 
+		///
+		/// 
+		private void LoadEntityKey(<#= entity.FullName #> data)
+		{
+			//Load EntityKey
+			LoadProperty(EntityKeyProperty, Csla.Serialization.Mobile.MobileFormatter.Serialize(data.EntityKey));
+		}
+<#+
+		}
+#>
+
+		/// 
 		/// 
 		/// 
 		private void LoadDataToProperties(<#= entity.FullName #> data)
@@ -795,6 +824,100 @@
 		}
 <#+
 		///
+		/// WriteKeyData
+		/// 
+		if (!entity.IsItemReadonly)
+		{
+#>
+
+		/// 
+		/// 
+		/// 
+		private void WriteKeyData(<#= entity.FullName #> data)
+		{
+			BeforeWriteKeyData(data);
+			
+<#+
+		///
+		/// Read Properties values to data
+		/// 
+		foreach (Property property in entity.Properties)
+		{
+			if (property.IsKey && (property.HasGetter || property.HasSetter))
+			{
+				if (property.IsValueType && property.IsNullable)
+				{
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property)<#= property.IsDataNullable ? string.Empty : ".Value" #>;
+<#+
+				}
+				else
+				{						
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property);
+<#+
+				}
+			}
+		}
+#>
+
+			AfterWriteKeyData(data);
+		} // WriteKeyData()
+		
+		partial void BeforeWriteKeyData(<#= entity.FullName #> data);
+		partial void AfterWriteKeyData(<#= entity.FullName #> data);
+<#+
+		} // if (!entity.IsItemReadonly)
+#>
+<#+
+		///
+		/// WriteNonKeyData
+		/// 
+		if (!entity.IsItemReadonly)
+		{
+#>
+
+		/// 
+		/// 
+		/// 
+		private void WriteNonKeyData(<#= entity.FullName #> data)
+		{
+			BeforeWriteNonKeyData(data);
+			
+<#+
+		///
+		/// Read Properties values to data
+		/// 
+		foreach (Property property in entity.Properties)
+		{
+			if (!property.IsKey && (property.HasGetter || property.HasSetter))
+			{
+				if (property.IsValueType && property.IsNullable)
+				{
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property)<#= property.IsDataNullable ? string.Empty : ".Value" #>;
+<#+
+				}
+				else
+				{						
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property);
+<#+
+				}
+			}
+		}
+#>
+
+			AfterWriteNonKeyData(data);
+		} // WriteNonKeyData()
+		
+		partial void BeforeWriteNonKeyData(<#= entity.FullName #> data);
+		partial void AfterWriteNonKeyData(<#= entity.FullName #> data);
+<#+
+		} // if (!entity.IsItemReadonly)
+#>
+<#+
+		///
 		/// WriteData
 		/// 
 		if (!entity.IsItemReadonly)
@@ -886,6 +1009,15 @@
 		{
 			BeforeFetch(data);
 			ReadData(data);
+<#+
+		if (!entity.IsItemReadonly)
+		{
+		
+#>
+			LoadEntityKey(data);
+<#+
+		}
+#>
 			AfterFetch(data);				
 		}			
 		partial void BeforeFetch(<#= entity.FullName #> data);
@@ -906,6 +1038,7 @@
 				WriteData(data);					
 				ctx.ObjectContext.<#= entity.SetName #>.AddObject(data);					
 				ctx.ObjectContext.SaveChanges();					
+				LoadEntityKey(data);
 				LoadDataToProperties(data);					
 				AfterInsert(data);					
 				FieldManager.UpdateChildren();
@@ -921,10 +1054,14 @@
 			{
 				if (this.IsSelfDirty)
 				{
-					var data = ctx.ObjectContext.<#= entity.SetName #>.Single(<#= entity.ThisQueryLambdaExp #>);
+					var data = ctx.ObjectContext.<#= entity.SetName #>.CreateObject();
+					WriteKeyData(data);
+					data.EntityKey = Csla.Serialization.Mobile.MobileFormatter.Deserialize(ReadProperty(EntityKeyProperty)) as System.Data.EntityKey;
+					ctx.ObjectContext.Attach(data);
 					BeforeUpdate(data);					
-					WriteData(data);
+					WriteNonKeyData(data);
 					ctx.ObjectContext.SaveChanges();
+					LoadEntityKey(data);
 					LoadDataToProperties(data);
 					AfterUpdate(data);
 				}
Developer
Jun 30, 2010 at 2:00 PM

Hi,

Upon further testing, it seems I can't use the MobileFormatter in this way.

Let me work on this some more and I'll post back.

Sorry.

 

Coordinator
Jun 30, 2010 at 2:25 PM
No problem. Thanks for your effort. I have some ideas how to solve this problem, will see what can be done. ~Josip
Developer
Jun 30, 2010 at 3:18 PM

Ok, here's a new attempt at it.

Basically, I now have 2 new methods WriteEntityKey() and LoadEntityKey() that encapsulate the Serialization/Deserialization of the EntityKey to/from byte[] (though I don't quite understand why Rocky uses byte[] instead of simply using System.Data.EntityKey).

Notice that I have split the WriteData() method in 2 methods, WriteKeyData() and WriteNonKeyData(). The reason is that when updating an entity with EF, we need to set its "Key" properties before attaching it to the context. Then, after attaching to context, we can write all the "non key" properties.

Here's the updated diff:

Index: CslaGenerator.tt
===================================================================
--- CslaGenerator.tt	(revision 51647)
+++ CslaGenerator.tt	(working copy)
@@ -371,6 +371,19 @@
 
 		#region Properties
 <#+
+		if(!entity.IsItemReadonly)
+		{
+#>			
+		protected static PropertyInfo EntityKeyProperty = RegisterProperty(c => c.EntityKey);
+		public byte[] EntityKey
+		{
+			get { return GetProperty(EntityKeyProperty); }
+			set { SetProperty(EntityKeyProperty, value); }
+		}
+ <#+
+		}
+ #>
+ <#+
 		foreach (Property property in entity.Properties)
 		{
 			///
@@ -773,7 +786,41 @@
 		partial void BeforeReadData(<#= entity.FullName #> data);
 		partial void AfterReadData(<#= entity.FullName #> data);
 
+<#+
+		if(!entity.IsItemReadonly)
+		{
+#>
 		/// 
+		///
+		/// 
+		private void WriteEntityKey(<#= entity.FullName #> data)
+		{
+			//Read EntityKey
+			using (var buffer = new System.IO.MemoryStream(ReadProperty(EntityKeyProperty)))
+            {
+                var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
+                data.EntityKey = formatter.Deserialize(buffer) as System.Data.EntityKey;
+            }
+		}
+		
+		/// 
+		///
+		/// 
+		private void LoadEntityKey(<#= entity.FullName #> data)
+		{
+			//Load EntityKey
+			using (var buffer = new System.IO.MemoryStream())
+            {
+                var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
+                formatter.Serialize(buffer, data.EntityKey);
+                LoadProperty(EntityKeyProperty, buffer.ToArray());
+            }
+		}
+<#+
+		}
+#>
+
+		/// 
 		/// 
 		/// 
 		private void LoadDataToProperties(<#= entity.FullName #> data)
@@ -795,6 +842,100 @@
 		}
 <#+
 		///
+		/// WriteKeyData
+		/// 
+		if (!entity.IsItemReadonly)
+		{
+#>
+
+		/// 
+		/// 
+		/// 
+		private void WriteKeyData(<#= entity.FullName #> data)
+		{
+			BeforeWriteKeyData(data);
+			
+<#+
+		///
+		/// Read Properties values to data
+		/// 
+		foreach (Property property in entity.Properties)
+		{
+			if (property.IsKey && (property.HasGetter || property.HasSetter))
+			{
+				if (property.IsValueType && property.IsNullable)
+				{
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property)<#= property.IsDataNullable ? string.Empty : ".Value" #>;
+<#+
+				}
+				else
+				{						
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property);
+<#+
+				}
+			}
+		}
+#>
+
+			AfterWriteKeyData(data);
+		} // WriteKeyData()
+		
+		partial void BeforeWriteKeyData(<#= entity.FullName #> data);
+		partial void AfterWriteKeyData(<#= entity.FullName #> data);
+<#+
+		} // if (!entity.IsItemReadonly)
+#>
+<#+
+		///
+		/// WriteNonKeyData
+		/// 
+		if (!entity.IsItemReadonly)
+		{
+#>
+
+		/// 
+		/// 
+		/// 
+		private void WriteNonKeyData(<#= entity.FullName #> data)
+		{
+			BeforeWriteNonKeyData(data);
+			
+<#+
+		///
+		/// Read Properties values to data
+		/// 
+		foreach (Property property in entity.Properties)
+		{
+			if (!property.IsKey && (property.HasGetter || property.HasSetter))
+			{
+				if (property.IsValueType && property.IsNullable)
+				{
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property)<#= property.IsDataNullable ? string.Empty : ".Value" #>;
+<#+
+				}
+				else
+				{						
+#>
+			data.<#= property.Name #> = ReadProperty<<#= property.DataTypeName #>>(<#= property.Name #>Property);
+<#+
+				}
+			}
+		}
+#>
+
+			AfterWriteNonKeyData(data);
+		} // WriteNonKeyData()
+		
+		partial void BeforeWriteNonKeyData(<#= entity.FullName #> data);
+		partial void AfterWriteNonKeyData(<#= entity.FullName #> data);
+<#+
+		} // if (!entity.IsItemReadonly)
+#>
+<#+
+		///
 		/// WriteData
 		/// 
 		if (!entity.IsItemReadonly)
@@ -886,6 +1027,15 @@
 		{
 			BeforeFetch(data);
 			ReadData(data);
+<#+
+		if (!entity.IsItemReadonly)
+		{
+		
+#>
+			LoadEntityKey(data);
+<#+
+		}
+#>
 			AfterFetch(data);				
 		}			
 		partial void BeforeFetch(<#= entity.FullName #> data);
@@ -906,6 +1056,7 @@
 				WriteData(data);					
 				ctx.ObjectContext.<#= entity.SetName #>.AddObject(data);					
 				ctx.ObjectContext.SaveChanges();					
+				LoadEntityKey(data);
 				LoadDataToProperties(data);					
 				AfterInsert(data);					
 				FieldManager.UpdateChildren();
@@ -921,10 +1072,14 @@
 			{
 				if (this.IsSelfDirty)
 				{
-					var data = ctx.ObjectContext.<#= entity.SetName #>.Single(<#= entity.ThisQueryLambdaExp #>);
+					var data = ctx.ObjectContext.<#= entity.SetName #>.CreateObject();
+					WriteKeyData(data);
+					WriteEntityKey(data);
+					ctx.ObjectContext.Attach(data);
 					BeforeUpdate(data);					
-					WriteData(data);
+					WriteNonKeyData(data);
 					ctx.ObjectContext.SaveChanges();
+					LoadEntityKey(data);
 					LoadDataToProperties(data);
 					AfterUpdate(data);
 				}
Developer
Jun 30, 2010 at 6:05 PM
Edited Jun 30, 2010 at 6:06 PM

See Rocky's reply as to why he uses byte[]:

http://forums.lhotka.net/forums/t/9151.aspx

Coordinator
Jun 30, 2010 at 6:42 PM
Excellent. I will add you as a contributor to this project.
Developer
Jun 30, 2010 at 7:11 PM

Thanks Josip,

This project is something I've been "dreaming" of for quite a while now.

Please let me know what needs to be worked on.

Luc