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..9cc8f36 100644 --- a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs +++ b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs @@ -1,5 +1,7 @@ -using System; +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); } @@ -151,23 +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); + var map = CreatePropertyMap(propertyMap); + if (map == null) continue; - EdmProperty modelProperty = propertyMapProxy.EdmProperty; - EdmProperty storeProperty = propertyMapProxy.ColumnProperty; + entityMap.PropertyMaps.Add(map); + } + } - var map = new PropertyMap + 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); + } + + + throw new InvalidOperationException("Invalid or unknown propertyMap type: " + propertyMap.GetType().Name); + } + + 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) { - ColumnName = storeProperty.Name, - PropertyName = modelProperty.Name - }; + var map = CreatePropertyMap(property); - entityMap.PropertyMaps.Add(map); + typeElements.Add(map); + } } + + return new ComplexPropertyMap + { + PropertyName = propertyName, + TypeElements = typeElements + }; } private static void SetTableName(EntityMap entityMap)