示例#1
0
        /// <summary>Retrieves entity by type and primary key value and sets database lock on the underlying record. </summary>
        /// <typeparam name="TEntity">Entity type.</typeparam>
        /// <param name="session">Entity session.</param>
        /// <param name="primaryKey">The value of the primary key.</param>
        /// <param name="lockType">Lock type.</param>
        /// <returns>An entity instance.</returns>
        /// <remarks>For composite primary keys pass an instance of primary key
        /// created using the <see cref="EntitySessionExtensions.CreatePrimaryKey"/> extension method.
        /// </remarks>
        public static TEntity GetEntity <TEntity>(this IEntitySession session, object primaryKey, LockType lockType)
            where TEntity : class
        {
            if (lockType == LockType.None)
            {
                return(session.GetEntity <TEntity>(primaryKey)); //short path, no locks
            }
            Util.CheckParam(primaryKey, nameof(primaryKey));
            session.LogMessage("-- Locking entity {0}/{1}", typeof(TEntity).Name, primaryKey);
            var       entInfo    = session.Context.App.Model.GetEntityInfo(typeof(TEntity), throwIfNotFound: true);
            var       entSession = (EntitySession)session;
            EntityKey pk         = entInfo.CreatePrimaryKeyInstance(primaryKey);
            var       ent        = entSession.SelectByPrimaryKey(entInfo, pk.Values, lockType);

            return((TEntity)ent);
        }
示例#2
0
        public static void ReleaseLocks(this IEntitySession session)
        {
            var entSession = (EntitySession)session;
            var currConn   = entSession.CurrentConnection;

            if (currConn == null || currConn.DbTransaction == null)
            {
                return;
            }
            session.LogMessage("-- Releasing locks ");
            currConn.Abort();
            if (currConn.Lifetime != ConnectionLifetime.Explicit)
            {
                currConn.Close();
                entSession.CurrentConnection = null;
            }
        }
示例#3
0
文件: LockHelper.cs 项目: radtek/vita
        /// <summary>Retrieves entity by type and primary key value and sets database lock on the underlying record. </summary>
        /// <typeparam name="TEntity">Entity type.</typeparam>
        /// <param name="primaryKey">The value of the primary key.</param>
        /// <param name="lockType">Lock type.</param>
        /// <returns>An entity instance.</returns>
        /// <remarks>For composite primary keys pass an instance of primary key
        /// created using the <see cref="EntitySessionExtensions.CreatePrimaryKey"/> extension method.
        /// </remarks>
        public static TEntity GetEntity <TEntity>(this IEntitySession session, object primaryKey, LockType lockType)
            where TEntity : class
        {
            if (lockType == LockType.None)
            {
                return(session.GetEntity <TEntity>(primaryKey)); //short path, no locks
            }
            Util.CheckParam(primaryKey, nameof(primaryKey));
            session.LogMessage("-- Locking entity {0}/{1}", typeof(TEntity).Name, primaryKey);
            var entInfo = session.Context.App.Model.GetEntityInfo(typeof(TEntity), throwIfNotFound: true);

            /*
             * var pkMembers = entInfo.PrimaryKey.KeyMembers;
             * Util.Check(pkMembers.Count == 1, "Cannot lock entity {0}: composite primary keys not supported.", entInfo.Name);
             * var pkMember = entInfo.PrimaryKey.KeyMembers[0].Member;
             * var prmEnt = Expression.Parameter(typeof(TEntity), "e");
             * var pkRead = Expression.MakeMemberAccess(prmEnt, pkMember.ClrMemberInfo);
             *
             * // PK box - we could use Constant expr to hold PK value directly, but the result is that PK is embedded into translated SQL as literal.
             * // (that's how Linq translation works). We want a query with parameter, so that translated linq command is cached and reused.
             * // So we add extra Convert expression to trick LINQ translator.
             * var pkValueExpr = Expression.Convert(Expression.Constant(primaryKey, typeof(object)), pkMember.DataType);
             * var eq = Expression.Equal(pkRead, pkValueExpr); // Expression.Constant(primaryKey, pkMember.DataType));
             * var filter = Expression.Lambda<Func<TEntity, bool>>(eq, prmEnt);
             */
            var       entSession = (EntitySession)session;
            EntityKey pk         = entInfo.CreatePrimaryKeyInstance(primaryKey);
            var       ent        = entSession.SelectByPrimaryKey(entInfo, pk.Values, lockType);

            return((TEntity)ent);

            /*
             * var query = session.EntitySet<TEntity>(lockType).Where(filter);
             * // We use ToList() on entire query instead of First() because we have already filter on PK value,
             * // and we want to avoid adding any paging (skip/take) clauses to the SQL.
             * // We use FirstOrDefult on entire list, and check that we got something; if not, we throw clear message.
             * var ent = query.ToList().FirstOrDefault();
             * Util.Check(ent != null, "Entity {0} with ID {1} does not exist, cannot set the lock.", entInfo.EntityType.Name,
             *  primaryKey);
             * return ent;
             */
        }
