From 45960f1d70c0cefd7ca4db6dfa0e585ecfb193c4 Mon Sep 17 00:00:00 2001 From: Stephen Reindl Date: Sun, 26 May 2013 17:33:07 +0200 Subject: [PATCH 1/4] Fix for #24: Allow batch updates for ComplexType property This fix is limited to one level of ComplexType types. --- .../Batch/SqlServerBatchRunner.cs | 226 ++++++++++-------- .../Mapping/ComplexPropertyMap.cs | 22 ++ .../Mapping/EntityMap.cs | 6 +- .../Mapping/IPropertyMapElement.cs | 19 ++ .../Mapping/PropertyMap.cs | 2 +- .../Mapping/ReflectionMappingProvider.cs | 54 ++++- 6 files changed, 218 insertions(+), 111 deletions(-) create mode 100644 Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs create mode 100644 Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs diff --git a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs index 37c09b8..a257537 100644 --- a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs +++ b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.EntityClient; using System.Data.Objects; +using System.Diagnostics; using System.Linq; using System.Linq.Dynamic; using System.Linq.Expressions; @@ -170,112 +172,28 @@ public int Update(ObjectContext objectContext, EntityMap entityMap, Obj bool wroteSet = false; foreach (MemberBinding binding in memberInitExpression.Bindings) { - if (wroteSet) - sqlBuilder.AppendLine(", "); - - string propertyName = binding.Member.Name; - string columnName = entityMap.PropertyMaps - .Where(p => p.PropertyName == propertyName) - .Select(p => p.ColumnName) - .FirstOrDefault(); - - - var memberAssignment = binding as MemberAssignment; - if (memberAssignment == null) - throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression"); - - Expression memberExpression = memberAssignment.Expression; - - ParameterExpression parameterExpression = null; - memberExpression.Visit((ParameterExpression p) => - { - if (p.Type == entityMap.EntityType) - parameterExpression = p; - - return p; - }); - - - if (parameterExpression == null) + IPropertyMapElement propertyMap = + entityMap.PropertyMaps.SingleOrDefault(p => p.PropertyName == binding.Member.Name); + if (propertyMap is ComplexPropertyMap) { - object value; - - if (memberExpression.NodeType == ExpressionType.Constant) - { - var constantExpression = memberExpression as ConstantExpression; - if (constantExpression == null) - throw new ArgumentException( - "The MemberAssignment expression is not a ConstantExpression.", "updateExpression"); - - value = constantExpression.Value; - } - else - { - LambdaExpression lambda = Expression.Lambda(memberExpression, null); - value = lambda.Compile().DynamicInvoke(); - } - - if (value != null) - { - string parameterName = "p__update__" + nameCount++; - var parameter = updateCommand.CreateParameter(); - parameter.ParameterName = parameterName; - parameter.Value = value; - updateCommand.Parameters.Add(parameter); - - sqlBuilder.AppendFormat("[{0}] = @{1}", columnName, parameterName); - } - else + ComplexPropertyMap cpm = propertyMap as ComplexPropertyMap; + var memberAssignment = binding as MemberAssignment; + if (memberAssignment == null) + throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression"); + var expr = memberAssignment.Expression as MemberInitExpression; + if (expr == null) + throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression"); + foreach (var subBinding in expr.Bindings) { - sqlBuilder.AppendFormat("[{0}] = NULL", columnName); + AddUpdateRow(objectContext, entityMap, subBinding, sqlBuilder, updateCommand, + cpm.TypeElements, ref nameCount, ref wroteSet); } } else { - // create clean objectset to build query from - var objectSet = objectContext.CreateObjectSet(); - - Type[] typeArguments = new[] { entityMap.EntityType, memberExpression.Type }; - - ConstantExpression constantExpression = Expression.Constant(objectSet); - LambdaExpression lambdaExpression = Expression.Lambda(memberExpression, parameterExpression); - - MethodCallExpression selectExpression = Expression.Call( - typeof(Queryable), - "Select", - typeArguments, - constantExpression, - lambdaExpression); - - // create query from expression - var selectQuery = objectSet.CreateQuery(selectExpression, entityMap.EntityType); - string sql = selectQuery.ToTraceString(); - - // parse select part of sql to use as update - string regex = @"SELECT\s*\r\n(?.+)?\s*AS\s*(?\[\w+\])\r\nFROM\s*(?\[\w+\]\.\[\w+\]|\[\w+\])\s*AS\s*(?\[\w+\])"; - Match match = Regex.Match(sql, regex); - if (!match.Success) - throw new ArgumentException("The MemberAssignment expression could not be processed.", "updateExpression"); - - string value = match.Groups["ColumnValue"].Value; - string alias = match.Groups["TableAlias"].Value; - - value = value.Replace(alias + ".", ""); - - foreach (ObjectParameter objectParameter in selectQuery.Parameters) - { - string parameterName = "p__update__" + nameCount++; - - var parameter = updateCommand.CreateParameter(); - parameter.ParameterName = parameterName; - parameter.Value = objectParameter.Value; - updateCommand.Parameters.Add(parameter); - - value = value.Replace(objectParameter.Name, parameterName); - } - sqlBuilder.AppendFormat("[{0}] = {1}", columnName, value); + AddUpdateRow(objectContext, entityMap, binding, sqlBuilder, updateCommand, + entityMap.PropertyMaps, ref nameCount, ref wroteSet); } - wroteSet = true; } sqlBuilder.AppendLine(" "); @@ -316,6 +234,116 @@ public int Update(ObjectContext objectContext, EntityMap entityMap, Obj } } + + private static void AddUpdateRow(ObjectContext objectContext, EntityMap entityMap, MemberBinding binding, StringBuilder sqlBuilder, DbCommand updateCommand, IEnumerable propertyMap, ref int nameCount, ref bool wroteSet) + where TEntity : class + { + if (wroteSet) + sqlBuilder.AppendLine(", "); + + string propertyName = binding.Member.Name; + PropertyMap property = + propertyMap.SingleOrDefault(p => p.PropertyName == propertyName) as PropertyMap; + Debug.Assert(property != null, "property != null"); + string columnName = property.ColumnName; + var memberAssignment = binding as MemberAssignment; + if (memberAssignment == null) + throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "binding"); + + Expression memberExpression = memberAssignment.Expression; + + ParameterExpression parameterExpression = null; + memberExpression.Visit((ParameterExpression p) => + { + if (p.Type == entityMap.EntityType) + parameterExpression = p; + + return p; + }); + + + if (parameterExpression == null) + { + object value; + + if (memberExpression.NodeType == ExpressionType.Constant) + { + var constantExpression = memberExpression as ConstantExpression; + if (constantExpression == null) + throw new ArgumentException( + "The MemberAssignment expression is not a ConstantExpression.", "binding"); + + value = constantExpression.Value; + } + else + { + LambdaExpression lambda = Expression.Lambda(memberExpression, null); + value = lambda.Compile().DynamicInvoke(); + } + + if (value != null) + { + string parameterName = "p__update__" + nameCount++; + var parameter = updateCommand.CreateParameter(); + parameter.ParameterName = parameterName; + parameter.Value = value; + updateCommand.Parameters.Add(parameter); + + sqlBuilder.AppendFormat("[{0}] = @{1}", columnName, parameterName); + } + else + { + sqlBuilder.AppendFormat("[{0}] = NULL", columnName); + } + } + else + { + // create clean objectset to build query from + var objectSet = objectContext.CreateObjectSet(); + + Type[] typeArguments = new[] { entityMap.EntityType, memberExpression.Type }; + + ConstantExpression constantExpression = Expression.Constant(objectSet); + LambdaExpression lambdaExpression = Expression.Lambda(memberExpression, parameterExpression); + + MethodCallExpression selectExpression = Expression.Call( + typeof(Queryable), + "Select", + typeArguments, + constantExpression, + lambdaExpression); + + // create query from expression + var selectQuery = objectSet.CreateQuery(selectExpression, entityMap.EntityType); + string sql = selectQuery.ToTraceString(); + + // parse select part of sql to use as update + const string regex = @"SELECT\s*\r\n(?.+)?\s*AS\s*(?\[\w+\])\r\nFROM\s*(?\[\w+\]\.\[\w+\]|\[\w+\])\s*AS\s*(?\[\w+\])"; + Match match = Regex.Match(sql, regex); + if (!match.Success) + throw new ArgumentException("The MemberAssignment expression could not be processed.", "binding"); + + string value = match.Groups["ColumnValue"].Value; + string alias = match.Groups["TableAlias"].Value; + + value = value.Replace(alias + ".", ""); + + foreach (ObjectParameter objectParameter in selectQuery.Parameters) + { + string parameterName = "p__update__" + nameCount++; + + var parameter = updateCommand.CreateParameter(); + parameter.ParameterName = parameterName; + parameter.Value = objectParameter.Value; + updateCommand.Parameters.Add(parameter); + + value = value.Replace(objectParameter.Name, parameterName); + } + sqlBuilder.AppendFormat("[{0}] = {1}", columnName, value); + } + wroteSet = true; + } + private static Tuple GetStore(ObjectContext objectContext) { DbConnection dbConnection = objectContext.Connection; diff --git a/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs b/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs new file mode 100644 index 0000000..de3d29e --- /dev/null +++ b/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFramework.Mapping +{ + /// + /// A property map element representing a complex class + /// + public class ComplexPropertyMap: IPropertyMapElement + { + + public string PropertyName { get; set; } + + /// + /// The enumeration of the complex' type + /// + public ICollection TypeElements { get; set; } + } +} diff --git a/Source/EntityFramework.Extended/Mapping/EntityMap.cs b/Source/EntityFramework.Extended/Mapping/EntityMap.cs index 0310835..6592ce6 100644 --- a/Source/EntityFramework.Extended/Mapping/EntityMap.cs +++ b/Source/EntityFramework.Extended/Mapping/EntityMap.cs @@ -20,7 +20,7 @@ public EntityMap(Type entityType) { _entityType = entityType; _keyMaps = new List(); - _propertyMaps = new List(); + _propertyMaps = new List(); } /// @@ -57,11 +57,11 @@ public Type EntityType /// public string TableName { get; set; } - private readonly List _propertyMaps; + private readonly List _propertyMaps; /// /// Gets the property maps. /// - public List PropertyMaps + public List PropertyMaps { get { return _propertyMaps; } } diff --git a/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs b/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs new file mode 100644 index 0000000..c51e431 --- /dev/null +++ b/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFramework.Mapping +{ + /// + /// Interface representing a property map element which can be single or complex + /// + public interface IPropertyMapElement + { + /// + /// Gets or sets the name of the property. + /// + string PropertyName { get; set; } + } +} diff --git a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs index abe0d78..932b0c8 100644 --- a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs +++ b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs @@ -6,7 +6,7 @@ namespace EntityFramework.Mapping /// A class representing a property map /// [DebuggerDisplay("Property: {PropertyName}, Column: {ColumnName}")] - public class PropertyMap + public class PropertyMap: IPropertyMapElement { /// /// Gets or sets the name of the property. diff --git a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs index ac4487f..09c1fe2 100644 --- a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs +++ b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Data.Metadata.Edm; using System.Data.Objects; using System.Linq; @@ -139,11 +141,15 @@ private static void SetKeys(EntityMap entityMap) var property = entityMap.PropertyMaps.FirstOrDefault(p => p.PropertyName == edmMember.Name); if (property == null) continue; - + if (!(property is PropertyMap)) + { + throw new InvalidOperationException(string.Format("KeyMember {1} of entity {0} cannot be complex.", + entityMap.TableName, edmMember.Name)); + } var map = new PropertyMap { PropertyName = property.PropertyName, - ColumnName = property.ColumnName + ColumnName = ((PropertyMap)property).ColumnName }; entityMap.KeyMaps.Add(map); } @@ -156,17 +162,49 @@ private static void SetProperties(EntityMap entityMap, dynamic mappingFragmentPr { // StorageScalarPropertyMapping dynamic propertyMapProxy = new DynamicProxy(propertyMap); - EdmProperty modelProperty = propertyMapProxy.EdmProperty; EdmProperty storeProperty = propertyMapProxy.ColumnProperty; + ICollection typeMappings = propertyMapProxy.TypeMappings; - var map = new PropertyMap + // use this "ugly" way of type detection as the types + // are internal + if (propertyMap.GetType().Name == "StorageScalarPropertyMapping") { - ColumnName = storeProperty.Name, - PropertyName = modelProperty.Name - }; - entityMap.PropertyMaps.Add(map); + var map = new PropertyMap + { + ColumnName = storeProperty.Name, + PropertyName = modelProperty.Name + }; + + entityMap.PropertyMaps.Add(map); + } + else if (propertyMap.GetType().Name == "StorageComplexPropertyMapping") + { + var map = new ComplexPropertyMap + { + PropertyName = modelProperty.Name, + TypeElements = new List() + }; + foreach (var typeMapping in typeMappings) + { + dynamic typeMappingProxy = new DynamicProxy(typeMapping); + foreach (var property in typeMappingProxy.AllProperties) + { + dynamic pMap = new DynamicProxy(property); + modelProperty = pMap.EdmProperty; + storeProperty = pMap.ColumnProperty; + + var item = new PropertyMap + { + ColumnName = storeProperty.Name, + PropertyName = modelProperty.Name + }; + map.TypeElements.Add(item); + } + } + entityMap.PropertyMaps.Add(map); + } } } From 20cb4fa4affae2a1a4d7ab16e76f309eaeb2c22e Mon Sep 17 00:00:00 2001 From: Stephen Reindl Date: Sun, 26 May 2013 22:41:47 +0200 Subject: [PATCH 2/4] #24 add missing project file changes --- Source/EntityFramework.Extended/EntityFramework.Extended.csproj | 2 ++ .../EntityFramework.Extended.net40.csproj | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj index 5d35fef..5a42b06 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj @@ -100,10 +100,12 @@ + + diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj index 10de3fd..02803d1 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj @@ -100,10 +100,12 @@ + + From 3be94e79c8dbddd5fd9ef53e312366371321b79e Mon Sep 17 00:00:00 2001 From: Stephen Reindl Date: Sun, 26 May 2013 23:00:59 +0200 Subject: [PATCH 3/4] #24: Allow ComplexType properties in batch update --- .../Batch/SqlServerBatchRunner.cs | 226 ++++++++++-------- .../EntityFramework.Extended.csproj | 2 + .../EntityFramework.Extended.net40.csproj | 2 + .../Mapping/ComplexPropertyMap.cs | 22 ++ .../Mapping/EntityMap.cs | 6 +- .../Mapping/IPropertyMapElement.cs | 19 ++ .../Mapping/PropertyMap.cs | 2 +- .../Mapping/ReflectionMappingProvider.cs | 54 ++++- 8 files changed, 222 insertions(+), 111 deletions(-) create mode 100644 Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs create mode 100644 Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs diff --git a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs index 37c09b8..a257537 100644 --- a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs +++ b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.EntityClient; using System.Data.Objects; +using System.Diagnostics; using System.Linq; using System.Linq.Dynamic; using System.Linq.Expressions; @@ -170,112 +172,28 @@ public int Update(ObjectContext objectContext, EntityMap entityMap, Obj bool wroteSet = false; foreach (MemberBinding binding in memberInitExpression.Bindings) { - if (wroteSet) - sqlBuilder.AppendLine(", "); - - string propertyName = binding.Member.Name; - string columnName = entityMap.PropertyMaps - .Where(p => p.PropertyName == propertyName) - .Select(p => p.ColumnName) - .FirstOrDefault(); - - - var memberAssignment = binding as MemberAssignment; - if (memberAssignment == null) - throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression"); - - Expression memberExpression = memberAssignment.Expression; - - ParameterExpression parameterExpression = null; - memberExpression.Visit((ParameterExpression p) => - { - if (p.Type == entityMap.EntityType) - parameterExpression = p; - - return p; - }); - - - if (parameterExpression == null) + IPropertyMapElement propertyMap = + entityMap.PropertyMaps.SingleOrDefault(p => p.PropertyName == binding.Member.Name); + if (propertyMap is ComplexPropertyMap) { - object value; - - if (memberExpression.NodeType == ExpressionType.Constant) - { - var constantExpression = memberExpression as ConstantExpression; - if (constantExpression == null) - throw new ArgumentException( - "The MemberAssignment expression is not a ConstantExpression.", "updateExpression"); - - value = constantExpression.Value; - } - else - { - LambdaExpression lambda = Expression.Lambda(memberExpression, null); - value = lambda.Compile().DynamicInvoke(); - } - - if (value != null) - { - string parameterName = "p__update__" + nameCount++; - var parameter = updateCommand.CreateParameter(); - parameter.ParameterName = parameterName; - parameter.Value = value; - updateCommand.Parameters.Add(parameter); - - sqlBuilder.AppendFormat("[{0}] = @{1}", columnName, parameterName); - } - else + ComplexPropertyMap cpm = propertyMap as ComplexPropertyMap; + var memberAssignment = binding as MemberAssignment; + if (memberAssignment == null) + throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression"); + var expr = memberAssignment.Expression as MemberInitExpression; + if (expr == null) + throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "updateExpression"); + foreach (var subBinding in expr.Bindings) { - sqlBuilder.AppendFormat("[{0}] = NULL", columnName); + AddUpdateRow(objectContext, entityMap, subBinding, sqlBuilder, updateCommand, + cpm.TypeElements, ref nameCount, ref wroteSet); } } else { - // create clean objectset to build query from - var objectSet = objectContext.CreateObjectSet(); - - Type[] typeArguments = new[] { entityMap.EntityType, memberExpression.Type }; - - ConstantExpression constantExpression = Expression.Constant(objectSet); - LambdaExpression lambdaExpression = Expression.Lambda(memberExpression, parameterExpression); - - MethodCallExpression selectExpression = Expression.Call( - typeof(Queryable), - "Select", - typeArguments, - constantExpression, - lambdaExpression); - - // create query from expression - var selectQuery = objectSet.CreateQuery(selectExpression, entityMap.EntityType); - string sql = selectQuery.ToTraceString(); - - // parse select part of sql to use as update - string regex = @"SELECT\s*\r\n(?.+)?\s*AS\s*(?\[\w+\])\r\nFROM\s*(?\[\w+\]\.\[\w+\]|\[\w+\])\s*AS\s*(?\[\w+\])"; - Match match = Regex.Match(sql, regex); - if (!match.Success) - throw new ArgumentException("The MemberAssignment expression could not be processed.", "updateExpression"); - - string value = match.Groups["ColumnValue"].Value; - string alias = match.Groups["TableAlias"].Value; - - value = value.Replace(alias + ".", ""); - - foreach (ObjectParameter objectParameter in selectQuery.Parameters) - { - string parameterName = "p__update__" + nameCount++; - - var parameter = updateCommand.CreateParameter(); - parameter.ParameterName = parameterName; - parameter.Value = objectParameter.Value; - updateCommand.Parameters.Add(parameter); - - value = value.Replace(objectParameter.Name, parameterName); - } - sqlBuilder.AppendFormat("[{0}] = {1}", columnName, value); + AddUpdateRow(objectContext, entityMap, binding, sqlBuilder, updateCommand, + entityMap.PropertyMaps, ref nameCount, ref wroteSet); } - wroteSet = true; } sqlBuilder.AppendLine(" "); @@ -316,6 +234,116 @@ public int Update(ObjectContext objectContext, EntityMap entityMap, Obj } } + + private static void AddUpdateRow(ObjectContext objectContext, EntityMap entityMap, MemberBinding binding, StringBuilder sqlBuilder, DbCommand updateCommand, IEnumerable propertyMap, ref int nameCount, ref bool wroteSet) + where TEntity : class + { + if (wroteSet) + sqlBuilder.AppendLine(", "); + + string propertyName = binding.Member.Name; + PropertyMap property = + propertyMap.SingleOrDefault(p => p.PropertyName == propertyName) as PropertyMap; + Debug.Assert(property != null, "property != null"); + string columnName = property.ColumnName; + var memberAssignment = binding as MemberAssignment; + if (memberAssignment == null) + throw new ArgumentException("The update expression MemberBinding must only by type MemberAssignment.", "binding"); + + Expression memberExpression = memberAssignment.Expression; + + ParameterExpression parameterExpression = null; + memberExpression.Visit((ParameterExpression p) => + { + if (p.Type == entityMap.EntityType) + parameterExpression = p; + + return p; + }); + + + if (parameterExpression == null) + { + object value; + + if (memberExpression.NodeType == ExpressionType.Constant) + { + var constantExpression = memberExpression as ConstantExpression; + if (constantExpression == null) + throw new ArgumentException( + "The MemberAssignment expression is not a ConstantExpression.", "binding"); + + value = constantExpression.Value; + } + else + { + LambdaExpression lambda = Expression.Lambda(memberExpression, null); + value = lambda.Compile().DynamicInvoke(); + } + + if (value != null) + { + string parameterName = "p__update__" + nameCount++; + var parameter = updateCommand.CreateParameter(); + parameter.ParameterName = parameterName; + parameter.Value = value; + updateCommand.Parameters.Add(parameter); + + sqlBuilder.AppendFormat("[{0}] = @{1}", columnName, parameterName); + } + else + { + sqlBuilder.AppendFormat("[{0}] = NULL", columnName); + } + } + else + { + // create clean objectset to build query from + var objectSet = objectContext.CreateObjectSet(); + + Type[] typeArguments = new[] { entityMap.EntityType, memberExpression.Type }; + + ConstantExpression constantExpression = Expression.Constant(objectSet); + LambdaExpression lambdaExpression = Expression.Lambda(memberExpression, parameterExpression); + + MethodCallExpression selectExpression = Expression.Call( + typeof(Queryable), + "Select", + typeArguments, + constantExpression, + lambdaExpression); + + // create query from expression + var selectQuery = objectSet.CreateQuery(selectExpression, entityMap.EntityType); + string sql = selectQuery.ToTraceString(); + + // parse select part of sql to use as update + const string regex = @"SELECT\s*\r\n(?.+)?\s*AS\s*(?\[\w+\])\r\nFROM\s*(?\[\w+\]\.\[\w+\]|\[\w+\])\s*AS\s*(?\[\w+\])"; + Match match = Regex.Match(sql, regex); + if (!match.Success) + throw new ArgumentException("The MemberAssignment expression could not be processed.", "binding"); + + string value = match.Groups["ColumnValue"].Value; + string alias = match.Groups["TableAlias"].Value; + + value = value.Replace(alias + ".", ""); + + foreach (ObjectParameter objectParameter in selectQuery.Parameters) + { + string parameterName = "p__update__" + nameCount++; + + var parameter = updateCommand.CreateParameter(); + parameter.ParameterName = parameterName; + parameter.Value = objectParameter.Value; + updateCommand.Parameters.Add(parameter); + + value = value.Replace(objectParameter.Name, parameterName); + } + sqlBuilder.AppendFormat("[{0}] = {1}", columnName, value); + } + wroteSet = true; + } + private static Tuple GetStore(ObjectContext objectContext) { DbConnection dbConnection = objectContext.Connection; diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj index 5d35fef..5a42b06 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj @@ -100,10 +100,12 @@ + + diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj index 10de3fd..02803d1 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj @@ -100,10 +100,12 @@ + + diff --git a/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs b/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs new file mode 100644 index 0000000..de3d29e --- /dev/null +++ b/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFramework.Mapping +{ + /// + /// A property map element representing a complex class + /// + public class ComplexPropertyMap: IPropertyMapElement + { + + public string PropertyName { get; set; } + + /// + /// The enumeration of the complex' type + /// + public ICollection TypeElements { get; set; } + } +} diff --git a/Source/EntityFramework.Extended/Mapping/EntityMap.cs b/Source/EntityFramework.Extended/Mapping/EntityMap.cs index 0310835..6592ce6 100644 --- a/Source/EntityFramework.Extended/Mapping/EntityMap.cs +++ b/Source/EntityFramework.Extended/Mapping/EntityMap.cs @@ -20,7 +20,7 @@ public EntityMap(Type entityType) { _entityType = entityType; _keyMaps = new List(); - _propertyMaps = new List(); + _propertyMaps = new List(); } /// @@ -57,11 +57,11 @@ public Type EntityType /// public string TableName { get; set; } - private readonly List _propertyMaps; + private readonly List _propertyMaps; /// /// Gets the property maps. /// - public List PropertyMaps + public List PropertyMaps { get { return _propertyMaps; } } diff --git a/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs b/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs new file mode 100644 index 0000000..c51e431 --- /dev/null +++ b/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFramework.Mapping +{ + /// + /// Interface representing a property map element which can be single or complex + /// + public interface IPropertyMapElement + { + /// + /// Gets or sets the name of the property. + /// + string PropertyName { get; set; } + } +} diff --git a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs index abe0d78..932b0c8 100644 --- a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs +++ b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs @@ -6,7 +6,7 @@ namespace EntityFramework.Mapping /// A class representing a property map /// [DebuggerDisplay("Property: {PropertyName}, Column: {ColumnName}")] - public class PropertyMap + public class PropertyMap: IPropertyMapElement { /// /// Gets or sets the name of the property. diff --git a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs index ac4487f..09c1fe2 100644 --- a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs +++ b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Data.Metadata.Edm; using System.Data.Objects; using System.Linq; @@ -139,11 +141,15 @@ private static void SetKeys(EntityMap entityMap) var property = entityMap.PropertyMaps.FirstOrDefault(p => p.PropertyName == edmMember.Name); if (property == null) continue; - + if (!(property is PropertyMap)) + { + throw new InvalidOperationException(string.Format("KeyMember {1} of entity {0} cannot be complex.", + entityMap.TableName, edmMember.Name)); + } var map = new PropertyMap { PropertyName = property.PropertyName, - ColumnName = property.ColumnName + ColumnName = ((PropertyMap)property).ColumnName }; entityMap.KeyMaps.Add(map); } @@ -156,17 +162,49 @@ private static void SetProperties(EntityMap entityMap, dynamic mappingFragmentPr { // StorageScalarPropertyMapping dynamic propertyMapProxy = new DynamicProxy(propertyMap); - EdmProperty modelProperty = propertyMapProxy.EdmProperty; EdmProperty storeProperty = propertyMapProxy.ColumnProperty; + ICollection typeMappings = propertyMapProxy.TypeMappings; - var map = new PropertyMap + // use this "ugly" way of type detection as the types + // are internal + if (propertyMap.GetType().Name == "StorageScalarPropertyMapping") { - ColumnName = storeProperty.Name, - PropertyName = modelProperty.Name - }; - entityMap.PropertyMaps.Add(map); + var map = new PropertyMap + { + ColumnName = storeProperty.Name, + PropertyName = modelProperty.Name + }; + + entityMap.PropertyMaps.Add(map); + } + else if (propertyMap.GetType().Name == "StorageComplexPropertyMapping") + { + var map = new ComplexPropertyMap + { + PropertyName = modelProperty.Name, + TypeElements = new List() + }; + foreach (var typeMapping in typeMappings) + { + dynamic typeMappingProxy = new DynamicProxy(typeMapping); + foreach (var property in typeMappingProxy.AllProperties) + { + dynamic pMap = new DynamicProxy(property); + modelProperty = pMap.EdmProperty; + storeProperty = pMap.ColumnProperty; + + var item = new PropertyMap + { + ColumnName = storeProperty.Name, + PropertyName = modelProperty.Name + }; + map.TypeElements.Add(item); + } + } + entityMap.PropertyMaps.Add(map); + } } } From 634f393eb840012e48591ae5d5ca009818189acd Mon Sep 17 00:00:00 2001 From: Michael Elschner Date: Wed, 19 Jun 2013 16:24:40 +0300 Subject: [PATCH 4/4] Update ReflectionMappingProvider.cs #24 also support nested complex properties --- .../Mapping/ReflectionMappingProvider.cs | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs index 09c1fe2..9cc8f36 100644 --- a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs +++ b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -157,55 +157,63 @@ private static void SetKeys(EntityMap entityMap) private static void SetProperties(EntityMap entityMap, dynamic mappingFragmentProxy) { - var propertyMaps = mappingFragmentProxy.Properties; + ICollection propertyMaps = mappingFragmentProxy.Properties; foreach (var propertyMap in propertyMaps) { - // StorageScalarPropertyMapping - dynamic propertyMapProxy = new DynamicProxy(propertyMap); - EdmProperty modelProperty = propertyMapProxy.EdmProperty; - EdmProperty storeProperty = propertyMapProxy.ColumnProperty; + var map = CreatePropertyMap(propertyMap); + if (map == null) continue; + + entityMap.PropertyMaps.Add(map); + } + } + + private static IPropertyMapElement CreatePropertyMap(object propertyMap) + { + // StorageScalarPropertyMapping + dynamic propertyMapProxy = new DynamicProxy(propertyMap); + EdmProperty modelProperty = propertyMapProxy.EdmProperty; + EdmProperty storeProperty = propertyMapProxy.ColumnProperty; + + // use this "ugly" way of type detection as the types + // are internal + if (propertyMap.GetType().Name == "StorageScalarPropertyMapping") + { + return new PropertyMap + { + ColumnName = storeProperty.Name, + PropertyName = modelProperty.Name + }; + } + + if (propertyMap.GetType().Name == "StorageComplexPropertyMapping") + { ICollection typeMappings = propertyMapProxy.TypeMappings; + return CreateComplexPropertyMap(modelProperty.Name, typeMappings); + } - // use this "ugly" way of type detection as the types - // are internal - if (propertyMap.GetType().Name == "StorageScalarPropertyMapping") - { - var map = new PropertyMap - { - ColumnName = storeProperty.Name, - PropertyName = modelProperty.Name - }; + throw new InvalidOperationException("Invalid or unknown propertyMap type: " + propertyMap.GetType().Name); + } - entityMap.PropertyMaps.Add(map); - } - else if (propertyMap.GetType().Name == "StorageComplexPropertyMapping") + private static ComplexPropertyMap CreateComplexPropertyMap(string propertyName, IEnumerable typeMappings) + { + var typeElements = new List(); + foreach (var typeMapping in typeMappings) + { + dynamic typeMappingProxy = new DynamicProxy(typeMapping); + foreach (var property in typeMappingProxy.AllProperties) { - var map = new ComplexPropertyMap - { - PropertyName = modelProperty.Name, - TypeElements = new List() - }; - foreach (var typeMapping in typeMappings) - { - dynamic typeMappingProxy = new DynamicProxy(typeMapping); - foreach (var property in typeMappingProxy.AllProperties) - { - dynamic pMap = new DynamicProxy(property); - modelProperty = pMap.EdmProperty; - storeProperty = pMap.ColumnProperty; - - var item = new PropertyMap - { - ColumnName = storeProperty.Name, - PropertyName = modelProperty.Name - }; - map.TypeElements.Add(item); - } - } - entityMap.PropertyMaps.Add(map); + var map = CreatePropertyMap(property); + + typeElements.Add(map); } } + + return new ComplexPropertyMap + { + PropertyName = propertyName, + TypeElements = typeElements + }; } private static void SetTableName(EntityMap entityMap)