C# NOSQL

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Text.RegularExpressions;

namespace Rocky.Data
{
    /// <summary>
    /// BusinessObject to PersistentObject Queryable
    /// </summary>
    public abstract class EntityContext<T> : Disposable where T : class, new()
    {
        #region StaticMembers
        internal static readonly bool EnableLog;
        private static Hashtable _stored;
        private static Regex _regWithNoLock = new Regex(@"(] AS \[t\d+\])", RegexOptions.Compiled);

        static EntityContext()
        {
            _stored = Hashtable.Synchronized(new Hashtable());
            EnableLog = bool.TrueString.Equals(System.Configuration.ConfigurationManager.AppSettings["DbEnableLog"], StringComparison.OrdinalIgnoreCase);
        }

        protected static void SetDataTypeMap(Type bizType, Type dataType)
        {
            _stored[bizType] = dataType;
        }
        #endregion

        #region Fields
        private object _boxedContext;
        private DataContext _context;
        private IDictionary _trackerItems;
        #endregion

        #region Properties
        /// <summary>
        /// 延迟提交数量
        /// </summary>
        public byte LazySize { get; set; }
        protected T Context
        {
            get
            {
                base.CheckDisposed();
                return (T)_boxedContext;
            }
        }
        private IDictionary TrackerItems
        {
            get
            {
                if (_trackerItems == null)
                {
                    object services = _context.GetType().GetProperty("Services", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty)
                        .GetValue(_context, null);
                    var fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
                    object tracker = services.GetType().GetField("tracker", fieldFlags).GetValue(services);
                    _trackerItems = (IDictionary)tracker.GetType().GetField("items", fieldFlags).GetValue(tracker);
                }
                return _trackerItems;
            }
        }
        #endregion

        #region Constructor
        public EntityContext(T context = null)
        {
            if (context == null)
            {
                context = new T();
            }
            _boxedContext = context;
            _context = _boxedContext as DataContext;
            if (_context == null)
            {
                throw new InvalidOperationException("The T must be DataContext.");
            }
            if (EnableLog)
            {
                _context.Log = new DbLogger();
            }
        }

        protected override void DisposeInternal(bool disposing)
        {
            if (disposing)
            {
                if (_context.ObjectTrackingEnabled)
                {
                    this.LazySize = 0;
                    this.SaveChanges();
                }
                _context.Dispose();
            }
            //_context = null;
            _boxedContext = null;
        }
        #endregion

        #region Methods
        /// <summary>
        /// BO Type to DO Type
        /// </summary>
        /// <param name="bizType"></param>
        /// <returns></returns>
        protected virtual Type MapDataType(Type bizType)
        {
            Type dataType = (Type)_stored[bizType];
            if (dataType == null)
            {
                throw new InvalidOperationException("bizType");
            }
            return dataType;
        }

        /// <summary>
        /// BO to DO
        /// </summary>
        /// <param name="bizObj"></param>
        /// <returns></returns>
        protected abstract void AssignDataObject(object dataObj, object bizObj);

        protected void Create<TEntity>(TEntity bizObj, Action<object> dataAction = null) where TEntity : class
        {
            Type fromType = typeof(TEntity), toType = MapDataType(fromType);
            var dataObj = Activator.CreateInstance(toType);
            AssignDataObject(dataObj, bizObj);
            _context.GetTable(toType).InsertOnSubmit(dataObj);

            if (dataAction != null)
            {
                dataAction(dataObj);
            }
        }

        protected void Update<TEntity>(TEntity bizObj, Action<object> dataAction = null) where TEntity : class
        {
            Type fromType = typeof(TEntity), toType = MapDataType(fromType);
            var metaType = _context.Mapping.GetMetaType(toType);
            var source = _context.GetTable(toType);
            var parameters = Expression.Parameter(toType, "item");
            PropertyInfo prop = fromType.GetProperty(metaType.IdentityMembers[0].Name, PropertyAccess.PropertyBinding);
            Expression body = Expression.Equal(Expression.Property(parameters, (PropertyInfo)metaType.IdentityMembers[0].Member), Expression.Constant(prop.GetValue(bizObj, null), prop.PropertyType));
            for (int i = 1; i < metaType.IdentityMembers.Count; i++)
            {
                prop = fromType.GetProperty(metaType.IdentityMembers[i].Name, PropertyAccess.PropertyBinding);
                body = Expression.AndAlso(body, Expression.Equal(Expression.Property(parameters, (PropertyInfo)metaType.IdentityMembers[i].Member), Expression.Constant(prop.GetValue(bizObj, null), prop.PropertyType)));
            }
            var predicate = Expression.Lambda(body, parameters);
            var dataObj = source.Provider.CreateQuery(
                Expression.Call(typeof(Queryable), "Where",
                new Type[] { toType },
                new Expression[] { source.Expression, Expression.Quote(predicate) })
                ).Cast<object>().SingleOrDefault();
            AssignDataObject(dataObj, bizObj);

            if (dataAction != null)
            {
                dataAction(dataObj);
            }
        }

        protected void Delete<TEntity>(TEntity bizObj, Action<object> dataAction = null) where TEntity : class
        {
            Type fromType = typeof(TEntity), toType = MapDataType(fromType);
            var dataObj = Activator.CreateInstance(toType);
            AssignDataObject(dataObj, bizObj);
            var table = _context.GetTable(toType);
            table.Attach(dataObj);
            table.DeleteOnSubmit(dataObj);

            if (dataAction != null)
            {
                dataAction(dataObj);
            }
        }

        protected TEntity QuerySingle<TEntity>(params object[] identityValues) where TEntity : class
        {
            if (identityValues.IsNullOrEmpty())
            {
                throw new ArgumentException("identityValues");
            }
            Type fromType = typeof(TEntity), toType = MapDataType(fromType);
            var metaType = _context.Mapping.GetMetaType(toType);
            if (metaType.IdentityMembers.Count != identityValues.Length)
            {
                throw new InvalidProgramException("IdentityMembers");
            }
            IQueryable<TEntity> source = Queryable<TEntity>();
            var parameters = Expression.Parameter(fromType, "item");
            Expression body = Expression.Equal(Expression.Property(parameters, fromType.GetMethod("get_" + metaType.IdentityMembers[0].Name)), Expression.Constant(identityValues[0], identityValues[0].GetType()));
            for (int i = 1; i < metaType.IdentityMembers.Count; i++)
            {
                body = Expression.AndAlso(body, Expression.Equal(Expression.Property(parameters, fromType.GetMethod("get_" + metaType.IdentityMembers[i].Name)), Expression.Constant(identityValues[i], identityValues[i].GetType())));
            }
            var predicate = Expression.Lambda(body, parameters);
            return source.Provider.CreateQuery<TEntity>(
                Expression.Call(typeof(Queryable), "Where",
                new Type[] { fromType },
                new Expression[] { source.Expression, Expression.Quote(predicate) })
                ).SingleOrDefault();
        }

        protected IQueryable<TEntity> Queryable<TEntity>(DataContext context = null) where TEntity : class
        {
            if (context == null)
            {
                context = _context;
            }

            Type fromType = typeof(TEntity), toType = MapDataType(fromType);
            string key = context.GetType().Name + fromType.FullName;
            Expression selector = (Expression)_stored[key];
            if (selector == null)
            {
                var parameters = Expression.Parameter(toType, "item");
                var q = from fromProperty in fromType.GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public)
                        join toProperty in toType.GetProperties() on fromProperty.Name equals toProperty.Name
                        let b = fromProperty.PropertyType.IsAssignableFrom(toProperty.PropertyType)
                        let p = fromProperty.DeclaringType == fromType ? fromProperty.GetSetMethod() : fromProperty.DeclaringType.GetProperty(fromProperty.Name).GetSetMethod()
                        select b ?
                            Expression.Bind(p, Expression.Property(parameters, toProperty.GetGetMethod())) :
                            Expression.Bind(p, Expression.Convert(Expression.Property(parameters, toProperty.GetGetMethod()), fromProperty.PropertyType));

                var body = Expression.MemberInit(Expression.New(fromType), q.ToArray());
                _stored[key] = selector = Expression.Lambda(body, parameters);
            }
            IQueryable source = context.GetTable(toType);
            return source.Provider.CreateQuery<TEntity>(
                Expression.Call(typeof(Queryable), "Select",
                new Type[] { toType, fromType },
                new Expression[] { source.Expression, Expression.Quote(selector) })
                );
        }