示例#4
0
        //For now implemented using dynamically built LINQ query; stored proc support to come later
        // TODO: save filter Func in EntityInfo and reuse it
        public static TEntity GetEntity <TEntity>(this IEntitySession session, object primaryKey, LockOptions options)
            where TEntity : class
        {
            if (options == LockOptions.None)
            {
                return(session.GetEntity <TEntity>(primaryKey)); //short path, no locks
            }
            session.LogMessage("-- Locking entity {0}/{1}", typeof(TEntity).Name, primaryKey);
            var entInfo   = session.Context.App.Model.GetEntityInfo(typeof(TEntity), throwIfNotFound: true);
            var pkMembers = entInfo.PrimaryKey.KeyMembers;

            Util.Check(pkMembers.Count == 1, "Cannot lock entity {0}: composite primary keys not supported.", entInfo.Name);
            var pkMember = entInfo.PrimaryKey.KeyMembers[0].Member;
            var prmEnt   = Expression.Parameter(typeof(TEntity), "e");
            var pkRead   = Expression.MakeMemberAccess(prmEnt, pkMember.ClrMemberInfo);
            var eq       = Expression.Equal(pkRead, Expression.Constant(primaryKey, pkMember.DataType));
            var filter   = Expression.Lambda <Func <TEntity, bool> >(eq, prmEnt);
            var query    = session.EntitySet <TEntity>(options).Where(filter);
            // We use ToList() on entire query instead of First() because we have already filter on PK value,
            // and we want to avoid adding any paging (skip/take) clauses to the SQL.
            // We use FirstOrDefult on entire list, and check that we got something; if not, we throw clear message.
            var ent = query.ToList().FirstOrDefault();

            Util.Check(ent != null, "Entity {0} with ID {1} does not exist, cannot set the lock.", entInfo.EntityType.Name,
                       primaryKey);
            return(ent);

            /*
             * //The following is just a sketch
             * if (checkLastModifiedId != null) {
             * Util.Check(entInfo.VersioningMember != null, "Entity {0} has no tracking/versioning column (last modified transaction id), cannot check data version.", entInfo.Name);
             * var lastTransId = EntityHelper.GetProperty<Guid>(ent, entInfo.VersioningMember.MemberName);
             * session.Context.ThrowIf(doNotUse_checkLastModifiedId.Value != lastTransId, ClientFaultCodes.ConcurrentUpdate, entInfo.VersioningMember.MemberName, "Entity {0} was modified by concurrent process.", entInfo.Name);
             * }
             * */
        }
示例#5
0
        private void RunRandomReadWriteOp(Guid[] docIds, LockType readLock, LockType writeLock, int readWriteCount)
        {
            var            rand    = new Random(Thread.CurrentThread.ManagedThreadId);
            IEntitySession session = null;
            // Use context with its own buffered op log - all entries related to single load/update operation are buffered,
            // and then flushed together at the end of loop body; so they will appear together in the output file
            var ctx = new OperationContext(_app);

            ctx.Log = new BufferedLog(ctx.LogContext);

            for (int i = 0; i < readWriteCount; i++)
            {
                session = ctx.OpenSession();
                var        randomDocId = docIds[rand.Next(docIds.Length)];
                IDoc       iDoc;
                IDocDetail iDet;
                int        randomOp = -1;
                try {
                    Thread.Yield();
                    var randomValueName = "N" + rand.Next(5);
                    randomOp = rand.Next(7);
                    switch (randomOp)
                    {
                    case 0:
                    case 1: //read and check total
                        session.LogMessage("\r\n----------------- Load, check Doc total ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, readLock);
                        Thread.Yield(); //to let other thread mess it up
                        var valuesSum = iDoc.Details.Sum(v => v.Value);
                        if (valuesSum != iDoc.Total)
                        {
                            session.LogMessage("!!!! Sum error: Doc.Total: {0}, Sum(Value): {1}", iDoc.Total, valuesSum);
                            Interlocked.Increment(ref _sumErrorCount);
                        }
                        Thread.Yield();
                        session.ReleaseLocks();
                        session.LogMessage("\r\n-------------- Completed Load/check total ---------------------");
                        break;

                    case 2:
                    case 3:
                    case 4:
                        //insert/update, we give it an edge over deletes, to have 2 upserts for 1 delete
                        session.LogMessage("\r\n----------------- Update/insert DocDetail ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeLock);
                        Thread.Yield();
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet == null)
                        {
                            iDet      = session.NewEntity <IDocDetail>();
                            iDet.Doc  = iDoc;
                            iDet.Name = randomValueName;
                            iDoc.Details.Add(iDet); //to correctly calculate total
                        }
                        iDet.Value = rand.Next(10);
                        iDoc.Total = iDoc.Details.Sum(v => v.Value);
                        Thread.Yield();
                        session.SaveChanges();
                        session.LogMessage("\r\n------Completed Update/insert doc detail ---------------------");

                        var entSession = (Vita.Entities.Runtime.EntitySession)session;
                        if (entSession.CurrentConnection != null)
                        {
                            Debugger.Break(); //check that connection is closed and removed from session
                        }
                        break;

                    case 6: //delete if exists
                        session.LogMessage("\r\n----------------- Delete doc detail ---------------------");
                        //Note: deletes do not throw any errors - if record does not exist (had been just deleted), stored proc do not throw error
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeLock);
                        Thread.Yield(); //allow others mess up
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet != null)
                        {
                            session.DeleteEntity(iDet);
                            iDoc.Details.Remove(iDet);
                            iDoc.Total = iDoc.Details.Sum(v => v.Value);
                            session.SaveChanges(); // even if there's no changes, it will release lock
                        }
                        else
                        {
                            session.ReleaseLocks();
                        }
                        session.LogMessage("\r\n----------------- Completed delete doc detail ---------------------");
                        break;
                    }//switch
                } catch (Exception ex) { //most will be UniqueIndexViolation
                    Debug.WriteLine(ex.ToLogString());
                    System.Threading.Interlocked.Increment(ref _updateErrorCount);
                    session.Context.Log.AddEntry(new ErrorLogEntry(ctx.LogContext, ex));
                    var entSession = (Vita.Entities.Runtime.EntitySession)session;
                    if (entSession.CurrentConnection != null)
                    {
                        entSession.CurrentConnection.Close();
                    }
                    //session.Context.Log.Flush();
                    _app.Flush();
                } finally {
                    //_app.Flush();
                }
                //session.Context.Log.Flush();
            } //for i
        }     //method
