public void AddBookToOrder(IEntitySession session, Guid orderId, Guid bookId, int quantity) { var order = session.GetEntity<IBookOrder>(orderId, LockOptions.ForUpdate); var book = session.GetEntity<IBook>(bookId); var orderLine = session.NewEntity<IBookOrderLine>(); orderLine.Order = order; orderLine.Book = book; orderLine.Quantity = quantity; orderLine.Price = book.Price; order.Lines.Add(orderLine); order.Total = order.Lines.Sum(ol => ol.Price * ol.Quantity); session.SaveChanges(); }
public void AddBookToOrder(IEntitySession session, Guid orderId, Guid bookId, byte quantity) { var order = session.GetEntity <IBookOrder>(orderId, LockOptions.ForUpdate); var book = session.GetEntity <IBook>(bookId); var orderLine = session.NewEntity <IBookOrderLine>(); orderLine.Order = order; orderLine.Book = book; orderLine.Quantity = quantity; orderLine.Price = book.Price; order.Lines.Add(orderLine); order.Total = order.Lines.Sum(ol => ol.Price * ol.Quantity); session.SaveChanges(); }
public BookOrderStats GetBookOrderStats(IEntitySession session, Guid orderId) { try { var order = session.GetEntity<IBookOrder>(orderId, LockOptions.SharedRead); return new BookOrderStats() { OrderId = orderId, LineCount = order.Lines.Count, MaxPrice = order.Lines.Max(ol => ol.Price) }; } finally { session.ReleaseLocks(); } }
private IBookOrder GetOpenOrder(IEntitySession session, LockType lockType, bool create = false) { var currUserId = Context.User.UserId; var openOrder = session.EntitySet <IBookOrder>(lockType) .Where(bo => bo.User.Id == currUserId && bo.Status == OrderStatus.Open).FirstOrDefault(); if (openOrder == null && create) { var user = session.GetEntity <IUser>(Context.User.UserId); openOrder = session.NewOrder(user); } return(openOrder); }
public BookOrderStats GetBookOrderStats(IEntitySession session, Guid orderId) { try { var order = session.GetEntity <IBookOrder>(orderId, LockOptions.SharedRead); return(new BookOrderStats() { OrderId = orderId, LineCount = order.Lines.Count, MaxPrice = order.Lines.Max(ol => ol.Price) }); } finally { session.ReleaseLocks(); } }
/// <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); }
// Can be called explicitly outside of migrations to unencrypt existing values public static void UnencryptFactorValues(IEntitySession session) { var loginConfig = session.Context.App.GetConfig <LoginModuleSettings>(); var errLog = session.Context.App.ErrorLog; int batchSize = 200; int skip = 0; try { var factors = session.EntitySet <ILoginExtraFactor>() .Where(f => f.FactorValue == null).OrderBy(f => f.CreatedOn) .Skip(skip).Take(batchSize).ToList(); skip += batchSize; if (factors.Count == 0) { return; } //preload all EncryptedValue records var encrIds = factors.Select(f => f.Info_Id).ToList(); var encrRecs = session.EntitySet <IEncryptedData>().Where(ed => encrIds.Contains(ed.Id)); foreach (var f in factors) { if (f.Info_Id == null || f.Info_Id.Value == Guid.Empty) { continue; } var ed = session.GetEntity <IEncryptedData>(f.Info_Id); //should be preloaded if (ed == null) { continue; } f.FactorValue = ed.DecryptString(loginConfig.EncryptionChannelName); f.Info_Id = null; } session.SaveChanges(); } catch (Exception ex) { if (errLog != null) { errLog.LogError(ex, session.Context); } if (!SuppressMigrationErrors) { throw; } } }
/// <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; */ }
//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); * } * */ }
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
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
private IBookOrder GetOpenOrder(IEntitySession session, LockOptions lockOptions, bool create = false) { var currUserId = Context.User.UserId; var openOrder = session.EntitySet<IBookOrder>(lockOptions) .Where(bo => bo.User.Id == currUserId && bo.Status == OrderStatus.Open).FirstOrDefault(); if (openOrder == null && create) { var user = session.GetEntity<IUser>(Context.User.UserId); openOrder = session.NewOrder(user); } return openOrder; }
public ILogin GetLogin(IEntitySession session, Guid loginId) { return session.GetEntity<ILogin>(loginId); }
public ILogin GetLogin(IEntitySession session, Guid loginId) { return(session.GetEntity <ILogin>(loginId)); }
// If necessary, can be called explicitly outside of migrations to unencrypt existing values public static void UnencryptTokens(IEntitySession session) { var config = session.Context.App.GetConfig <OAuthClientSettings>(); var errLog = session.Context.App.ErrorLog; // Unencrypt client secret in remote server accounts - should be a smaill table var accts = session.EntitySet <IOAuthRemoteServerAccount>().Where(a => a.ClientSecret_Id != null).Take(100).ToList(); foreach (var acct in accts) { var ed = session.GetEntity <IEncryptedData>(acct.ClientSecret_Id); if (ed == null) { continue; } acct.ClientSecret = ed.DecryptString(config.EncryptionChannel); acct.ClientSecret_Id = null; } session.SaveChanges(); // Tokens - might be big table, process in batches int batchSize = 200; int skip = 0; try { var tokenRecs = session.EntitySet <IOAuthAccessToken>() .Where(tr => tr.AccessToken == null).OrderBy(tr => tr.RetrievedOn) .Skip(skip).Take(batchSize).ToList(); skip += batchSize; if (tokenRecs.Count == 0) { return; } //preload all EncryptedValue records var encrIds1 = tokenRecs.Where(tr => tr.AccessToken_Id != null).Select(tr => tr.AccessToken_Id.Value).ToList(); var encrIds2 = tokenRecs.Where(tr => tr.RefreshToken_Id != null).Select(tr => tr.RefreshToken_Id.Value).ToList(); encrIds1.AddRange(encrIds2); var encrRecs = session.EntitySet <IEncryptedData>().Where(ed => encrIds1.Contains(ed.Id)); foreach (var tr in tokenRecs) { //Access token var eId = tr.AccessToken_Id; if (eId != null && eId.Value != Guid.Empty) { var ed = session.GetEntity <IEncryptedData>(eId.Value); //should be preloaded if (ed != null) { tr.AccessToken = ed.DecryptString(config.EncryptionChannel); tr.AccessToken_Id = null; } } // Refresh token eId = tr.AccessToken_Id; if (eId != null && eId.Value != Guid.Empty) { var ed = session.GetEntity <IEncryptedData>(eId.Value); //should be preloaded if (ed != null) { tr.RefreshToken = ed.DecryptString(config.EncryptionChannel); tr.RefreshToken_Id = null; } } } //foreach tr session.SaveChanges(); } catch (Exception ex) { if (errLog != null) { errLog.LogError(ex, session.Context); } if (!SuppressMigrationErrors) { throw; } } } //method