        protected virtual void SaveChanges()
        {
            base.CheckDisposed();

            byte lazySize = this.LazySize;
            if (lazySize > 0)
            {
                var trackerItems = this.TrackerItems;
                PropertyInfo prop = null;
                foreach (object value in trackerItems.Values)
                {
                    if (prop == null)
                    {
                        prop = value.GetType().GetProperty("IsNew", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty);
                    }
                    if (Convert.ToBoolean(prop.GetValue(value, null)))
                    {
                        goto save;
                    }
                }
                if (lazySize > trackerItems.Count)
                {
                    return;
                }
            }
        save:
            try
            {
                _context.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            //catch (DuplicateKeyException ex)
            //{
            //    this.TrackerItems.Remove(ex.Object);
            //    goto save;
            //}
            catch (ChangeConflictException)
            {
                foreach (var conflict in _context.ChangeConflicts)
                {
                    // 使用当前数据库中的值,覆盖Linq缓存中实体对象的值
                    //conflict.Resolve(RefreshMode.OverwriteCurrentValues);
                    // 使用Linq缓存中实体对象的值,覆盖当前数据库中的值
                    //conflict.Resolve(RefreshMode.KeepCurrentValues);
                    // 只更新实体对象中改变的字段的值,其他的保留不变
                    conflict.Resolve(RefreshMode.KeepChanges);
                }
                goto save;
            }
        }
        #endregion

        #region ExecuteQuery
        protected DbCommand PrepareCommand(IQueryable query, bool withNoLock)
        {
            var command = _context.GetCommand(query) as System.Data.SqlClient.SqlCommand;
            if (command == null)
            {
                throw new NotSupportedException("PrepareCommand WithNoLock");
            }
            if (withNoLock)
            {
                string cmdText = command.CommandText;
                IEnumerable<Match> matches = _regWithNoLock.Matches(cmdText).Cast<Match>().OrderByDescending(m => m.Index);
                foreach (Match m in matches)
                {
                    int splitIndex = m.Index + m.Value.Length;
                    cmdText =
                        cmdText.Substring(0, splitIndex) + " WITH (NOLOCK)" +
                        cmdText.Substring(splitIndex);
                }
                command.CommandText = cmdText;
            }
            return command;
        }

        /// <summary>
        /// LoadOptions
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="query"></param>
        /// <param name="withNoLock"></param>
        /// <returns></returns>
        public List<TEntity> ExecuteQuery<TEntity>(IQueryable query, bool withNoLock = false)
        {
            var cmd = PrepareCommand(query, withNoLock);
            bool isClosed = cmd.Connection.State == System.Data.ConnectionState.Closed;
            try
            {
                if (isClosed)
                {
                    cmd.Connection.Open();
                }
                using (var dr = cmd.ExecuteReader())
                {
                    return _context.Translate<TEntity>(dr).ToList();
                }
            }
            finally
            {
                if (isClosed)
                {
                    cmd.Connection.Close();
                }
            }
        }
        #endregion
    }
}
复制代码
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Rocky.Data
{
    internal class DbLogger : TextWriter
    {
        private log4net.ILog _logger;

        public DbLogger()
        {
            _logger = log4net.LogManager.GetLogger(this.GetType());
        }

        public override Encoding Encoding
        {
            get { return Encoding.UTF8; }
        }

        public override void Write(string value)
        {
            _logger.Debug(value);
        }
        public override void Write(char[] buffer, int index, int count)
        {
            Write(new string(buffer, index, count));
        }
    }
}
复制代码
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Linq.Expressions;

namespace Rocky.Data
{
    public static class ExpressionExtensions
    {
        public static Expression Visit<T>(
            this Expression exp,
            Func<T, Expression> visitor) where T : Expression
        {
            return ExpressionVisitor<T>.Visit(exp, visitor);
        }

        public static TExp Visit<T, TExp>(
            this TExp exp,
            Func<T, Expression> visitor)
            where T : Expression
            where TExp : Expression
        {
            return (TExp)ExpressionVisitor<T>.Visit(exp, visitor);
        }

        public static Expression<TDelegate> Visit<T, TDelegate>(
            this Expression<TDelegate> exp,
            Func<T, Expression> visitor) where T : Expression
        {
            return ExpressionVisitor<T>.Visit<TDelegate>(exp, visitor);
        }

        public static IQueryable<TSource> Visit<T, TSource>(
            this IQueryable<TSource> source,
            Func<T, Expression> visitor) where T : Expression
        {
            return source.Provider.CreateQuery<TSource>(ExpressionVisitor<T>.Visit(source.Expression, visitor));
        }
    }

    /// <summary>
    /// This class visits every Parameter expression in an expression tree and calls a delegate
    /// to optionally replace the parameter.  This is useful where two expression trees need to
    /// be merged (and they don't share the same ParameterExpressions).
    /// </summary>
    public class ExpressionVisitor<T> : ExpressionVisitor where T : Expression
    {
        Func<T, Expression> visitor;

        public ExpressionVisitor(Func<T, Expression> visitor)
        {
            this.visitor = visitor;
        }

        public static Expression Visit(
            Expression exp,
            Func<T, Expression> visitor)
        {
            return new ExpressionVisitor<T>(visitor).Visit(exp);
        }

        public static Expression<TDelegate> Visit<TDelegate>(
            Expression<TDelegate> exp,
            Func<T, Expression> visitor)
        {
            return (Expression<TDelegate>)new ExpressionVisitor<T>(visitor).Visit(exp);
        }

        public override Expression Visit(Expression exp)
        {
            if (exp is T && visitor != null) exp = visitor((T)exp);

            return base.Visit(exp);
        }
    }
}
复制代码
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;

namespace Rocky.Data
{
    public static class LinqToSqlExtensions
    {
        #region DumpCSV
        /// <summary>
        /// Creates a *.csv file from an IQueryable query, dumping out the 'simple' properties/fields.
        /// </summary>
        /// <param name="query">Represents a SELECT query to execute.</param>
        /// <param name="fileName">The name of the file to create.</param>
        /// <remarks>
        /// <para>If the <paramref name="query"/> contains any properties that are entity sets (i.e. rows from a FK relationship) the values will not be dumped to the file.</para>
        /// <para>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</para>
        /// </remarks>
        public static void DumpCSV(this IQueryable query, string fileName)
        {
            using (var output = new FileStream(fileName, FileMode.Create))
            using (var writer = new StreamWriter(output))
            {
                var firstRow = true;

                PropertyInfo[] properties = null;
                FieldInfo[] fields = null;
                Type type = null;
                bool typeIsAnonymous = false;

                foreach (var r in query)
                {
                    if (type == null)
                    {
                        type = r.GetType();
                        typeIsAnonymous = type.IsAnonymous();
                        properties = type.GetProperties();
                        fields = type.GetFields();
                    }

                    var firstCol = true;

                    if (typeIsAnonymous)
                    {
                        if (firstRow)
                        {
                            foreach (var p in properties)
                            {
                                if (!firstCol) writer.Write(",");
                                else { firstCol = false; }

                                writer.Write(p.Name);
                            }
                            writer.WriteLine();
                        }
                        firstRow = false;
                        firstCol = true;

                        foreach (var p in properties)
                        {
                            if (!firstCol) writer.Write(",");
                            else { firstCol = false; }
                            DumpValue(p.GetValue(r, null), writer);
                        }
                    }
                    else
                    {
                        if (firstRow)
                        {
                            foreach (var p in fields)
                            {
                                if (!firstCol) writer.Write(",");
                                else { firstCol = false; }

                                writer.Write(p.Name);
                            }
                            writer.WriteLine();
                        }
                        firstRow = false;
                        firstCol = true;

                        foreach (var p in fields)
                        {
                            if (!firstCol) writer.Write(",");
                            else { firstCol = false; }

                            DumpValue(p.GetValue(r), writer);
                        }
                    }

                    writer.WriteLine();
                }
            }
        }

        private static void DumpValue(object v, StreamWriter writer)
        {
            if (v != null)
            {
                switch (Type.GetTypeCode(v.GetType()))
                {
                    // csv encode the value
                    case TypeCode.String:
                        string value = (string)v;
                        if (value.Contains(",") || value.Contains('"') || value.Contains("\n"))
                        {
                            value = value.Replace("\"", "\"\"");

                            if (value.Length > 31735)
                            {
                                value = value.Substring(0, 31732) + "...";
                            }
                            writer.Write("\"" + value + "\"");
                        }
                        else
                        {
                            writer.Write(value);
                        }
                        break;
                    default: writer.Write(v); break;
                }
            }
        }

        private static bool IsAnonymous(this Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            // HACK: The only way to detect anonymous types right now.
            return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
                       && type.IsGenericType && type.Name.Contains("AnonymousType")
                       && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
                       && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
        }
        #endregion