示例#6
0
        private void RunRandomReadWriteOp(Guid[] docIds, LockOptions readOptions, LockOptions writeOptions, int readWriteCount)
        {
            var            rand    = new Random(Thread.CurrentThread.ManagedThreadId);
            IEntitySession session = null;

            for (int i = 0; i < readWriteCount; i++)
            {
                var        randomDocId     = docIds[rand.Next(docIds.Length)];
                var        randomValueName = "N" + rand.Next(5);
                IDoc       iDoc;
                IDocDetail iDet;
                int        randomOp = -1;
                try {
                    Thread.Yield();
                    session  = _app.OpenSession();
                    randomOp = rand.Next(5);
                    switch (randomOp)
                    {
                    case 0:
                    case 1: //insert/update, we give it an edge over deletes, to have 2 upserts for 1 delete
                        session.LogMessage("\r\n----------------- Update/insert value ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeOptions);
                        Thread.Yield();
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet == null)
                        {
                            iDet      = session.NewEntity <IDocDetail>();
                            iDet.Doc  = iDoc;
                            iDet.Name = randomValueName;
                            iDoc.Details.Add(iDet); //to correctly calculate total
                        }
                        iDet.Value = rand.Next(10);
                        iDoc.Total = iDoc.Details.Sum(v => v.Value);
                        Thread.Yield();
                        session.SaveChanges();

                        var entSession = (Vita.Entities.Runtime.EntitySession)session;
                        if (entSession.CurrentConnection != null)
                        {
                            Debugger.Break(); //check that connection is closed and removed from session
                        }
                        break;

                    case 2: //delete if exists
                        session.LogMessage("\r\n----------------- Delete value ---------------------");
                        //Note: deletes do not throw any errors - if record does not exist (had been just deleted), stored proc do not throw error
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeOptions);
                        Thread.Yield(); //allow others mess up
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet != null)
                        {
                            session.DeleteEntity(iDet);
                            iDoc.Details.Remove(iDet);
                            iDoc.Total = iDoc.Details.Sum(v => v.Value);
                        }
                        Thread.Yield();
                        session.SaveChanges(); // even if there's no changes, it will release lock
                        break;

                    case 3:
                    case 4: //read and check total
                        session.LogMessage("\r\n----------------- Loading doc ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, readOptions);
                        Thread.Yield(); //to let other thread mess it up
                        var valuesSum = iDoc.Details.Sum(v => v.Value);
                        if (valuesSum != iDoc.Total)
                        {
                            session.LogMessage("!!!! Sum error: Doc.Total: {0}, Sum(Value): {1}", iDoc.Total, valuesSum);
                            Interlocked.Increment(ref _sumErrorCount);
                        }
                        Thread.Yield();
                        session.ReleaseLocks();
                        break;
                    }//switch
                    WriteLog(session);
                } catch (Exception ex) { //most will be UniqueIndexViolation
                    Debug.WriteLine(ex.ToLogString());
                    System.Threading.Interlocked.Increment(ref _updateErrorCount);
                    if (session != null)
                    {
                        WriteLog(session);
                        var log = session.Context.LocalLog.GetAllAsText();
                        Debug.WriteLine(log);
                        var entSession = (Vita.Entities.Runtime.EntitySession)session;
                        if (entSession.CurrentConnection != null)
                        {
                            entSession.CurrentConnection.Close();
                        }
                    }
                }
            } //for i
        }     //method