diff --git a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs index 6f23996..66b04a8 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.Entity.Core.EntityClient; using System.Data.Entity.Core.Objects; +using System.Diagnostics; using System.Linq; using System.Linq.Dynamic; using System.Linq.Expressions; @@ -238,112 +240,38 @@ private int InternalUpdate(ObjectContext objectContext, EntityMap entit 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) => + var propertyMap = entityMap.PropertyMaps.Single(p => p.PropertyName == binding.Member.Name); + + var pm = propertyMap as PropertyMap; + if (pm != null) { - if (p.Type == entityMap.EntityType) - parameterExpression = p; - - return p; - }); - + AddUpdateRow(objectContext, entityMap, binding, sqlBuilder, updateCommand, + entityMap.PropertyMaps, ref nameCount, ref wroteSet); + continue; + } - if (parameterExpression == null) + var cpm = propertyMap as ComplexPropertyMap; + if (cpm != 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.", "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 + 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.Elements, ref nameCount, ref wroteSet); } + continue; } - 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*(?.+)?\s*AS\s*(?\[\w+\])\r\n\s*FROM\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 ?? DBNull.Value; - updateCommand.Parameters.Add(parameter); - - value = value.Replace(objectParameter.Name, parameterName); - } - sqlBuilder.AppendFormat("[{0}] = {1}", columnName, value); - } - wroteSet = true; + throw new InvalidOperationException(string.Format("Invalid propertyMap type: {0}", propertyMap.GetType())); } sqlBuilder.AppendLine(" "); @@ -372,6 +300,7 @@ private int InternalUpdate(ObjectContext objectContext, EntityMap entit #else int result = updateCommand.ExecuteNonQuery(); #endif + // only commit if created transaction if (ownTransaction) updateTransaction.Commit(); @@ -389,6 +318,115 @@ private int InternalUpdate(ObjectContext objectContext, EntityMap entit } } + 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; + var 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 + string regex = @"SELECT\s*\r\n\s*(?.+)?\s*AS\s*(?\[\w+\])\r\n\s*FROM\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 ?? DBNull.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) { // TODO, re-eval if this is needed diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj index c595f28..b58d62f 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj @@ -104,10 +104,12 @@ + + diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj index 88fb4e5..c8bd367 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj @@ -104,10 +104,12 @@ + + @@ -159,4 +161,4 @@ --> - \ No newline at end of file + diff --git a/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs b/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs new file mode 100644 index 0000000..776e8ee --- /dev/null +++ b/Source/EntityFramework.Extended/Mapping/ComplexPropertyMap.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; + +namespace EntityFramework.Mapping +{ + /// + /// A property map element representing a complex class + /// + public class ComplexPropertyMap : IPropertyMapElement + { + private readonly IList _elements; + + /// + /// Create a new instance. + /// + /// the property name. + /// the mapped elements of the complex property + public ComplexPropertyMap(string propertyName, IEnumerable elements) + { + _elements = elements.ToList(); + PropertyName = propertyName; + } + + /// + /// The property name. + /// + public string PropertyName { get; private set; } + + /// + /// The complex type's elements. + /// + public IEnumerable Elements + { + get { return _elements; } + } + } +} diff --git a/Source/EntityFramework.Extended/Mapping/EntityMap.cs b/Source/EntityFramework.Extended/Mapping/EntityMap.cs index c338452..2e84b5b 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..5139d9a --- /dev/null +++ b/Source/EntityFramework.Extended/Mapping/IPropertyMapElement.cs @@ -0,0 +1,13 @@ +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; } + } +} diff --git a/Source/EntityFramework.Extended/Mapping/MetadataMappingProvider.cs b/Source/EntityFramework.Extended/Mapping/MetadataMappingProvider.cs index 2465e2f..edee697 100644 --- a/Source/EntityFramework.Extended/Mapping/MetadataMappingProvider.cs +++ b/Source/EntityFramework.Extended/Mapping/MetadataMappingProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Core.Mapping; using System.Data.Entity.Core.Metadata.Edm; @@ -92,14 +93,14 @@ private static void SetKeys(EntityMap entityMap) { var property = entityMap.PropertyMaps.FirstOrDefault(p => p.PropertyName == edmMember.Name); if (property == null) - continue; - - var map = new PropertyMap - { - PropertyName = property.PropertyName, - ColumnName = property.ColumnName - }; - entityMap.KeyMaps.Add(map); + throw new InvalidOperationException(string.Format("There is no mapping for key member '{0}'.", edmMember)); + + var propertyMap = property as PropertyMap; + if (propertyMap == null) + throw new InvalidOperationException(string.Format("KeyMember {1} of entity {0} cannot be complex.", + entityMap.TableName, edmMember.Name)); + + entityMap.KeyMaps.Add(propertyMap); } } @@ -107,23 +108,33 @@ private static void SetProperties(EntityMap entityMap, MappingFragment mappingFr { foreach (var propertyMapping in mappingFragment.PropertyMappings) { - var map = new PropertyMap(); - map.PropertyName = propertyMapping.Property.Name; - + var map = CreatePropertyMap(propertyMapping); entityMap.PropertyMaps.Add(map); + } + } + + private static IPropertyMapElement CreatePropertyMap(PropertyMapping propertyMapping) + { + var scalarPropertyMapping = propertyMapping as ScalarPropertyMapping; + if (scalarPropertyMapping != null) + { + return new PropertyMap(propertyMapping.Property.Name, scalarPropertyMapping.Column.Name); + } - var scalarPropertyMapping = propertyMapping as ScalarPropertyMapping; - if (scalarPropertyMapping != null) - { - map.ColumnName = scalarPropertyMapping.Column.Name; - continue; - } + var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; + if (complexPropertyMapping != null) + { + var typeElements = + from typeMapping in complexPropertyMapping.TypeMappings + from property in typeMapping.PropertyMappings + select CreatePropertyMap(property); - // TODO support complex mapping - var complexPropertyMapping = propertyMapping as ComplexPropertyMapping; + return new ComplexPropertyMap(propertyMapping.Property.Name, typeElements.ToList()); } + + throw new InvalidOperationException("Invalid or unknown propertyMapping type: " + propertyMapping.GetType()); } - + private static void SetTableName(EntityMap entityMap) { var builder = new StringBuilder(50); diff --git a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs index abe0d78..004ec0d 100644 --- a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs +++ b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs @@ -6,15 +6,27 @@ namespace EntityFramework.Mapping /// A class representing a property map /// [DebuggerDisplay("Property: {PropertyName}, Column: {ColumnName}")] - public class PropertyMap + public class PropertyMap : IPropertyMapElement { + /// + /// Create a new instance. + /// + /// the property name + /// the column name + public PropertyMap(string propertyName, string columnName) + { + ColumnName = columnName; + PropertyName = propertyName; + } + /// /// Gets or sets the name of the property. /// - public string PropertyName { get; set; } + public string PropertyName { get; private set; } + /// /// Gets or sets the name of the column. /// - public string ColumnName { get; set; } + public string ColumnName { get; private set; } } } \ No newline at end of file