        #region SelectMutlipleResults
        /// <summary>
        /// Batches together multiple IQueryable queries into a single DbCommand and returns all data in
        /// a single roundtrip to the database.
        /// </summary>
        /// <param name="context">The DataContext to execute the batch select against.</param>
        /// <param name="queries">Represents a collections of SELECT queries to execute.</param>
        /// <returns>Returns an IMultipleResults object containing all results.</returns>
        public static IMultipleResults SelectMutlipleResults(this DataContext context, IQueryable[] queries)
        {
            var commandList = new List<DbCommand>();

            foreach (IQueryable query in queries)
            {
                var command = context.GetCommand(query);
                commandList.Add(command);
            }

            SqlCommand batchCommand = CombineCommands(commandList);
            batchCommand.Connection = context.Connection as SqlConnection;

            DbDataReader dr = null;

            if (batchCommand.Connection.State == ConnectionState.Closed)
            {
                batchCommand.Connection.Open();
                dr = batchCommand.ExecuteReader(CommandBehavior.CloseConnection);
            }
            else
            {
                dr = batchCommand.ExecuteReader();
            }

            IMultipleResults mr = context.Translate(dr);
            return mr;
        }

        /// <summary>
        /// Combines multiple SELECT commands into a single SqlCommand so that all statements can be executed in a 
        /// single roundtrip to the database and return multiple result sets.
        /// </summary>
        /// <param name="commandList">Represents a collection of commands to be batched together.</param>
        /// <returns>Returns a single SqlCommand that executes all SELECT statements at once.</returns>
        private static SqlCommand CombineCommands(List<DbCommand> selectCommands)
        {
            SqlCommand batchCommand = new SqlCommand();
            SqlParameterCollection newParamList = batchCommand.Parameters;

            int commandCount = 0;

            foreach (DbCommand cmd in selectCommands)
            {
                string commandText = cmd.CommandText;
                DbParameterCollection paramList = cmd.Parameters;
                int paramCount = paramList.Count;

                for (int currentParam = paramCount - 1; currentParam >= 0; currentParam--)
                {
                    DbParameter param = paramList[currentParam];
                    DbParameter newParam = (DbParameter)((ICloneable)param).Clone();
                    string newParamName = param.ParameterName.Replace("@", string.Format("@{0}_", commandCount));
                    commandText = commandText.Replace(param.ParameterName, newParamName);
                    newParam.ParameterName = newParamName;
                    newParamList.Add(newParam);
                }
                if (batchCommand.CommandText.Length > 0)
                {
                    batchCommand.CommandText += ";";
                }
                batchCommand.CommandText += commandText;
                commandCount++;
            }

            return batchCommand;
        }
        #endregion

        /// <summary>
        /// Immediately deletes all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <typeparam name="TPrimaryKey">Represents the object type for the primary key of rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="primaryKey">Represents the primary key of the item to be removed from <paramref name="table"/>.</param>
        /// <returns>The number of rows deleted from the database (maximum of 1).</returns>
        /// <remarks>
        /// <para>If the primary key for <paramref name="table"/> is a composite key, <paramref name="primaryKey"/> should be an anonymous type with property names mapping to the property names of objects of type <typeparamref name="TEntity"/>.</para>
        /// </remarks>
        public static int DeleteByPK<TEntity>(this Table<TEntity> table, object primaryKey) where TEntity : class
        {
            DbCommand delete = table.GetDeleteByPKCommand<TEntity>(primaryKey);

            var parameters = from p in delete.Parameters.Cast<DbParameter>()
                             select p.Value;

            return table.Context.ExecuteCommand(delete.CommandText, parameters.ToArray());
        }

        /// <summary>Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete a entity row via the supplied primary key.</summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <typeparam name="TPrimaryKey">Represents the object type for the primary key of rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="primaryKey">Represents the primary key of the item to be removed from <paramref name="table"/>.</param>
        /// <returns>Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete a entity row via the supplied primary key.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string DeleteByPKPreview<TEntity>(this Table<TEntity> table, object primaryKey) where TEntity : class
        {
            DbCommand delete = table.GetDeleteByPKCommand<TEntity>(primaryKey);
            return delete.PreviewCommandText(false) + table.Context.GetLog();
        }

        private static DbCommand GetDeleteByPKCommand<TEntity>(this Table<TEntity> table, object primaryKey) where TEntity : class
        {
            Type type = primaryKey.GetType();
            bool typeIsAnonymous = type.IsAnonymous();
            string dbName = table.GetDbName();

            var metaTable = table.Context.Mapping.GetTable(typeof(TEntity));

            var keys = from mdm in metaTable.RowType.DataMembers
                       where mdm.IsPrimaryKey
                       select new { mdm.MappedName, mdm.Name, mdm.Type };

            SqlCommand deleteCommand = new SqlCommand();
            deleteCommand.Connection = table.Context.Connection as SqlConnection;

            var whereSB = new StringBuilder();

            foreach (var key in keys)
            {
                // Add new parameter with massaged name to avoid clashes.
                whereSB.AppendFormat("[{0}] = @p{1}, ", key.MappedName, deleteCommand.Parameters.Count);

                object value = primaryKey;
                if (typeIsAnonymous || (type.IsClass && type != typeof(string)))
                {
                    if (typeIsAnonymous)
                    {
                        PropertyInfo property = type.GetProperty(key.Name);

                        if (property == null)
                        {
                            throw new ArgumentOutOfRangeException(string.Format("The property {0} which is defined as part of the primary key for {1} was not supplied by the parameter primaryKey.", key.Name, metaTable.TableName));
                        }

                        value = property.GetValue(primaryKey, null);
                    }
                    else
                    {
                        FieldInfo field = type.GetField(key.Name);

                        if (field == null)
                        {
                            throw new ArgumentOutOfRangeException(string.Format("The property {0} which is defined as part of the primary key for {1} was not supplied by the parameter primaryKey.", key.Name, metaTable.TableName));
                        }

                        value = field.GetValue(primaryKey);
                    }

                    if (value.GetType() != key.Type)
                    {
                        throw new InvalidCastException(string.Format("The property {0} ({1}) does not have the same type as {2} ({3}).", key.Name, value.GetType(), key.MappedName, key.Type));
                    }
                }
                else if (value.GetType() != key.Type)
                {
                    throw new InvalidCastException(string.Format("The value supplied in primaryKey ({0}) does not have the same type as {1} ({2}).", value.GetType(), key.MappedName, key.Type));
                }

                deleteCommand.Parameters.Add(new SqlParameter(string.Format("@p{0}", deleteCommand.Parameters.Count), value));
            }

            string wherePK = whereSB.ToString();

            if (wherePK == "")
            {
                throw new MissingPrimaryKeyException(string.Format("{0} does not have a primary key defined.  Batch updating/deleting can not be used for tables without a primary key.", metaTable.TableName));
            }

            deleteCommand.CommandText = string.Format("DELETE {0}\r\nWHERE {1}", dbName, wherePK.Substring(0, wherePK.Length - 2));

            return deleteCommand;
        }

        /// <summary>
        /// Immediately deletes all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <param name="entities">Represents the collection of items which are to be removed from <paramref name="table"/>.</param>
        /// <returns>The number of rows deleted from the database.</returns>
        /// <remarks>
        /// <para>Similiar to stored procedures, and opposite from DeleteAllOnSubmit, rows provided in <paramref name="entities"/> will be deleted immediately with no need to call <see cref="DataContext.SubmitChanges()"/>.</para>
        /// <para>Additionally, to improve performance, instead of creating a delete command for each item in <paramref name="entities"/>, a single delete command is created.</para>
        /// </remarks>
        public static int DeleteBatch<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities) where TEntity : class
        {
            DbCommand delete = table.GetDeleteBatchCommand<TEntity>(entities);

            var parameters = from p in delete.Parameters.Cast<DbParameter>()
                             select p.Value;

            return table.Context.ExecuteCommand(delete.CommandText, parameters.ToArray());
        }

