From 743ce53e97d55addfbf127c4254c06b286cdb223 Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 15:26:30 -0500 Subject: [PATCH 1/9] Added property map for constant values Added ConstantPropertyMap to support mapping to constant values, such as with Table-Per-Hierarchy (TPH) polymorphic entities. --- .../EntityFramework.Extended/Mapping/PropertyMap.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs index abe0d78..6fb7f1e 100644 --- a/Source/EntityFramework.Extended/Mapping/PropertyMap.cs +++ b/Source/EntityFramework.Extended/Mapping/PropertyMap.cs @@ -17,4 +17,15 @@ public class PropertyMap /// public string ColumnName { get; set; } } -} \ No newline at end of file + /// + /// A class representing a property map for a constant value + /// + [DebuggerDisplay("Property: {PropertyName}, Column: {ColumnName}, Value: {Value}")] + public class ConstantPropertyMap : PropertyMap + { + /// + /// Gets or sets the constant value + /// + public object Value { get; set; } + } +} From c0748df943723afc312c5810991aa2bea8df6acb Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 15:32:30 -0500 Subject: [PATCH 2/9] Multiple mapping fragments,better schema detection Adds support for multiple mapping fragments Improves schema name detection --- .../Mapping/ReflectionMappingProvider.cs | 121 +++++++++++------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs index ac4487f..8bd9143 100644 --- a/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs +++ b/Source/EntityFramework.Extended/Mapping/ReflectionMappingProvider.cs @@ -67,22 +67,22 @@ private static EntityMap CreateEntityMap(ObjectQuery query) if (itemCollection == null) return null; - dynamic mappingFragmentProxy = FindMappingFragment(itemCollection, entityMap.ModelSet); - if (mappingFragmentProxy == null) + List mappingFragmentProxies = FindMappingFragments(entityType, itemCollection, entityMap.ModelSet); + if (mappingFragmentProxies == null) return null; // SModel - entityMap.StoreSet = mappingFragmentProxy.TableSet; + entityMap.StoreSet = mappingFragmentProxies.Select(x => x.TableSet).Where(x => x != null).FirstOrDefault(); entityMap.StoreType = entityMap.StoreSet.ElementType; - SetProperties(entityMap, mappingFragmentProxy); + SetProperties(entityMap, mappingFragmentProxies); SetKeys(entityMap); SetTableName(entityMap); return entityMap; } - private static dynamic FindMappingFragment(IEnumerable itemCollection, EntitySet entitySet) + private static List FindMappingFragments(Type entityType, IEnumerable itemCollection, EntitySet entitySet) { //StorageEntityContainerMapping var storage = itemCollection.FirstOrDefault(); @@ -108,26 +108,37 @@ private static dynamic FindMappingFragment(IEnumerable itemCollectio if (typeMappings == null) continue; - object typeMapping = typeMappings.FirstOrDefault(); - if (typeMapping == null) - continue; - - // StorageEntityTypeMapping - dynamic typeMappingProxy = new DynamicProxy(typeMapping); - - // only support first mapping fragment - IEnumerable mappingFragments = typeMappingProxy.MappingFragments; - if (mappingFragments == null) - continue; - object mappingFragment = mappingFragments.FirstOrDefault(); - if (mappingFragment == null) - continue; - - //StorageMappingFragment - dynamic mappingFragmentProxy = new DynamicProxy(mappingFragment); - return mappingFragmentProxy; + List mappingFragmentProxies = new List(); + foreach (var typeMapping in typeMappings) + { + if (typeMapping == null) + continue; + + // StorageEntityTypeMapping + dynamic typeMappingProxy = new DynamicProxy(typeMapping); + IEnumerable types = typeMappingProxy.Types; + if (types.Any(x => x.Name != entityType.Name)) + { + continue; + } + + // only support first mapping fragment + IEnumerable mappingFragments = typeMappingProxy.MappingFragments; + if (mappingFragments == null) + continue; + foreach (var mappingFragment in mappingFragments) + { + if (mappingFragment == null) + continue; + + mappingFragmentProxies.Add(new DynamicProxy(mappingFragment)); + } + } + if (mappingFragmentProxies.Any()) + { + return mappingFragmentProxies; + } } - return null; } @@ -149,24 +160,40 @@ private static void SetKeys(EntityMap entityMap) } } - private static void SetProperties(EntityMap entityMap, dynamic mappingFragmentProxy) + private static void SetProperties(EntityMap entityMap, List mappingFragmentProxies) { - var propertyMaps = mappingFragmentProxy.Properties; - foreach (var propertyMap in propertyMaps) + foreach (dynamic mappingFragmentProxy in mappingFragmentProxies) { - // StorageScalarPropertyMapping - dynamic propertyMapProxy = new DynamicProxy(propertyMap); - - EdmProperty modelProperty = propertyMapProxy.EdmProperty; - EdmProperty storeProperty = propertyMapProxy.ColumnProperty; - - var map = new PropertyMap + var propertyMaps = mappingFragmentProxy.AllProperties; + foreach (var propertyMap in propertyMaps) { - ColumnName = storeProperty.Name, - PropertyName = modelProperty.Name - }; - - entityMap.PropertyMaps.Add(map); + // StorageScalarPropertyMapping + dynamic propertyMapProxy = new DynamicProxy(propertyMap); + EdmProperty storeProperty = propertyMapProxy.ColumnProperty; + if (!entityMap.PropertyMaps.Any(x => x.PropertyName == storeProperty.Name)) + { + PropertyMap map; + EdmProperty modelProperty = propertyMapProxy.EdmProperty; + if (modelProperty == null) + { + map = new ConstantPropertyMap + { + ColumnName = QuoteIdentifier(storeProperty.Name), + PropertyName = storeProperty.Name, + Value = propertyMapProxy.Value.Wrapped + }; + } + else + { + map = new PropertyMap + { + ColumnName = QuoteIdentifier(storeProperty.Name), + PropertyName = modelProperty.Name + }; + } + entityMap.PropertyMaps.Add(map); + } + } } } @@ -193,13 +220,17 @@ private static void SetTableName(EntityMap entityMap) if (table == null) table = storeSet.Name; - storeSet.MetadataProperties.TryGetValue("Schema", true, out schemaProperty); - if (schemaProperty == null || schemaProperty.Value == null) - storeSet.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Schema", true, out schemaProperty); - - if (schemaProperty != null) - schema = schemaProperty.Value as string; + dynamic storeSetProxy = new DynamicProxy(storeSet); + schema = (string)storeSetProxy.Schema; + if (schema == null) + { + storeSet.MetadataProperties.TryGetValue("Schema", true, out schemaProperty); + if (schemaProperty == null || schemaProperty.Value == null) + storeSet.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator:Schema", true, out schemaProperty); + if (schemaProperty != null) + schema = schemaProperty.Value as string; + } if (!string.IsNullOrWhiteSpace(schema)) { builder.Append(QuoteIdentifier(schema)); From e23bc35240052ca18b8e3e27b214353d5389f4c0 Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 15:45:22 -0500 Subject: [PATCH 3/9] Add BulkInsert and InsertFrom --- .../Extensions/BatchExtensions.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs b/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs index 390dd1e..5a8f95f 100644 --- a/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs @@ -233,6 +233,85 @@ public static int Update( return runner.Update(objectContext, entityMap, sourceQuery, updateExpression); } + /// + /// Executes a bulk insert statement, inserting a set of records. + /// + /// The type of the entity. + /// The to insert the records into. + /// Collection of to be inserted + /// The number of row inserted. + /// + /// When executing this method, the statement is immediately executed on the database provider + /// and is not part of the change tracking system. + /// + public static int BulkInsert(this DbSet destination, IEnumerable records) where TEntity : class + { + if (destination == null) + throw new ArgumentNullException("source"); + if (records == null) + throw new ArgumentNullException("records"); + + ObjectQuery sourceQuery = destination.ToObjectQuery(); + if (sourceQuery == null) + throw new ArgumentException("The query must be of type ObjectQuery or DbQuery.", "source"); + + ObjectContext objectContext = sourceQuery.Context; + if (objectContext == null) + throw new ArgumentException("The ObjectContext for the source query can not be null.", "source"); + + EntityMap entityMap = sourceQuery.GetEntityMap(); + if (entityMap == null) + throw new ArgumentException("Could not load the entity mapping information for the source.", "source"); + + var runner = ResolveRunner(); + return runner.BulkInsert(objectContext, entityMap, records); + } + + /// + /// Executes an insert from statement, inserting a set of records. + /// + /// The type of the source entity. + /// The type of the entity to be inserted. + /// The to insert the records into. + /// The query from which to get the entities. + /// The insert expression mapping to + /// The number of row inserted. + /// + /// When executing this method, the statement is immediately executed on the database provider + /// and is not part of the change tracking system. + /// + public static int InsertFrom(this DbSet destination, + IQueryable source, Expression> mappingExpression) + where TEntity : class + where TSource : class + { + if (destination == null) + throw new ArgumentNullException("destination"); + if (source == null) + throw new ArgumentNullException("query"); + if (mappingExpression == null) + throw new ArgumentNullException("insertExpression"); + + ObjectQuery destinationQuery = destination.ToObjectQuery(); + if (destinationQuery == null) + throw new ArgumentException("The query must be of type ObjectQuery or DbQuery.", "destination"); + + ObjectContext destinationContext = destinationQuery.Context; + if (destinationContext == null) + throw new ArgumentException("The ObjectContext for the destination query can not be null.", "destination"); + + EntityMap destinationEntityMap = destinationQuery.GetEntityMap(); + if (destinationEntityMap == null) + throw new ArgumentException("Could not load the entity mapping information for the destination.", "destination"); + + ObjectQuery sourceQuery = source.ToObjectQuery(); + if (sourceQuery == null) + throw new ArgumentException("The source must be of type ObjectQuery or DbQuery.", "source"); + + var runner = ResolveRunner(); + return runner.InsertFrom(destinationContext, destinationEntityMap, sourceQuery, mappingExpression); + } + private static IBatchRunner ResolveRunner() { var provider = Locator.Current.Resolve(); From a6420ce85665187b52657de5214cc0b4e0916f24 Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 16:33:57 -0500 Subject: [PATCH 4/9] Update Source/EntityFramework.Extended/Extensions/BatchExtensions.cs --- Source/EntityFramework.Extended/Extensions/BatchExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs b/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs index 5a8f95f..5a01996 100644 --- a/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/BatchExtensions.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; From d48f93a015bcc6910cd7c12b1104c160929253a0 Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 16:34:43 -0500 Subject: [PATCH 5/9] Add BulkInsert, InsertFrom --- .../Batch/IBatchRunner.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Source/EntityFramework.Extended/Batch/IBatchRunner.cs b/Source/EntityFramework.Extended/Batch/IBatchRunner.cs index c406fb1..ba8d915 100644 --- a/Source/EntityFramework.Extended/Batch/IBatchRunner.cs +++ b/Source/EntityFramework.Extended/Batch/IBatchRunner.cs @@ -33,5 +33,30 @@ int Delete(ObjectContext objectContext, EntityMap entityMap, ObjectQuer /// The number of rows updated. int Update(ObjectContext objectContext, EntityMap entityMap, ObjectQuery query, Expression> updateExpression) where TEntity : class; + + /// + /// Create and runs a batch insert statement. + /// + /// The type of the entity to be inserted + /// The to get connection and metadata information from. + /// The for . + /// Collection of to be inserted + /// The number of rows inserted. + int BulkInsert(ObjectContext objectContext, EntityMap entityMap, IEnumerable records) + where TEntity : class; + + /// + /// Create and runs a batch insert from statement. + /// + /// The type of the source entity. + /// The type of the entity to be inserted. + /// The to get connection and metadata information from. + /// The for . + /// The query from which to get the entities. + /// The insert expression mapping to + /// The number of rows inserted. + int InsertFrom(ObjectContext destinationContext, EntityMap destinationEntityMap, ObjectQuery sourceQuery, Expression> mappingExpression) + where TEntity : class + where TSource : class; } } From 7f8dc34ee7f6c290754db8605ef5b0b7e9074730 Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 17:20:14 -0500 Subject: [PATCH 6/9] Add BulkInsert and InsertFrom --- .../Batch/SqlServerBatchRunner.cs | 252 +++++++++++++++++- 1 file changed, 251 insertions(+), 1 deletion(-) diff --git a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs index 37c09b8..ffb633d 100644 --- a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs +++ b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.EntityClient; @@ -6,8 +7,10 @@ using System.Linq; using System.Linq.Dynamic; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Transactions; using EntityFramework.Extensions; using EntityFramework.Mapping; using EntityFramework.Reflection; @@ -316,6 +319,204 @@ public int Update(ObjectContext objectContext, EntityMap entityMap, Obj } } + + /// + /// Create and runs a batch insert statement. + /// + /// The type of the entity to be inserted + /// The to get connection and metadata information from. + /// The for . + /// Collection of to be inserted + /// The number of rows inserted. + public int BulkInsert(ObjectContext objectContext, EntityMap entityMap, IEnumerable records) + where TEntity : class + { + DbConnection insertConnection = null; + DbTransaction insertTransaction = null; + DbCommand insertCommand = null; + bool ownConnection = false; + bool ownTransaction = false; + + try + { + // get store connection and transaction + var store = GetStore(objectContext); + insertConnection = store.Item1; + insertTransaction = store.Item2; + + if (insertConnection.State != ConnectionState.Open) + { + insertConnection.Open(); + ownConnection = true; + } + + if (insertTransaction == null) + { + insertTransaction = insertConnection.BeginTransaction(); + ownTransaction = true; + } + + insertCommand = insertConnection.CreateCommand(); + insertCommand.Transaction = insertTransaction; + + if (objectContext.CommandTimeout.HasValue) + insertCommand.CommandTimeout = objectContext.CommandTimeout.Value; + + List propertyMaps = entityMap.PropertyMaps; + + var insertLine = new StringBuilder(); + insertLine.Append("INSERT INTO "); + insertLine.AppendLine(entityMap.TableName); + insertLine.Append(" ("); + insertLine.Append(String.Join(", ", propertyMaps + .Select(x => x.ColumnName))); + insertLine.AppendLine(") VALUES "); + var rows = new StringBuilder(); + List parameters = new List(); + Type entityType = records.FirstOrDefault().GetType(); + PropertyInfo[] properties = entityType.GetProperties(); + int i = 0; + int result = 0; + int maxRows = 2000 / propertyMaps.Count(); + + foreach (var record in records) + { + int propCount = parameters.Count(); + if (propCount > 0) + { + rows.AppendLine(","); + } + rows.Append("("); + rows.Append(String.Join(", ", propertyMaps + .Select(x => String.Format("@{0}", propCount++)))); + rows.Append(")"); + foreach (PropertyMap propMap in propertyMaps) + { + object value = null; + DbParameter dbParam = insertCommand.CreateParameter(); + dbParam.ParameterName = String.Format("@{0}", parameters.Count); + if (propMap is ConstantPropertyMap) + { + value = ((ConstantPropertyMap)propMap).Value; + } + else + { + PropertyInfo prop = properties.SingleOrDefault(x => x.Name == propMap.PropertyName); + if (prop != null) + { + value = prop.GetValue(record, new object[0]); + dbParam.DbType = DbTypeConversion.ToDbType(prop.PropertyType); + } + } + dbParam.Value = value ?? DBNull.Value; + parameters.Add(dbParam); + } + if (++i >= maxRows) + { + result += CommitBulk(insertCommand, insertLine, rows, parameters); + i = 0; + rows.Clear(); + parameters.Clear(); + } + } + if (rows.Length > 0) + { + result += CommitBulk(insertCommand, insertLine, rows, parameters); + } + return result; + } + finally + { + if (insertCommand != null) + insertCommand.Dispose(); + if (insertTransaction != null && ownTransaction) + insertTransaction.Dispose(); + if (insertConnection != null && ownConnection) + insertConnection.Close(); + } + } + + /// + /// Create and runs a batch insert from statement. + /// + /// The type of the source entity. + /// The type of the entity to be inserted. + /// The to get connection and metadata information from. + /// The for . + /// The query from which to get the entities. + /// The insert expression mapping to + /// The number of rows inserted. + public int InsertFrom(ObjectContext destinationContext, EntityMap destinationEntityMap, ObjectQuery sourceQuery, Expression> mappingExpression) + where TEntity : class + where TSource : class + { + DbConnection insertConnection = null; + DbTransaction insertTransaction = null; + DbCommand insertCommand = null; + bool ownConnection = false; + bool ownTransaction = false; + + try + { + // get store connection and transaction + var store = GetStore(destinationContext); + insertConnection = store.Item1; + insertTransaction = store.Item2; + + if (insertConnection.State != ConnectionState.Open) + { + insertConnection.Open(); + ownConnection = true; + } + + if (insertTransaction == null) + { + insertTransaction = insertConnection.BeginTransaction(); + ownTransaction = true; + } + + insertCommand = insertConnection.CreateCommand(); + insertCommand.Transaction = insertTransaction; + + if (destinationContext.CommandTimeout.HasValue) + insertCommand.CommandTimeout = destinationContext.CommandTimeout.Value; + + var memberInitExpression = mappingExpression.Body as MemberInitExpression; + if (memberInitExpression == null) + throw new ArgumentException("The insert expression must be of type MemberInitExpression.", "insertExpression"); + + var innerSelect = GetInsertSelectSql(sourceQuery, destinationEntityMap, memberInitExpression, insertCommand); + var sqlBuilder = new StringBuilder(innerSelect.Length * 2); + + sqlBuilder.Append("INSERT INTO "); + sqlBuilder.AppendLine(destinationEntityMap.TableName); + sqlBuilder.Append(" ("); + + sqlBuilder.Append(String.Join(", ", memberInitExpression.Bindings + .Select(x => destinationEntityMap.PropertyMaps + .Where(p => p.PropertyName == x.Member.Name) + .Select(p => p.ColumnName) + .FirstOrDefault()) + .Union(destinationEntityMap.PropertyMaps.OfType().Select(x => x.ColumnName)))); + + sqlBuilder.AppendLine(") "); + sqlBuilder.AppendLine(innerSelect); + insertCommand.CommandText = sqlBuilder.ToString(); + + int result = insertCommand.ExecuteNonQuery(); + return result; + } + finally + { + if (insertCommand != null) + insertCommand.Dispose(); + if (insertTransaction != null && ownTransaction) + insertTransaction.Dispose(); + if (insertConnection != null && ownConnection) + insertConnection.Close(); + } + } + private static Tuple GetStore(ObjectContext objectContext) { DbConnection dbConnection = objectContext.Connection; @@ -372,5 +573,54 @@ private static string GetSelectSql(ObjectQuery query, EntityMa return innerJoinSql; } + + private static string GetInsertSelectSql(ObjectQuery query, EntityMap entityMap, MemberInitExpression memberInitExpression, DbCommand command) + where TSource : class + { + var selector = new StringBuilder(50); + int i = 0; + var constantProperties = entityMap.PropertyMaps.OfType(); + selector.Append("new("); + selector.Append(String.Join(", ", memberInitExpression.Bindings + .Select(x => ((x as MemberAssignment).Expression as MemberExpression).Member.Name) + .Union(constantProperties.Select(x => String.Format("@{0} as {1}", i++, x.PropertyName))))); //, x.PropertyName + selector.Append(")"); + + var selectQuery = DynamicQueryable.Select(query, selector.ToString(), constantProperties.Select(x => x.Value).ToArray()); + var objectQuery = selectQuery as ObjectQuery; + + if (objectQuery == null) + throw new ArgumentException("The query must be of type ObjectQuery.", "query"); + objectQuery.EnablePlanCaching = true; + string innerJoinSql = EFQueryUtils.GetLimitedQuery(objectQuery); + // create parameters + foreach (var objectParameter in objectQuery.Parameters) + { + var parameter = command.CreateParameter(); + parameter.ParameterName = objectParameter.Name; + parameter.Value = objectParameter.Value ?? DBNull.Value; + + command.Parameters.Add(parameter); + } + + return innerJoinSql; + } + + private static int CommitBulk(DbCommand command, StringBuilder insertLine, StringBuilder rows, List parameters) + { + string commandText = insertLine.ToString() + rows.ToString(); + int parameterCount = 0; + if (command.CommandText != commandText) + { + command.CommandText = commandText; + command.Parameters.Clear(); + command.Parameters.AddRange(parameters.ToArray()); + } + else + { + parameters.ForEach(x => command.Parameters[parameterCount++].Value = x.Value); + } + return command.ExecuteNonQuery(); + } } -} \ No newline at end of file +} From 6c78396ebef565938ec28db1b962187f7a7d586d Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 17:21:42 -0500 Subject: [PATCH 7/9] Add DB Type Conversions --- .../Batch/SqlServerBatchRunner.cs | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs index ffb633d..6e8f1ff 100644 --- a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs +++ b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs @@ -623,4 +623,143 @@ private static int CommitBulk(DbCommand command, StringBuilder insertLine, Strin return command.ExecuteNonQuery(); } } + + /// + /// Convert a base data type to another base data type + /// + public static class DbTypeConversion + { + private class DbTypeMapEntry + { + public Type Type; + public DbType DbType; + public SqlDbType SqlDbType; + public DbTypeMapEntry(Type type, DbType dbType, SqlDbType sqlDbType) + { + this.Type = type; + this.DbType = dbType; + this.SqlDbType = sqlDbType; + } + }; + private static List dbTypeList = new List(); + + #region Constructors + static DbTypeConversion() + { + dbTypeList.Add(new DbTypeMapEntry(typeof(bool), DbType.Boolean, SqlDbType.Bit)); + dbTypeList.Add(new DbTypeMapEntry(typeof(byte), DbType.Double, SqlDbType.TinyInt)); + dbTypeList.Add(new DbTypeMapEntry(typeof(byte[]), DbType.Binary, SqlDbType.Image)); + dbTypeList.Add(new DbTypeMapEntry(typeof(DateTime), DbType.DateTime, SqlDbType.DateTime)); + dbTypeList.Add(new DbTypeMapEntry(typeof(Decimal), DbType.Decimal, SqlDbType.Decimal)); + dbTypeList.Add(new DbTypeMapEntry(typeof(double), DbType.Double, SqlDbType.Float)); + dbTypeList.Add(new DbTypeMapEntry(typeof(Guid), DbType.Guid, SqlDbType.UniqueIdentifier)); + dbTypeList.Add(new DbTypeMapEntry(typeof(Int16), DbType.Int16, SqlDbType.SmallInt)); + dbTypeList.Add(new DbTypeMapEntry(typeof(Int32), DbType.Int32, SqlDbType.Int)); + dbTypeList.Add(new DbTypeMapEntry(typeof(Int64), DbType.Int64, SqlDbType.BigInt)); + dbTypeList.Add(new DbTypeMapEntry(typeof(object), DbType.Object, SqlDbType.Variant)); + dbTypeList.Add(new DbTypeMapEntry(typeof(string), DbType.String, SqlDbType.VarChar)); + dbTypeList.Add(new DbTypeMapEntry(typeof(DBNull), DbType.Object, SqlDbType.Variant)); + } + + #endregion + + #region Methods + /// + /// Convert db type to .Net data type + /// + /// + /// + public static Type ToNetType(DbType dbType) + { + DbTypeMapEntry entry = Find(dbType); + return entry.Type; + } + + /// + /// Convert TSQL type to .Net data type + /// + /// + /// + public static Type ToNetType(SqlDbType sqlDbType) + { + DbTypeMapEntry entry = Find(sqlDbType); + return entry.Type; + } + /// + /// Convert .Net type to Db type + /// + /// + /// + public static DbType ToDbType(Type type) + { + DbTypeMapEntry entry = Find(type); + return entry.DbType; + } + /// + /// Convert TSQL data type to DbType + /// + /// + /// + public static DbType ToDbType(SqlDbType sqlDbType) + { + DbTypeMapEntry entry = Find(sqlDbType); + return entry.DbType; + } + + /// + /// Convert .Net type to TSQL data type + /// + /// + /// + public static SqlDbType ToSqlDbType(Type type) + { + DbTypeMapEntry entry = Find(type); + return entry.SqlDbType; + } + + /// + /// Convert DbType type to TSQL data type + /// + /// + /// + public static SqlDbType ToSqlDbType(DbType dbType) + { + DbTypeMapEntry entry = Find(dbType); + return entry.SqlDbType; + } + + private static DbTypeMapEntry Find(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + type = type.GetGenericArguments()[0]; + } + DbTypeMapEntry result = dbTypeList.FirstOrDefault(x => x.Type == type); + if (result == null) + { + throw new ApplicationException("Referenced an unsupported Type"); + } + return result; + } + private static DbTypeMapEntry Find(DbType dbType) + { + DbTypeMapEntry result = dbTypeList.FirstOrDefault(x => x.DbType == dbType); + if (result == null) + { + throw new ApplicationException("Referenced an unsupported DbType"); + } + return result; + + } + private static DbTypeMapEntry Find(SqlDbType sqlDbType) + { + DbTypeMapEntry result = dbTypeList.FirstOrDefault(x => x.SqlDbType == sqlDbType); + if (result == null) + { + throw new ApplicationException("Referenced an unsupported SqlDbType"); + } + return result; + } + #endregion + } } From 343c5038f21c9def5017377d266088891dccd088 Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 17:41:08 -0500 Subject: [PATCH 8/9] Add inner Select builder --- .../Batch/SqlServerBatchRunner.cs | 105 +++++++++++++++++- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs index 6e8f1ff..6073676 100644 --- a/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs +++ b/Source/EntityFramework.Extended/Batch/SqlServerBatchRunner.cs @@ -10,7 +10,6 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Transactions; using EntityFramework.Extensions; using EntityFramework.Mapping; using EntityFramework.Reflection; @@ -592,7 +591,78 @@ private static string GetInsertSelectSql(ObjectQuery query, En if (objectQuery == null) throw new ArgumentException("The query must be of type ObjectQuery.", "query"); objectQuery.EnablePlanCaching = true; - string innerJoinSql = EFQueryUtils.GetLimitedQuery(objectQuery); + string select = query.ToTraceString(); + + // get private ObjectQueryState ObjectQuery._state; + // of actual type internal class + // System.Data.Objects.ELinq.ELinqQueryState + object queryState = GetProperty(query, "QueryState", "System.Data.Objects.ELinq.ELinqQueryState"); + + // get protected ObjectQueryExecutionPlan ObjectQueryState._cachedPlan; + // of actual type internal sealed class + // System.Data.Objects.Internal.ObjectQueryExecutionPlan + object plan = GetField(queryState, "_cachedPlan", "System.Data.Objects.Internal.ObjectQueryExecutionPlan"); + + // get internal readonly DbCommandDefinition ObjectQueryExecutionPlan.CommandDefinition; + // of actual type internal sealed class + // System.Data.EntityClient.EntityCommandDefinition + object commandDefinition = GetField(plan, "CommandDefinition", "System.Data.EntityClient.EntityCommandDefinition"); + + // get private readonly IColumnMapGenerator EntityCommandDefinition._columnMapGenerator; + // of actual type private sealed class + // System.Data.EntityClient.EntityCommandDefinition.ConstantColumnMapGenerator + object columnMapGenerator; + try + { + columnMapGenerator = GetField(commandDefinition, "_columnMapGenerator", "System.Data.EntityClient.EntityCommandDefinition+ConstantColumnMapGenerator"); + } + catch (EFChangedException) + { + columnMapGenerator = GetField(commandDefinition, "_columnMapGenerators", "System.Data.EntityClient.EntityCommandDefinition+ConstantColumnMapGenerator", 0); + } + + // get private readonly ColumnMap ConstantColumnMapGenerator._columnMap; + // of actual type internal class + // System.Data.Query.InternalTrees.SimpleCollectionColumnMap + object columnMap = GetField(columnMapGenerator, "_columnMap", "System.Data.Query.InternalTrees.SimpleCollectionColumnMap"); + + // get internal ColumnMap CollectionColumnMap.Element; + // of actual type internal class + // System.Data.Query.InternalTrees.RecordColumnMap + object columnMapElement = GetProperty(columnMap, "Element", "System.Data.Query.InternalTrees.RecordColumnMap"); + + // get internal ColumnMap[] StructuredColumnMap.Properties; + // array of internal abstract class + // System.Data.Query.InternalTrees.ColumnMap + Array columnMapProperties = GetProperty(columnMapElement, "Properties", "System.Data.Query.InternalTrees.ColumnMap[]") as Array; + + int n = columnMapProperties.Length; + var cols = select.Substring(select.IndexOf("SELECT") + 6, select.IndexOf("FROM") - 6) + .Split(new char[] { ',' }, columnMapProperties.Length + 1); + StringBuilder innerSelectBuilder = new StringBuilder(select.Length * 2); + for (int j = 0; j < n; ++j) + { + // get value at index j in array + // of actual type internal class + // System.Data.Query.InternalTrees.ScalarColumnMap + object column = columnMapProperties.GetValue(j); + if (column == null || column.GetType().FullName != "System.Data.Query.InternalTrees.ScalarColumnMap") throw new EFChangedException(); + + //string colName = (string)GetProp(column, "Name"); + // can be used for more advanced bingings + + // get internal int ScalarColumnMap.ColumnPos; + object columnPositionOfAProperty = GetProperty(column, "ColumnPos", "System.Int32"); + + if (innerSelectBuilder.Length > 0) + { + innerSelectBuilder.Append(", "); + } + innerSelectBuilder.Append(cols[(int)columnPositionOfAProperty]); + } + innerSelectBuilder.Insert(0, "SELECT "); + innerSelectBuilder.Append(select.Substring(select.IndexOf("FROM"))); + string innserSelectSql = innerSelectBuilder.ToString(); // create parameters foreach (var objectParameter in objectQuery.Parameters) { @@ -603,7 +673,34 @@ private static string GetInsertSelectSql(ObjectQuery query, En command.Parameters.Add(parameter); } - return innerJoinSql; + return innserSelectSql; + } + + internal class EFChangedException : InvalidOperationException + { + internal EFChangedException() : base("Entity Framework internals have changed") { } + } + + static object GetProperty(object obj, string propName, string expectedType) + { + PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance); + if (prop == null) throw new EFChangedException(); + object value = prop.GetValue(obj, new object[0]); + if (value == null || value.GetType().FullName != expectedType) throw new EFChangedException(); + return value; + } + + static object GetField(object obj, string fieldName, string expectedType, int index = -1) + { + FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) throw new EFChangedException(); + var result = field.GetValue(obj); + if (index >= 0 && result is Array) + { + result = ((Array)result).GetValue(index); + } + if (result == null || result.GetType().FullName != expectedType) throw new EFChangedException(); + return result; } private static int CommitBulk(DbCommand command, StringBuilder insertLine, StringBuilder rows, List parameters) @@ -621,7 +718,7 @@ private static int CommitBulk(DbCommand command, StringBuilder insertLine, Strin parameters.ForEach(x => command.Parameters[parameterCount++].Value = x.Value); } return command.ExecuteNonQuery(); - } + } } /// From 101614a6ddf9e63e9be0fdc665dbcd56f67e4c1a Mon Sep 17 00:00:00 2001 From: wolffit Date: Mon, 11 Feb 2013 17:43:19 -0500 Subject: [PATCH 9/9] Added new dynamic operations --- .../Dynamic/DynamicQueryable.cs | 314 +++++++++++++----- 1 file changed, 231 insertions(+), 83 deletions(-) diff --git a/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs b/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs index 20dced3..cea0b8b 100644 --- a/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs +++ b/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs @@ -1,4 +1,4 @@ -//Copyright (C) Microsoft Corporation. All rights reserved. +//Copyright (C) Microsoft Corporation. All rights reserved. using System; using System.Collections.Generic; @@ -8,6 +8,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Threading; +using System.Security; namespace System.Linq.Dynamic { @@ -42,19 +43,47 @@ public static IQueryable Select(this IQueryable source, string selector, params source.Expression, Expression.Quote(lambda))); } + public static IQueryable Select(this IQueryable source, string selector, params object[] values) + where TResult : class + { + if (source == null) throw new ArgumentNullException("source"); + if (selector == null) throw new ArgumentNullException("selector"); + LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(TResult), selector, values); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Select", + new Type[] { source.ElementType, lambda.Body.Type }, + source.Expression, Expression.Quote(lambda))); + } + public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) { return (IQueryable)OrderBy((IQueryable)source, ordering, values); } public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) + { + return OrderBy(source, ordering, true, values); + } + + public static IQueryable OrderByDescending(this IQueryable source, string ordering, params object[] values) + { + return (IQueryable)OrderByDescending((IQueryable)source, ordering, values); + } + + public static IQueryable OrderByDescending(this IQueryable source, string ordering, params object[] values) + { + return OrderBy(source, ordering, false, values); + } + + private static IQueryable OrderBy(this IQueryable source, string ordering, bool ascending, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (ordering == null) throw new ArgumentNullException("ordering"); ParameterExpression[] parameters = new ParameterExpression[] { Expression.Parameter(source.ElementType, "") }; ExpressionParser parser = new ExpressionParser(parameters, ordering, values); - IEnumerable orderings = parser.ParseOrdering(); + IEnumerable orderings = parser.ParseOrdering(ascending); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; @@ -285,8 +314,7 @@ public Type GetDynamicClass(IEnumerable properties) Type type; if (!classes.TryGetValue(signature, out type)) { - type = CreateDynamicClass(signature.properties); - classes.Add(signature, type); + type = CreateDynamicClass(signature); } return type; } @@ -296,11 +324,16 @@ public Type GetDynamicClass(IEnumerable properties) } } - Type CreateDynamicClass(DynamicProperty[] properties) + Type CreateDynamicClass(Signature signature) { LockCookie cookie = rwLock.UpgradeToWriterLock(Timeout.Infinite); try { + Type type; + if (classes.TryGetValue(signature, out type)) + { + return type; + } string typeName = "DynamicClass" + (classCount + 1); #if ENABLE_LINQ_PARTIAL_TRUST new ReflectionPermission(PermissionState.Unrestricted).Assert(); @@ -309,11 +342,12 @@ Type CreateDynamicClass(DynamicProperty[] properties) { TypeBuilder tb = this.module.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, typeof(DynamicClass)); - FieldInfo[] fields = GenerateProperties(tb, properties); + FieldInfo[] fields = GenerateProperties(tb, signature.properties); GenerateEquals(tb, fields); GenerateGetHashCode(tb, fields); Type result = tb.CreateType(); classCount++; + classes.Add(signature, result); return result; } finally @@ -418,6 +452,7 @@ void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields) } } + [Serializable] public sealed class ParseException : Exception { int position; @@ -437,6 +472,14 @@ public override string ToString() { return string.Format(Res.ParseExceptionFormat, Message, position); } + /* + [SecurityCritical(SecurityCriticalScope.Explicit)] + public override void GetObjectData(Runtime.Serialization.SerializationInfo info, Runtime.Serialization.StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("position", position); + } + */ } internal class ExpressionParser @@ -481,7 +524,9 @@ enum TokenId LessGreater, DoubleEqual, GreaterThanEqual, - DoubleBar + DoubleBar, + IsType, + In, } interface ILogicalSignatures @@ -563,6 +608,7 @@ interface INotSignatures interface IEnumerableSignatures { + IQueryable OfType() where T : class; void Where(bool predicate); void Any(); void Any(bool predicate); @@ -630,6 +676,7 @@ interface IEnumerableSignatures IDictionary externals; Dictionary literals; ParameterExpression it; + Type resultType; string text; int textPos; int textLen; @@ -684,6 +731,7 @@ void AddSymbol(string name, object value) public Expression Parse(Type resultType) { + this.resultType = resultType; int exprPos = token.pos; Expression expr = ParseExpression(); if (resultType != null) @@ -694,16 +742,16 @@ public Expression Parse(Type resultType) } #pragma warning disable 0219 - public IEnumerable ParseOrdering() + public IEnumerable ParseOrdering(bool ascending) { List orderings = new List(); while (true) { Expression expr = ParseExpression(); - bool ascending = true; if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) { NextToken(); + ascending = true; } else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) { @@ -766,87 +814,124 @@ Expression ParseLogicalAnd() return left; } - // =, ==, !=, <>, >, >=, <, <= operators + // =, ==, !=, <>, >, >=, <, <=, is operators Expression ParseComparison() { Expression left = ParseAdditive(); while (token.id == TokenId.Equal || token.id == TokenId.DoubleEqual || token.id == TokenId.ExclamationEqual || token.id == TokenId.LessGreater || token.id == TokenId.GreaterThan || token.id == TokenId.GreaterThanEqual || - token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual) + token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual || + token.id == TokenId.IsType || token.id == TokenId.In) { Token op = token; NextToken(); - Expression right = ParseAdditive(); - bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual || - op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater; - if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) + if (op.id == TokenId.IsType) { - if (left.Type != right.Type) - { - if (left.Type.IsAssignableFrom(right.Type)) - { - right = Expression.Convert(right, left.Type); - } - else if (right.Type.IsAssignableFrom(left.Type)) - { - left = Expression.Convert(left, right.Type); - } - else - { - throw IncompatibleOperandsError(op.text, left, right, op.pos); - } - } + Type right = ParseType(); + left = GenerateTypeIs(left, right); } - else if (IsEnumType(left.Type) || IsEnumType(right.Type)) + else { - if (left.Type != right.Type) + Expression right = ParseAdditive(); + bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual || + op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater; + if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) { - Expression e; - if ((e = PromoteExpression(right, left.Type, true)) != null) + if (left.Type != right.Type) { - right = e; - } - else if ((e = PromoteExpression(left, right.Type, true)) != null) - { - left = e; + if (left.Type.IsAssignableFrom(right.Type)) + { + right = Expression.Convert(right, left.Type); + } + else if (right.Type.IsAssignableFrom(left.Type)) + { + left = Expression.Convert(left, right.Type); + } + else + { + throw IncompatibleOperandsError(op.text, left, right, op.pos); + } } - else + } + else if (IsEnumType(left.Type) || IsEnumType(right.Type)) + { + if (left.Type != right.Type) { - throw IncompatibleOperandsError(op.text, left, right, op.pos); + Expression e; + if ((e = PromoteExpression(right, left.Type, true)) != null) + { + right = e; + } + else if ((e = PromoteExpression(left, right.Type, true)) != null) + { + left = e; + } + else + { + throw IncompatibleOperandsError(op.text, left, right, op.pos); + } } } + else if (op.id == TokenId.In) + { + // TODO - Make sure Right.Type is a collection of objects of the same type as Left.Type + } + else + { + CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), + op.text, ref left, ref right, op.pos); + } + switch (op.id) + { + case TokenId.In: + left = GenerateIn(left, right); + break; + case TokenId.Equal: + case TokenId.DoubleEqual: + left = GenerateEqual(left, right); + break; + case TokenId.ExclamationEqual: + case TokenId.LessGreater: + left = GenerateNotEqual(left, right); + break; + case TokenId.GreaterThan: + left = GenerateGreaterThan(left, right); + break; + case TokenId.GreaterThanEqual: + left = GenerateGreaterThanEqual(left, right); + break; + case TokenId.LessThan: + left = GenerateLessThan(left, right); + break; + case TokenId.LessThanEqual: + left = GenerateLessThanEqual(left, right); + break; + } } - else - { - CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), - op.text, ref left, ref right, op.pos); - } - switch (op.id) + } + return left; + } + + // the 'is' operator, convert the right-value expression to a Type + Type ParseType() + { + string type; + if (token.id == TokenId.StringLiteral) + { + type = token.text; + } + else + { + StringBuilder sb = new StringBuilder(); + while (token.id == TokenId.Dot || token.id == TokenId.Comma || token.id == TokenId.Identifier) { - case TokenId.Equal: - case TokenId.DoubleEqual: - left = GenerateEqual(left, right); - break; - case TokenId.ExclamationEqual: - case TokenId.LessGreater: - left = GenerateNotEqual(left, right); - break; - case TokenId.GreaterThan: - left = GenerateGreaterThan(left, right); - break; - case TokenId.GreaterThanEqual: - left = GenerateGreaterThanEqual(left, right); - break; - case TokenId.LessThan: - left = GenerateLessThan(left, right); - break; - case TokenId.LessThanEqual: - left = GenerateLessThanEqual(left, right); - break; + sb.Append(token.text); + NextToken(); } + type = sb.ToString(); } - return left; + return Type.GetType(type.ToString(), true); } // +, -, & operators @@ -1178,7 +1263,7 @@ Expression ParseNew() } ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); NextToken(); - Type type = DynamicExpression.CreateClass(properties); + Type type = resultType ?? DynamicExpression.CreateClass(properties); MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); @@ -1320,29 +1405,63 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa ParameterExpression outerIt = it; ParameterExpression innerIt = Expression.Parameter(elementType, ""); it = innerIt; - Expression[] args = ParseArgumentList(); - it = outerIt; - MethodBase signature; - if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) - throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); - Type[] typeArgs; - if (signature.Name == "Min" || signature.Name == "Max") + Expression[] args; + if (methodName == "OfType") { - typeArgs = new Type[] { elementType, args[0].Type }; + args = ParseTypeArgumentList(innerIt); } else { - typeArgs = new Type[] { elementType }; + args = ParseArgumentList(); } - if (args.Length == 0) + it = outerIt; + MethodBase signature; + if (methodName == "OfType") { - args = new Expression[] { instance }; + ConstantExpression arg = args[0] as ConstantExpression; + MethodInfo methodInfo = typeof(Enumerable).GetMethod("OfType").MakeGenericMethod(arg.Value as Type); + return Expression.Call(null, methodInfo, instance); } else { - args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) }; + if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) + throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); + Type[] typeArgs; + if (signature.Name == "Min" || signature.Name == "Max") + { + typeArgs = new Type[] { elementType, args[0].Type }; + } + else + { + typeArgs = new Type[] { elementType }; + } + if (args.Length == 0) + { + args = new Expression[] { instance }; + } + else + { + args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) }; + } + return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); } - return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); + } + + Expression[] ParseTypeArgumentList(ParameterExpression innerIt) + { + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + Expression[] args = token.id != TokenId.CloseParen ? ParseTypeArguments(innerIt) : new Expression[0]; + ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); + NextToken(); + return args; + } + + Expression[] ParseTypeArguments(ParameterExpression innerIt) + { + List argList = new List(); + argList.Add(Expression.Constant(ParseType())); + return argList.ToArray(); } Expression[] ParseArgumentList() @@ -1891,6 +2010,23 @@ Expression GenerateEqual(Expression left, Expression right) return Expression.Equal(left, right); } + private Expression GenerateIn(Expression left, Expression right) + { + var methods = typeof(Enumerable).GetMethods().Where(x => x.Name == "Contains" && x.GetParameters().Count() == 2); + MethodInfo methodInfo = methods.First().MakeGenericMethod(left.Type); + return Expression.Call(null, methodInfo, right, left); + } + + Expression GenerateTypeIs(Expression left, Type right) + { + return Expression.TypeIs(left, right); + } + + Expression GenerateTypeAs(Expression left, Type right) + { + return Expression.TypeAs(left, right); + } + Expression GenerateNotEqual(Expression left, Expression right) { return Expression.NotEqual(left, right); @@ -2144,7 +2280,19 @@ void NextToken() { NextChar(); } while (Char.IsLetterOrDigit(ch) || ch == '_'); - t = TokenId.Identifier; + var subText = text.Substring(tokenPos, textPos - tokenPos); + switch (subText) + { + case "isType": + t = TokenId.IsType; + break; + case "in": + t = TokenId.In; + break; + default: + t = TokenId.Identifier; + break; + } break; } if (Char.IsDigit(ch))