        /// <summary>
        /// Immediately deletes all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <param name="filter">Represents a filter of items to be updated in <paramref name="table"/>.</param>
        /// <returns>The number of rows deleted from the database.</returns>
        /// <remarks>
        /// <para>Similiar to stored procedures, and opposite from DeleteAllOnSubmit, rows provided in <paramref name="entities"/> will be deleted immediately with no need to call <see cref="DataContext.SubmitChanges()"/>.</para>
        /// <para>Additionally, to improve performance, instead of creating a delete command for each item in <paramref name="entities"/>, a single delete command is created.</para>
        /// </remarks>
        public static int DeleteBatch<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            return table.DeleteBatch(table.Where(filter));
        }

        /// <summary>
        /// Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <param name="entities">Represents the collection of items which are to be removed from <paramref name="table"/>.</param>
        /// <returns>Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string DeleteBatchPreview<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities) where TEntity : class
        {
            DbCommand delete = table.GetDeleteBatchCommand<TEntity>(entities);
            return delete.PreviewCommandText(false) + table.Context.GetLog();
        }

        /// <summary>
        /// Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <param name="filter">Represents a filter of items to be updated in <paramref name="table"/>.</param>
        /// <returns>Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string DeleteBatchPreview<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            return table.DeleteBatchPreview(table.Where(filter));
        }

        /// <summary>
        /// Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <param name="entities">Represents the collection of items which are to be removed from <paramref name="table"/>.</param>
        /// <returns>Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.</returns>
        /// <remarks>This method is useful for debugging purposes or when LINQ generated queries need to be passed to developers without LINQ/LINQPad.</remarks>
        public static string DeleteBatchSQL<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities) where TEntity : class
        {
            DbCommand delete = table.GetDeleteBatchCommand<TEntity>(entities);
            return delete.PreviewCommandText(true);
        }

        /// <summary>
        /// Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be deleted.</param>
        /// <param name="filter">Represents a filter of items to be updated in <paramref name="table"/>.</param>
        /// <returns>Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to delete all entities from the collection with a single delete command.</returns>
        /// <remarks>This method is useful for debugging purposes or when LINQ generated queries need to be passed to developers without LINQ/LINQPad.</remarks>
        public static string DeleteBatchSQL<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            return table.DeleteBatchSQL(table.Where(filter));
        }

        /// <summary>
        /// Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be updated.</param>
        /// <param name="entities">Represents the collection of items which are to be updated in <paramref name="table"/>.</param>
        /// <param name="evaluator">A Lambda expression returning a <typeparamref name="TEntity"/> that defines the update assignments to be performed on each item in <paramref name="entities"/>.</param>
        /// <returns>Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string UpdateBatchPreview<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            DbCommand update = table.GetUpdateBatchCommand<TEntity>(entities, evaluator);
            return update.PreviewCommandText(false) + table.Context.GetLog();
        }

        /// <summary>
        /// Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be updated.</param>
        /// <param name="filter">Represents a filter of items to be updated in <paramref name="table"/>.</param>
        /// <param name="evaluator">A Lambda expression returning a <typeparamref name="TEntity"/> that defines the update assignments to be performed on each item in <paramref name="entities"/>.</param>
        /// <returns>Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string UpdateBatchPreview<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> filter, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            return table.UpdateBatchPreview(table.Where(filter), evaluator);
        }

        /// <summary>
        /// Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be updated.</param>
        /// <param name="entities">Represents the collection of items which are to be updated in <paramref name="table"/>.</param>
        /// <param name="evaluator">A Lambda expression returning a <typeparamref name="TEntity"/> that defines the update assignments to be performed on each item in <paramref name="entities"/>.</param>
        /// <returns>Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.</returns>
        /// <remarks>This method is useful for debugging purposes or when LINQ generated queries need to be passed to developers without LINQ/LINQPad.</remarks>
        public static string UpdateBatchSQL<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            DbCommand update = table.GetUpdateBatchCommand<TEntity>(entities, evaluator);
            return update.PreviewCommandText(true);
        }

        /// <summary>
        /// Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be updated.</param>
        /// <param name="filter">Represents a filter of items to be updated in <paramref name="table"/>.</param>
        /// <param name="evaluator">A Lambda expression returning a <typeparamref name="TEntity"/> that defines the update assignments to be performed on each item in <paramref name="entities"/>.</param>
        /// <returns>Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to update all entities from the collection with a single update command.</returns>
        /// <remarks>This method is useful for debugging purposes or when LINQ generated queries need to be passed to developers without LINQ/LINQPad.</remarks>
        public static string UpdateBatchSQL<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> filter, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            return table.UpdateBatchSQL(table.Where(filter), evaluator);
        }

        /// <summary>
        /// Immediately updates all entities in the collection with a single update command based on a <typeparamref name="TEntity"/> created from a Lambda expression.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be updated.</param>
        /// <param name="entities">Represents the collection of items which are to be updated in <paramref name="table"/>.</param>
        /// <param name="evaluator">A Lambda expression returning a <typeparamref name="TEntity"/> that defines the update assignments to be performed on each item in <paramref name="entities"/>.</param>
        /// <returns>The number of rows updated in the database.</returns>
        /// <remarks>
        /// <para>Similiar to stored procedures, and opposite from similiar InsertAllOnSubmit, rows provided in <paramref name="entities"/> will be updated immediately with no need to call <see cref="DataContext.SubmitChanges()"/>.</para>
        /// <para>Additionally, to improve performance, instead of creating an update command for each item in <paramref name="entities"/>, a single update command is created.</para>
        /// </remarks>
        public static int UpdateBatch<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            DbCommand update = table.GetUpdateBatchCommand<TEntity>(entities, evaluator);

            var parameters = from p in update.Parameters.Cast<DbParameter>()
                             select p.Value;
            return table.Context.ExecuteCommand(update.CommandText, parameters.ToArray());
        }

        /// <summary>
        /// Immediately updates all entities in the collection with a single update command based on a <typeparamref name="TEntity"/> created from a Lambda expression.
        /// </summary>
        /// <typeparam name="TEntity">Represents the object type for rows contained in <paramref name="table"/>.</typeparam>
        /// <param name="table">Represents a table for a particular type in the underlying database containing rows are to be updated.</param>
        /// <param name="filter">Represents a filter of items to be updated in <paramref name="table"/>.</param>
        /// <param name="evaluator">A Lambda expression returning a <typeparamref name="TEntity"/> that defines the update assignments to be performed on each item in <paramref name="entities"/>.</param>
        /// <returns>The number of rows updated in the database.</returns>
        /// <remarks>
        /// <para>Similiar to stored procedures, and opposite from similiar InsertAllOnSubmit, rows provided in <paramref name="entities"/> will be updated immediately with no need to call <see cref="DataContext.SubmitChanges()"/>.</para>
        /// <para>Additionally, to improve performance, instead of creating an update command for each item in <paramref name="entities"/>, a single update command is created.</para>
        /// </remarks>
        public static int UpdateBatch<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> filter, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            return table.UpdateBatch(table.Where(filter), evaluator);
        }

        /// <summary>
        /// Returns the Transact SQL string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to perform the query's select statement.
        /// </summary>
        /// <param name="context">The DataContext to execute the batch select against.</param>
        /// <param name="query">Represents the SELECT query to execute.</param>
        /// <returns>Returns the Transact SQL string representation of the <see cref="DbCommand.CommandText"/> along with <see cref="DbCommand.Parameters"/> if present.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string PreviewSQL(this DataContext context, IQueryable query)
        {
            var cmd = context.GetCommand(query);
            return cmd.PreviewCommandText(true);
        }

        /// <summary>
        /// Returns a string representation the LINQ <see cref="IProvider"/> command text and parameters used that would be issued to perform the query's select statement.
        /// </summary>
        /// <param name="context">The DataContext to execute the batch select against.</param>
        /// <param name="query">Represents the SELECT query to execute.</param>
        /// <returns>Returns a string representation of the <see cref="DbCommand.CommandText"/> along with <see cref="DbCommand.Parameters"/> if present.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        public static string PreviewCommandText(this DataContext context, IQueryable query)
        {
            var cmd = context.GetCommand(query);
            return cmd.PreviewCommandText(false);
        }

        /// <summary>
        /// Returns a string representation of the <see cref="DbCommand.CommandText"/> along with <see cref="DbCommand.Parameters"/> if present.
        /// </summary>
        /// <param name="cmd">The <see cref="DbCommand"/> to analyze.</param>
        /// <param name="forTransactSQL">Whether or not the text should be formatted as 'logging' similiar to LINQ to SQL output, or in valid Transact SQL syntax ready for use with a 'query analyzer' type tool.</param>
        /// <returns>Returns a string representation of the <see cref="DbCommand.CommandText"/> along with <see cref="DbCommand.Parameters"/> if present.</returns>
        /// <remarks>This method is useful for debugging purposes or when used in other utilities such as LINQPad.</remarks>
        private static string PreviewCommandText(this DbCommand cmd, bool forTransactSQL)
        {
            var output = new StringBuilder();

            if (!forTransactSQL) output.AppendLine(cmd.CommandText);

            foreach (DbParameter parameter in cmd.Parameters)
            {
                int num = 0;
                int num2 = 0;
                PropertyInfo property = parameter.GetType().GetProperty("Precision");
                if (property != null)
                {
                    num = (int)Convert.ChangeType(property.GetValue(parameter, null), typeof(int), CultureInfo.InvariantCulture);
                }
                PropertyInfo info2 = parameter.GetType().GetProperty("Scale");
                if (info2 != null)
                {
                    num2 = (int)Convert.ChangeType(info2.GetValue(parameter, null), typeof(int), CultureInfo.InvariantCulture);
                }
                SqlParameter parameter2 = parameter as SqlParameter;

                if (forTransactSQL)
                {
                    output.AppendFormat("DECLARE {0} {1}{2}; SET {0} = {3}\r\n",
                        new object[] { 
                                parameter.ParameterName, 
                                ( parameter2 == null ) ? parameter.DbType.ToString() : parameter2.SqlDbType.ToString(), 
                                ( parameter.Size > 0 ) ? "( " + parameter.Size.ToString( CultureInfo.CurrentCulture ) + " )" : "", 
                                GetParameterTransactValue( parameter, parameter2 ) });
                }
                else
                {
                    output.AppendFormat("-- {0}: {1} {2} (Size = {3}; Prec = {4}; Scale = {5}) [{6}]\r\n", new object[] { parameter.ParameterName, parameter.Direction, (parameter2 == null) ? parameter.DbType.ToString() : parameter2.SqlDbType.ToString(), parameter.Size.ToString(CultureInfo.CurrentCulture), num, num2, (parameter2 == null) ? parameter.Value : parameter2.SqlValue });
                }
            }

            if (forTransactSQL) output.Append("\r\n" + cmd.CommandText);

            return output.ToString();
        }

        private static string GetParameterTransactValue(DbParameter parameter, SqlParameter parameter2)
        {
            if (parameter2 == null) return parameter.Value.ToString(); // Not going to deal with NON SQL parameters.

            switch (parameter2.SqlDbType)
            {
                case SqlDbType.Char:
                case SqlDbType.Date:
                case SqlDbType.DateTime:
                case SqlDbType.DateTime2:
                case SqlDbType.NChar:
                case SqlDbType.NText:
                case SqlDbType.NVarChar:
                case SqlDbType.SmallDateTime:
                case SqlDbType.Text:
                case SqlDbType.VarChar:
                case SqlDbType.UniqueIdentifier:
                    return string.Format("'{0}'", parameter2.SqlValue);

                default:
                    return parameter2.SqlValue.ToString();
            }
        }

        private static DbCommand GetDeleteBatchCommand<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities) where TEntity : class
        {
            var deleteCommand = table.Context.GetCommand(entities);
            deleteCommand.CommandText = string.Format("DELETE {0}\r\n", table.GetDbName()) + GetBatchJoinQuery<TEntity>(table, entities);
            return deleteCommand;
        }

        private static DbCommand GetUpdateBatchCommand<TEntity>(this Table<TEntity> table, IQueryable<TEntity> entities, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class
        {
            var updateCommand = table.Context.GetCommand(entities);

            var setSB = new StringBuilder();
            int memberInitCount = 1;

            // Process the MemberInitExpression (there should only be one in the evaluator Lambda) to convert the expression tree
            // into a valid DbCommand.  The Visit<> method will only process expressions of type MemberInitExpression and requires
            // that a MemberInitExpression be returned - in our case we'll return the same one we are passed since we are building
            // a DbCommand and not 'really using' the evaluator Lambda.
            evaluator.Visit<MemberInitExpression>(delegate(MemberInitExpression expression)
            {
                if (memberInitCount > 1)
                {
                    throw new NotImplementedException("Currently only one MemberInitExpression is allowed for the evaluator parameter.");
                }
                memberInitCount++;

                setSB.Append(GetDbSetStatement<TEntity>(expression, table, updateCommand));

                return expression; // just return passed in expression to keep 'visitor' happy.
            });

            // Complete the command text by concatenating bits together.
            updateCommand.CommandText = string.Format("UPDATE {0}\r\n{1}\r\n\r\n{2}",
                                                            table.GetDbName(),                                    // Database table name
                                                            setSB.ToString(),                                    // SET fld = {}, fld2 = {}, ...
                                                            GetBatchJoinQuery<TEntity>(table, entities));    // Subquery join created from entities command text

            if (updateCommand.CommandText.IndexOf("[arg0]") >= 0 || updateCommand.CommandText.IndexOf("NULL AS [EMPTY]") >= 0)
            {
                // TODO (Chris): Probably a better way to determine this by using an visitor on the expression before the
                //                 var selectExpression = Expression.Call... method call (search for that) and see which funcitons
                //                 are being used and determine if supported by LINQ to SQL
                throw new NotSupportedException(string.Format("The evaluator Expression<Func<{0},{0}>> has processing that needs to be performed once the query is returned (i.e. string.Format()) and therefore can not be used during batch updating.", table.GetType()));
            }

            return updateCommand;
        }

        private static string GetDbSetStatement<TEntity>(MemberInitExpression memberInitExpression, Table<TEntity> table, DbCommand updateCommand) where TEntity : class
        {
            var entityType = typeof(TEntity);

            if (memberInitExpression.Type != entityType)
            {
                throw new NotImplementedException(string.Format("The MemberInitExpression is intializing a class of the incorrect type '{0}' and it should be '{1}'.", memberInitExpression.Type, entityType));
            }

            var setSB = new StringBuilder();

            var tableName = table.GetDbName();
            var metaTable = table.Context.Mapping.GetTable(entityType);
            // Used to look up actual field names when MemberAssignment is a constant,
            // need both the Name (matches the property name on LINQ object) and the
            // MappedName (db field name).
            var dbCols = from mdm in metaTable.RowType.DataMembers
                         select new { mdm.MappedName, mdm.Name };

            // Walk all the expression bindings and generate SQL 'commands' from them.  Each binding represents a property assignment
            // on the TEntity object initializer Lambda expression.
            foreach (var binding in memberInitExpression.Bindings)
            {
                var assignment = binding as MemberAssignment;

                if (binding == null)
                {
                    throw new NotImplementedException("All bindings inside the MemberInitExpression are expected to be of type MemberAssignment.");
                }

                // TODO (Document): What is this doing?  I know it's grabbing existing parameter to pass into Expression.Call() but explain 'why'
                //                    I assume it has something to do with fact we can't just access the parameters of assignment.Expression?
                //                    Also, any concerns of whether or not if there are two params of type entity type?
                ParameterExpression entityParam = null;
                assignment.Expression.Visit<ParameterExpression>(delegate(ParameterExpression p) { if (p.Type == entityType) entityParam = p; return p; });

                // Get the real database field name.  binding.Member.Name is the 'property' name of the LINQ object
                // so I match that to the Name property of the table mapping DataMembers.
                string name = binding.Member.Name;
                var dbCol = (from c in dbCols
                             where c.Name == name
                             select c).FirstOrDefault();

                if (dbCol == null)
                {
                    throw new ArgumentOutOfRangeException(name, string.Format("The corresponding field on the {0} table could not be found.", tableName));
                }

                // If entityParam is NULL, then no references to other columns on the TEntity row and need to eval 'constant' value...
                if (entityParam == null)
                {
                    // Compile and invoke the assignment expression to obtain the contant value to add as a parameter.
                    var constant = Expression.Lambda(assignment.Expression, null).Compile().DynamicInvoke();

                    // use the MappedName from the table mapping DataMembers - that is field name in DB table.
                    if (constant == null)
                    {
                        setSB.AppendFormat("[{0}] = null, ", dbCol.MappedName);
                    }
                    else
                    {
                        // Add new parameter with massaged name to avoid clashes.
                        setSB.AppendFormat("[{0}] = @p{1}, ", dbCol.MappedName, updateCommand.Parameters.Count);
                        updateCommand.Parameters.Add(new SqlParameter(string.Format("@p{0}", updateCommand.Parameters.Count), constant));
                    }
                }
                else
                {
                    // TODO (Documentation): Explain what we are doing here again, I remember you telling me why we have to call but I can't remember now.
                    // Wny are we calling Expression.Call and what are we passing it?  Below comments are just 'made up' and probably wrong.

                    // Create a MethodCallExpression which represents a 'simple' select of *only* the assignment part (right hand operator) of
                    // of the MemberInitExpression.MemberAssignment so that we can let the Linq Provider do all the 'sql syntax' generation for
                    // us. 
                    //
                    // For Example: TEntity.Property1 = TEntity.Property1 + " Hello"
                    // This selectExpression will be only dealing with TEntity.Property1 + " Hello"
                    var selectExpression = Expression.Call(
                                                typeof(Queryable),
                                                "Select",
                                                new Type[] { entityType, assignment.Expression.Type },

                    // TODO (Documentation): How do we know there are only 'two' parameters?  And what is Expression.Lambda
                        //                         doing?  I assume it's returning a type of assignment.Expression.Type to match above?

                                                Expression.Constant(table),
                                                Expression.Lambda(assignment.Expression, entityParam));

                    setSB.AppendFormat("[{0}] = {1}, ",
                                            dbCol.MappedName,
                                            GetDbSetAssignment(table, selectExpression, updateCommand, name));
                }
            }

            var setStatements = setSB.ToString();
            return "SET " + setStatements.Substring(0, setStatements.Length - 2); // remove ', '
        }

        /// <summary>
        /// Some LINQ Query syntax is invalid because SQL (or whomever the provider is) can not translate it to its native language.  
        /// DataContext.GetCommand() does not detect this, only IProvider.Execute or IProvider.Compile call the necessary code to 
        /// check this.  This function invokes the IProvider.Compile to make sure the provider can translate the expression.
        /// </summary>
        /// <remarks>
        /// An example of a LINQ query that previously 'worked' in the *Batch methods but needs to throw an exception is something
        /// like the following:
        /// 
        /// var pay = 
        ///        from h in HistoryData
        ///        where h.his.Groups.gName == "Ochsner" && h.hisType == "pay"
        ///        select h;
        ///        
        /// HistoryData.UpdateBatchPreview( pay, h => new HistoryData { hisIndex = ( int.Parse( h.hisIndex ) - 1 ).ToString() } ).Dump();
        /// 
        /// The int.Parse is not valid and needs to throw an exception like: 
        /// 
        ///        Could not translate expression '(Parse(p.hisIndex) - 1).ToString()' into SQL and could not treat it as a local expression.
        ///        
        ///    Unfortunately, the IProvider.Compile is internal and I need to use Reflection to call it (ugh).  I've several e-mails sent into
        ///    MS LINQ team members and am waiting for a response and will correct/improve code as soon as possible.
        /// </remarks>
        private static void ValidateExpression(ITable table, Expression expression)
        {
            var context = table.Context;
            PropertyInfo providerProperty = context.GetType().GetProperty("Provider", BindingFlags.Instance | BindingFlags.NonPublic);
            var provider = providerProperty.GetValue(context, null);
            var compileMI = provider.GetType().GetMethod("System.Data.Linq.Provider.IProvider.Compile", BindingFlags.Instance | BindingFlags.NonPublic);

            // Simply compile the expression to see if it will work.
            compileMI.Invoke(provider, new object[] { expression });
        }

        private static string GetDbSetAssignment(ITable table, MethodCallExpression selectExpression, DbCommand updateCommand, string bindingName)
        {
            ValidateExpression(table, selectExpression);

            // Convert the selectExpression into an IQueryable query so that I can get the CommandText
            var selectQuery = (table as IQueryable).Provider.CreateQuery(selectExpression);

            // Get the DbCommand so I can grab relavent parts of CommandText to construct a field 
            // assignment and based on the 'current TEntity row'.  Additionally need to massage parameter 
            // names from temporary command when adding to the final update command.
            var selectCmd = table.Context.GetCommand(selectQuery);
            var selectStmt = selectCmd.CommandText;
            selectStmt = selectStmt.Substring(7,                                    // Remove 'SELECT ' from front ( 7 )
                                        selectStmt.IndexOf("\r\nFROM ") - 7)        // Return only the selection field expression
                                    .Replace("[t0].", "")                            // Remove table alias from the select
                                    .Replace(" AS [value]", "")                    // If the select is not a direct field (constant or expression), remove the field alias
                                    .Replace("@p", "@p" + bindingName);            // Replace parameter name so doesn't conflict with existing ones.

            foreach (var selectParam in selectCmd.Parameters.Cast<DbParameter>())
            {
                var paramName = string.Format("@p{0}", updateCommand.Parameters.Count);

                // DataContext.ExecuteCommand ultimately just takes a object array of parameters and names them p0-N.  
                // So I need to now do replaces on the massaged value to get it in proper format.
                selectStmt = selectStmt.Replace(
                                    selectParam.ParameterName.Replace("@p", "@p" + bindingName),
                                    paramName);

                updateCommand.Parameters.Add(new SqlParameter(paramName, selectParam.Value));
            }

            return selectStmt;
        }

        private static string GetBatchJoinQuery<TEntity>(Table<TEntity> table, IQueryable<TEntity> entities) where TEntity : class
        {
            var metaTable = table.Context.Mapping.GetTable(typeof(TEntity));

            var keys = from mdm in metaTable.RowType.DataMembers
                       where mdm.IsPrimaryKey
                       select new { mdm.MappedName };

            var joinSB = new StringBuilder();
            var subSelectSB = new StringBuilder();

            foreach (var key in keys)
            {
                joinSB.AppendFormat("j0.[{0}] = j1.[{0}] AND ", key.MappedName);
                // For now, always assuming table is aliased as t0.  Should probably improve at some point.
                // Just writing a smaller sub-select so it doesn't get all the columns of data, but instead
                // only the primary key fields used for joining.
                subSelectSB.AppendFormat("[t0].[{0}], ", key.MappedName);
            }

            var selectCommand = table.Context.GetCommand(entities);
            var select = selectCommand.CommandText;

            var join = joinSB.ToString();

            if (join == "")
            {
                throw new MissingPrimaryKeyException(string.Format("{0} does not have a primary key defined.  Batch updating/deleting can not be used for tables without a primary key.", metaTable.TableName));
            }

            join = join.Substring(0, join.Length - 5);                                            // Remove last ' AND '
            #region - Better ExpressionTree Handling Needed -
            /*
            
            Below is a sample query where the let statement was used to simply the 'where clause'.  However, it produced an extra level
            in the query.
             
            var manage =
                from u in User
                join g in Groups on u.User_Group_id equals g.gKey into groups
                from g in groups.DefaultIfEmpty()
                let correctGroup = groupsToManage.Contains( g.gName ) || ( groupsToManage.Contains( "_GLOBAL" ) && g.gKey == null )
                where correctGroup && ( users.Contains( u.User_Authenticate_id ) || userEmails.Contains( u.User_Email ) ) || userKeys.Contains( u.User_id )
                select u;
             
            Produces this SQL:
            SELECT [t2].[User_id] AS [uKey], [t2].[User_Authenticate_id] AS [uAuthID], [t2].[User_Email] AS [uEmail], [t2].[User_Pin] AS [uPin], [t2].[User_Active] AS [uActive], [t2].[uAdminAuthID], [t2].[uFailureCount]
            FROM (
                SELECT [t0].[User_id], [t0].[User_Authenticate_id], [t0].[User_Email], [t0].[User_Pin], [t0].[User_Active], [t0].[uFailureCount], [t0].[uAdminAuthID], 
                    (CASE 
                        WHEN [t1].[gName] IN (@p0) THEN 1
                        WHEN NOT ([t1].[gName] IN (@p0)) THEN 0
                        ELSE NULL
                     END) AS [value]
                FROM [User] AS [t0]
                LEFT OUTER JOIN [Groups] AS [t1] ON [t0].[User_Group_id] = ([t1].[gKey])
                ) AS [t2]
            WHERE (([t2].[value] = 1) AND (([t2].[User_Authenticate_id] IN (@p1)) OR ([t2].[User_Email] IN (@p2)))) OR ([t2].[User_id] IN (@p3))             
            
            If I put the entire where in one line...
            where     ( groupsToManage.Contains( g.gName ) || ( groupsToManage.Contains( "_GLOBAL" ) && g.gKey == null ) ) && 
                    ( users.Contains( u.User_Authenticate_id ) || userEmails.Contains( u.User_Email ) ) || userKeys.Contains ( u.User_id )

            I get this SQL:
            SELECT [t0].[User_id] AS [uKey], [t0].[User_Authenticate_id] AS [uAuthID], [t0].[User_Email] AS [uEmail], [t0].[User_Pin] AS [uPin], [t0].[User_Active] AS [uActive], [t0].[uAdminAuthID], [t0].[uFailureCount]
            FROM [User] AS [t0]
            LEFT OUTER JOIN [Groups] AS [t1] ON [t0].[User_Group_id] = ([t1].[gKey])
            WHERE (([t1].[gName] IN (@p0)) AND (([t0].[User_Authenticate_id] IN (@p1)) OR ([t0].[User_Email] IN (@p2)))) OR ([t0].[User_id] IN (@p3))            
            
            The second 'cleaner' SQL worked with my original 'string parsing' of simply looking for [t0] and stripping everything before it
            to get rid of the SELECT and any 'TOP' clause if present.  But the first SQL introduced a layer which caused [t2] to be used.  So
            I have to do a bit different string parsing.  There is probably a more efficient way to examine the ExpressionTree and figure out
            if something like this is going to happen.  I will explore it later.
            */
            #endregion
            var endSelect = select.IndexOf("[t");                                                    // Get 'SELECT ' and any TOP clause if present
            var selectClause = select.Substring(0, endSelect);
            var selectTableNameStart = endSelect + 1;                                                // Get the table name LINQ to SQL used in query generation
            var selectTableName = select.Substring(selectTableNameStart,                            // because I have to replace [t0] with it in the subSelectSB
                                        select.IndexOf("]", selectTableNameStart) - (selectTableNameStart));

            // TODO (Chris): I think instead of searching for ORDER BY in the entire select statement, I should examine the ExpressionTree and see
            // if the *outer* select (in case there are nested subselects) has an orderby clause applied to it.
            var needsTopClause = selectClause.IndexOf(" TOP ") < 0 && select.IndexOf("\r\nORDER BY ") > 0;

            var subSelect = selectClause
                                + (needsTopClause ? "TOP 100 PERCENT " : "")                            // If order by in original select without TOP clause, need TOP
                                + subSelectSB.ToString()                                                // Append just the primary keys.
                                             .Replace("[t0]", string.Format("[{0}]", selectTableName));
            subSelect = subSelect.Substring(0, subSelect.Length - 2);                                    // Remove last ', '

            subSelect += select.Substring(select.IndexOf("\r\nFROM ")); // Create a sub SELECT that *only* includes the primary key fields

            var batchJoin = String.Format("FROM {0} AS j0 INNER JOIN (\r\n\r\n{1}\r\n\r\n) AS j1 ON ({2})\r\n", table.GetDbName(), subSelect, join);
            return batchJoin;
        }

        private static string GetDbName<TEntity>(this Table<TEntity> table) where TEntity : class
        {
            var entityType = typeof(TEntity);
            var metaTable = table.Context.Mapping.GetTable(entityType);
            var tableName = metaTable.TableName;

            if (!tableName.StartsWith("["))
            {
                string[] parts = tableName.Split('.');
                tableName = string.Format("[{0}]", string.Join("].[", parts));
            }

            return tableName;
        }

        private static string GetLog(this DataContext context)
        {
            PropertyInfo providerProperty = context.GetType().GetProperty("Provider", BindingFlags.Instance | BindingFlags.NonPublic);
            var provider = providerProperty.GetValue(context, null);
            Type providerType = provider.GetType();

            PropertyInfo modeProperty = providerType.GetProperty("Mode", BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo servicesField = providerType.GetField("services", BindingFlags.Instance | BindingFlags.NonPublic);
            object services = servicesField != null ? servicesField.GetValue(provider) : null;
            PropertyInfo modelProperty = services != null ? services.GetType().GetProperty("Model", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetProperty) : null;

            return string.Format("-- Context: {0}({1}) Model: {2} Build: {3}\r\n",
                            providerType.Name,
                            modeProperty != null ? modeProperty.GetValue(provider, null) : "unknown",
                            modelProperty != null ? modelProperty.GetValue(services, null).GetType().Name : "unknown",
                            "3.5.21022.8");
        }
    }
}复制代码

———————–NoSQL复制代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.Linq;
using System.IO;
using System.Reflection;
using System.Linq.Expressions;
using Rocky;
using Rocky.Data;
using Rocky.Caching;

namespace NoSQL
{
    /// <summary>
    /// PS: DataContext用的是PO,但DistributedCache用的是DTO;CacheContext这里是使用的无主外键关系的PO。
    /// </summary>
    /// <typeparam name="TDataContext"></typeparam>
    public sealed class CacheContext<TDataContext> : EntityContext<TDataContext>, ICacheContext where TDataContext : DataContext, new()
    {
        #region Fields
        private static readonly MethodInfo _LinqWhere = typeof(Queryable).GetMethods().Where(item => item.Name == "Where").First();
        private TDataContext _context;
        private DistributedCache _cache;
        private ICacheKeyGenerator _queryKeyGenerator;
        #endregion

        #region Properties
        /// <summary>
        /// DataAccess DLL名称,随意更改会出异常
        /// <example>Soubisc.DataAccess.{0}, Soubisc.DataAccess</example>
        /// </summary>
        public string DataAccessFormat { get; private set; }
        public TDataContext Linq
        {
            get { return _context; }
        }
        DataContext ICacheContext.Database
        {
            get { return _context; }
        }
        DistributedCache ICacheContext.Cache
        {
            get { return _cache; }
        }
        public bool ReadOnly
        {
            get { return !_context.ObjectTrackingEnabled; }
            set { _context.ObjectTrackingEnabled = !value; }
        }
        public bool QueryNoLock { get; set; }
        #endregion

        #region Constructors
        public CacheContext(string dataAccessFormat)
        {
            this.DataAccessFormat = dataAccessFormat;
            _context = base.Context;
            this.ReadOnly = true;
            _cache = NoSQLHelper.CreateCache(_context.Mapping.DatabaseName);
            _queryKeyGenerator = new CacheKeyGenerator();
            _queryKeyGenerator.AutoHash = true;
        }

        protected override void DisposeInternal(bool disposing)
        {
            if (disposing)
            {
                _cache.Dispose();
            }
            _cache = null;
            _queryKeyGenerator = null;
            base.DisposeInternal(disposing);
        }
        #endregion

        #region BaseCore
        protected override Type MapDataType(Type bizType)
        {
            try
            {
                return base.MapDataType(bizType);
            }
            catch (InvalidOperationException)
            {
                int i = bizType.Name.LastIndexOf("Entity");
                if (i == -1)
                {
                    throw new InvalidOperationException("BO Mapping");
                }
                Type dataType = Type.GetType(string.Format(DataAccessFormat, bizType.Name.Remove(i)), false);
                if (dataType == null)
                {
                    Type objType = typeof(object);
                    while (dataType == null && bizType.BaseType != objType)
                    {
                        bizType = bizType.BaseType;
                        i = bizType.Name.LastIndexOf("Entity");
                        if (i == -1)
                        {
                            continue;
                        }
                        dataType = Type.GetType(string.Format(DataAccessFormat, bizType.Name.Remove(i)), false);
                    }

                    if (dataType == null)
                    {
                        throw new InvalidOperationException("BO Mapping");
                    }
                }
                SetDataTypeMap(bizType, dataType);
                return dataType;
            }
        }
        protected override void AssignDataObject(object dataObj, object bizObj)
        {
            Type fromType = bizObj.GetType(), toType = MapDataType(fromType);
            var mapper = NoSQLHelper.MapperManager.GetMapperImpl(fromType, toType, EmitMapper.MappingConfiguration.DefaultMapConfig.Instance);
            mapper.Map(bizObj, dataObj, null);
        }

        public TTO MapEntity<TFrom, TTO>(TFrom from, TTO to)
        {
            return MapEntity(from, to, null);
        }
        /// <summary>
        /// http://emitmapper.codeplex.com/wikipage?title=Customization%20using%20default%20configurator
        /// </summary>
        /// <typeparam name="TFrom"></typeparam>
        /// <typeparam name="TTO"></typeparam>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <param name="ignoreNames">"RowID" int</param>
        /// <returns></returns>
        public TTO MapEntity<TFrom, TTO>(TFrom from, TTO to, string[] ignoreNames)
        {
            Type tFrom = typeof(TFrom), tTo = typeof(TTO);
            EmitMapper.Mappers.ObjectsMapperBaseImpl mapper = null;
            if (ignoreNames.IsNullOrEmpty())
            {
                mapper = NoSQLHelper.MapperManager.GetMapperImpl(tFrom, tTo, EmitMapper.MappingConfiguration.DefaultMapConfig.Instance);
            }
            else
            {
                mapper = NoSQLHelper.MapperManager.GetMapperImpl(tFrom, tTo, new EmitMapper.MappingConfiguration.DefaultMapConfig().IgnoreMembers(tFrom, tTo, ignoreNames));
            }
            return (TTO)mapper.Map(from, to, null);
        }
        #endregion

        #region Methods
        private string GenerateQueryKey(DbCommand cmd)
        {
            string longKey = cmd.CommandText;
            if (cmd.Parameters.Count > 0)
            {
                var buffer = new StringBuilder(longKey).AppendLine();
                foreach (DbParameter param in cmd.Parameters)
                {
                    switch (param.Direction)
                    {
                        case ParameterDirection.Input:
                        case ParameterDirection.InputOutput:
                            if (param.Value != null)
                            {
                                buffer.AppendFormat("{0}:{1},", param.ParameterName, param.Value);
                            }
                            break;
                    }
                }
                buffer.Length--;
                longKey = buffer.ToString();
            }
            _queryKeyGenerator.HashKey(ref longKey);

            Runtime.LogDebug("GenerateQueryKey:{0}.", longKey);

            return longKey;
        }

        public ICacheKeyMapper CreateKeyMapper<TEntity>()
        {
            var model = MetaTable.GetTable(typeof(TEntity));
            return new PrimaryKeyMapper(model.PrimaryKey);
        }

        public void Initialize<TEntity>(IQueryable<TEntity> query) where TEntity : class
        {
            var model = MetaTable.GetTable(typeof(TEntity));
            var keyMapper = new PrimaryKeyMapper(model.PrimaryKey);
            foreach (var entity in query)
            {
                _cache.AddOrGetExisting(keyMapper.GenerateKey(entity), entity, DistributedCache.InfiniteAbsoluteExpiration);
            }
        }

        /// <summary>
        /// Delete all cache items, and save db changes.
        /// This only used 4 debug.
        /// </summary>
        [System.Diagnostics.Conditional(Runtime.DebugSymbal)]
        public void FlushAll()
        {
            _cache.FlushAll();
            if (!this.ReadOnly)
            {
                base.LazySize = 0;
                base.SaveChanges();
            }
        }
        #endregion

        #region Linq
        /// <summary>
        /// 从数据库加载
        /// </summary>
        /// <param name="model"></param>
        /// <param name="value"></param>
        /// <param name="sourceCmd"></param>
        /// <returns>DataRow</returns>
        /// <exception cref="System.Data.Linq.ChangeConflictException">数据库表中该行已被删除</exception>
        private object LoadItem(MetaTable model, Array value, DbCommand sourceCmd)
        {
            Func<PropertyInfo, object, LambdaExpression> func = (prop, constValue) =>
            {
                var param = Expression.Parameter(model.EntityType, "item");
                var propLeft = Expression.Property(param, prop);
                if (constValue.GetType() != prop.PropertyType)
                {
                    constValue = Convert.ChangeType(constValue, prop.PropertyType);
                }
                var valueRight = Expression.Constant(constValue, prop.PropertyType);
                var body = Expression.Equal(propLeft, valueRight);
                return Expression.Lambda(body, param);
            };

            IQueryable query = _context.GetTable(model.EntityType);
            for (int i = 0; i < value.Length; i++)
            {
                query = query.Provider.CreateQuery(Expression.Call(null, _LinqWhere.MakeGenericMethod(model.EntityType), new Expression[] { query.Expression, Expression.Quote(func(model.PrimaryKey[i].EntityProperty, value.GetValue(i))) }));
            }
            var cmd = PrepareCommand(query, false);
            cmd.Connection = sourceCmd.Connection;
            cmd.Transaction = sourceCmd.Transaction;

            Runtime.LogDebug(string.Format("LoadItem:{0}.", cmd.CommandText));

            var tor = _context.Translate(model.EntityType, cmd.ExecuteReader()).GetEnumerator();
            if (!tor.MoveNext())
            {
                throw new ChangeConflictException("数据库表中该行已被删除");
            }
            return tor.Current;
        }

        /// <summary>
        /// 执行查询
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="query"></param>
        /// <returns></returns>
        public IEnumerable<TEntity> ExecuteQuery<TEntity>(IQueryable<TEntity> query) where TEntity : class
        {
            LinqResolver resolver;
            var resultSet = ExecuteQuery(query, out resolver);
            int modelCount = resolver.QueriedModels.Count;
            if (modelCount == 1)
            {
                if (resolver.QueriedModels[0].EntityType == query.ElementType)
                {
                    return resultSet.Cast<TEntity>();
                }
                else
                {
                    return resultSet.Select(item => (TEntity)resolver.SelectAssign(item));
                }
            }
            else
            {
                var list = new List<TEntity>((int)Math.Ceiling((double)resultSet.Length / modelCount));
                object[] param = new object[modelCount];
                for (int i = 0, j; i < resultSet.Length; i += modelCount)
                {
                    for (j = 0; j < modelCount; j++)
                    {
                        param[j] = resultSet[i + j];
                    }
                    list.Add((TEntity)resolver.SelectAssign(param));
                }
                return list;
            }
        }
        private object[] ExecuteQuery(IQueryable query, out LinqResolver resolver)
        {
            resolver = new LinqResolver(query);
            var cmd = PrepareCommand(query, this.QueryNoLock);
            string queryKey = GenerateQueryKey(cmd);
            var primaryKeys = (List<string>)_cache.Get(queryKey);
            if (primaryKeys == null)
            {
                try
                {
                    cmd.Connection.Open();
                    //发布事务锁定读取,但不锁定写入;通过数据库事务来避免并发下此处的多次重复写入DistributedCache
                    cmd.Transaction = cmd.Connection.BeginTransaction(IsolationLevel.ReadCommitted);

                    primaryKeys = (List<string>)_cache.Get(queryKey);
                    if (primaryKeys == null)
                    {
                        primaryKeys = new List<string>();
                        #region InsertPrimaryKey
                        var buffer = new StringBuilder(cmd.CommandText);
                        string tValue = "[t", fValue = "FROM ";
                        int offset = 7;
                        offset = cmd.CommandText.IndexOf(tValue, offset);
                        int maxTIndex = int.Parse(cmd.CommandText.Substring(offset + tValue.Length, 1));
                        do
                        {
                            offset = cmd.CommandText.IndexOf(fValue, offset);
                            buffer.Insert(offset, " ");
                            bool withOrdinal = cmd.CommandText.Substring(offset + fValue.Length, 1) == "[";
                            if (withOrdinal)
                            {
                                for (int i = 0; i < resolver.QueriedModels.Count; i++)
                                {
                                    foreach (var pk in resolver.QueriedModels[i].PrimaryKey)
                                    {
                                        buffer.Insert(offset, string.Format(",t{0}.[{1}] '{2}'", i, pk.MappedName, pk.FullName));
                                    }
                                }
                                cmd.CommandText = buffer.ToString();
                                break;
                            }

                            foreach (var pk in resolver.QueriedModels.SelectMany(t => t.PrimaryKey))
                            {
                                string sqlField = string.Format(",t{0}.[{1}]", maxTIndex, pk.FullName);
                                buffer.Insert(offset, sqlField);
                                offset += sqlField.Length;
                            }
                            offset += fValue.Length;
                            maxTIndex--;
                            cmd.CommandText = buffer.ToString();
                        }
                        while (offset != -1);
                        #endregion
                        Runtime.LogDebug("ExecuteQuery:{0}.", cmd.CommandText);

                        var dr = cmd.ExecuteReader();
                        while (dr.Read())
                        {
                            foreach (var model in resolver.QueriedModels)
                            {
                                Array pkArray = model.PrimaryKey.Select(pk => dr[pk.FullName]).ToArray();
                                var keyMapper = new PrimaryKeyMapper(model.PrimaryKey);
                                string key = keyMapper.GenerateKey(pkArray);
                                var item = _cache.Get(key);
                                if (item == null)
                                {
                                    try
                                    {
                                        item = LoadItem(model, pkArray, cmd);
                                        _cache.AddOrGetExisting(key, item, DistributedCache.InfiniteAbsoluteExpiration);
                                    }
                                    catch (ChangeConflictException)
                                    {
                                        //数据库表中该行已被删除
                                        continue;
                                    }
                                }
                                primaryKeys.Add(key);
                            }
                        }
                    }
                }
                finally
                {
                    cmd.Transaction.Dispose();
                    cmd.Connection.Close();
                }
                if (primaryKeys != null)
                {
                    _cache.Set(queryKey, primaryKeys, DateTimeOffset.Now.AddMinutes(10D));
                }
            }
            // 因为无法得到DistributedCache server remove callback,所以在查询时检测结果集的有效性。
            var resultSet = _cache.GetValues(primaryKeys).Values.ToArray();
            try
            {
                for (int i = 0; i < resultSet.Length; i++)
                {
                    if (resultSet[i] == null)
                    {
                        if (cmd.Connection.State != ConnectionState.Open)
                        {
                            cmd.Connection.Open();
                        }

                        MetaTable model;
                        var value = PrimaryKeyMapper.ResolveKey(primaryKeys[i], out model);
                        try
                        {
                            resultSet[i] = LoadItem(model, value, cmd);
                            _cache.AddOrGetExisting(primaryKeys[i], resultSet[i], DistributedCache.InfiniteAbsoluteExpiration);
                        }
                        catch (ChangeConflictException)
                        {
                            //数据库表中该行已被删除
                            continue;
                        }
                    }
                }
            }
            finally
            {
                if (cmd.Connection.State != ConnectionState.Closed)
                {
                    cmd.Connection.Close();
                }
            }
            return resultSet;
        }
        #endregion
    }
}
复制代码

未经允许不得转载:聚艺帮 » C# NOSQL

分享到:更多 ()

牛评 抢沙发

爱生活 爱设计

齐聚智慧联系我们