public void Init() { EventStoreDbContext.NameOrConnectionString = @"Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=False; Initial Catalog=ItsCqrsTestsEventStore"; using (var eventStore = new EventStoreDbContext()) { new EventStoreDatabaseInitializer<EventStoreDbContext>().InitializeDatabase(eventStore); } }
/// <summary> /// Initializes a new instance of the <see cref="ExclusiveEventStoreCatchupQuery" /> class. /// </summary> /// <param name="dbContext">The event store database context to execute the query against.</param> /// <param name="lockResourceName">Name of the lock. Multiple instances compete with other instances having the same <paramref name="lockResourceName" />.</param> /// <param name="getStartAtId">The id of the first event to query.</param> /// <param name="applyFilter">Transforms a query to filter the events to be read from the event store.</param> /// <param name="batchSize">The number of events queried from the event store at each iteration.</param> public ExclusiveEventStoreCatchupQuery( EventStoreDbContext dbContext, string lockResourceName, Func <long> getStartAtId, Func <IQueryable <StorableEvent>, IQueryable <StorableEvent> > applyFilter, int batchSize = 10000) { if (batchSize < 1) { throw new ArgumentException($"{nameof(batchSize)} must be greater than zero."); } BatchSize = batchSize; this.dbContext = dbContext; this.lockResourceName = lockResourceName; if (TryGetAppLock()) { startAtId = getStartAtId(); IQueryable <StorableEvent> eventQuery = applyFilter(dbContext.Events.AsNoTracking()) .Where(e => e.Id >= startAtId) .OrderBy(e => e.Id); var oldCommandTimeout = dbContext.Database.CommandTimeout; try { dbContext.Database.CommandTimeout = TimeSpan.FromMinutes(10).Seconds; TotalMatchedEventCount = eventQuery.Count(); } catch { System.Diagnostics.Trace.WriteLine("Failed :eventQuery.Count() \n" + eventQuery.ToString()); throw; } finally { dbContext.Database.CommandTimeout = oldCommandTimeout; } BatchMatchedEventCount = Math.Min(BatchSize, TotalMatchedEventCount); eventQuery = eventQuery.Take(batchSize); events = DurableStreamFrom(eventQuery, startAtId); } else { events = Enumerable.Empty <StorableEvent>(); } }
public static void EnsureEventStoreIsInitialized() { if (!eventStoreInitialized) { EventStoreDbContext.NameOrConnectionString = @"Data Source=(localdb)\MSSQLLocalDB; Integrated Security=True; MultipleActiveResultSets=False; Initial Catalog=ItsCqrsTestsEventStore"; using (var eventStore = new EventStoreDbContext()) { new EventStoreDatabaseInitializer<EventStoreDbContext>().InitializeDatabase(eventStore); } } eventStoreInitialized = true; }
/// <summary> /// Seeds an event store using JSON-serialized events stored in a file. /// </summary> public static void SeedFromFile(this EventStoreDbContext context, FileInfo file) { using (var stream = file.OpenRead()) using (var reader = new StreamReader(stream)) { var json = reader.ReadToEnd(); var events = Serializer.FromJsonToEvents(json).ToArray(); foreach (var e in events) { context.Events.Add(e.ToStorableEvent()); // it's necessary to save at every event to preserve ordering, otherwise EF will reorder them context.SaveChanges(); } } }
/// <summary> /// Initializes a new instance of the <see cref="ExclusiveEventStoreCatchupQuery"/> class. /// </summary> /// <param name="dbContext">The event store database context to execute the query against.</param> /// <param name="lockResourceName">Name of the lock. Multiple instances compete with other instances having the same <paramref name="lockResourceName" />.</param> /// <param name="getStartAtId">The id of the first event to query.</param> /// <param name="matchEvents">Specifies the event types to include the query. If none are specified, all events are queried.</param> public ExclusiveEventStoreCatchupQuery(EventStoreDbContext dbContext, string lockResourceName, Func <long> getStartAtId, MatchEvent[] matchEvents) { this.dbContext = dbContext; this.lockResourceName = lockResourceName; if (TryGetAppLock()) { startAtId = getStartAtId(); IQueryable <StorableEvent> eventQuery = dbContext.Events; matchEvents = matchEvents ?? new[] { new MatchEvent() }; // if specific event types are requested, we can optimize the event store query // if Event or IEvent are requested, we don't filter -- this requires reading every event if (matchEvents.Any()) { var eventTypes = matchEvents.Select(m => m.Type).Distinct().ToArray(); var aggregates = matchEvents.Select(m => m.StreamName).Distinct().ToArray(); if (!aggregates.Contains(MatchEvent.Wildcard)) { var aggregateWildCard = aggregates.Any(string.IsNullOrWhiteSpace) || aggregates.Contains(MatchEvent.Wildcard); var eventWildCard = eventTypes.Any(string.IsNullOrWhiteSpace) || eventTypes.Contains(MatchEvent.Wildcard); eventQuery = eventQuery.Where(e => (aggregateWildCard || aggregates.Contains(e.StreamName)) && (eventWildCard || eventTypes.Contains(e.Type))); } } Debug.WriteLine(new { eventQuery }); expectedNumberOfEvents = eventQuery.Count(e => e.Id >= startAtId); events = DurableStreamFrom(eventQuery, startAtId); } else { events = Enumerable.Empty <StorableEvent>(); } }
/// <summary> /// Initializes a new instance of the <see cref="ExclusiveEventStoreCatchupQuery"/> class. /// </summary> /// <param name="dbContext">The event store database context to execute the query against.</param> /// <param name="lockResourceName">Name of the lock. Multiple instances compete with other instances having the same <paramref name="lockResourceName" />.</param> /// <param name="getStartAtId">The id of the first event to query.</param> /// <param name="matchEvents">Specifies the event types to include the query. If none are specified, all events are queried.</param> public ExclusiveEventStoreCatchupQuery(EventStoreDbContext dbContext, string lockResourceName, Func<long> getStartAtId, MatchEvent[] matchEvents) { this.dbContext = dbContext; this.lockResourceName = lockResourceName; if (TryGetAppLock()) { startAtId = getStartAtId(); IQueryable<StorableEvent> eventQuery = dbContext.Events; matchEvents = matchEvents ?? new[] { new MatchEvent() }; // if specific event types are requested, we can optimize the event store query // if Event or IEvent are requested, we don't filter -- this requires reading every event if (matchEvents.Any()) { var eventTypes = matchEvents.Select(m => m.Type).Distinct().ToArray(); var aggregates = matchEvents.Select(m => m.StreamName).Distinct().ToArray(); if (!aggregates.Contains(MatchEvent.Wildcard)) { var aggregateWildCard = aggregates.Any(string.IsNullOrWhiteSpace) || aggregates.Contains(MatchEvent.Wildcard); var eventWildCard = eventTypes.Any(string.IsNullOrWhiteSpace) || eventTypes.Contains(MatchEvent.Wildcard); eventQuery = eventQuery.Where(e => (aggregateWildCard || aggregates.Contains(e.StreamName)) && (eventWildCard || eventTypes.Contains(e.Type))); } } this.eventQuery = eventQuery; expectedNumberOfEvents = eventQuery.Count(e => e.Id >= startAtId); events = DurableStreamFrom(eventQuery, startAtId); } else { events = Enumerable.Empty<StorableEvent>(); } }
/// <summary> /// Initializes a new instance of the <see cref="ExclusiveEventStoreCatchupQuery" /> class. /// </summary> /// <param name="dbContext">The event store database context to execute the query against.</param> /// <param name="lockResourceName">Name of the lock. Multiple instances compete with other instances having the same <paramref name="lockResourceName" />.</param> /// <param name="getStartAtId">The id of the first event to query.</param> /// <param name="matchEvents">Specifies the event types to include the query. If none are specified, all events are queried.</param> /// <param name="batchSize">The number of events queried from the event store at each iteration.</param> /// <param name="filter">An optional filter expression to constrain the query that the catchup uses over the event store.</param> public ExclusiveEventStoreCatchupQuery( EventStoreDbContext dbContext, string lockResourceName, Func <long> getStartAtId, MatchEvent[] matchEvents, int batchSize = 10000, Expression <Func <StorableEvent, bool> > filter = null) { if (batchSize < 1) { throw new ArgumentException($"{nameof(batchSize)} must be greater than zero."); } this.dbContext = dbContext; this.lockResourceName = lockResourceName; if (TryGetAppLock()) { startAtId = getStartAtId(); IQueryable <StorableEvent> eventQuery = dbContext.Events.AsNoTracking(); matchEvents = matchEvents ?? new[] { new MatchEvent() }; // if specific event types are requested, we can optimize the event store query // if Event or IEvent are requested, we don't filter -- this requires reading every event if (matchEvents.Any()) { var eventTypes = matchEvents.Select(m => m.Type).Distinct().ToArray(); var aggregates = matchEvents.Select(m => m.StreamName).Distinct().ToArray(); if (!aggregates.Any(streamName => string.IsNullOrWhiteSpace(streamName) || streamName == MatchEvent.Wildcard)) { if (!eventTypes.Any(type => string.IsNullOrWhiteSpace(type) || type == MatchEvent.Wildcard)) { // Filter on StreamName and Type var projectionEventFilter = new CatchupEventFilter(matchEvents); eventQuery = eventQuery.Where(projectionEventFilter.Filter); } else { // Filter on StreamName eventQuery = eventQuery.Where(e => aggregates.Contains(e.StreamName)); } } } if (filter != null) { eventQuery = eventQuery.Where(filter); } eventQuery = eventQuery .Where(e => e.Id >= startAtId) .Take(batchSize); expectedNumberOfEvents = eventQuery.Count(); events = DurableStreamFrom(eventQuery, startAtId); } else { events = Enumerable.Empty <StorableEvent>(); } }
/// <summary> /// Initializes a new instance of the <see cref="AppLock"/> class. /// </summary> /// <param name="db">The database.</param> /// <param name="lockResourceName">The lock resource.</param> public AppLock(EventStoreDbContext db, string lockResourceName) { this.db = db; this.lockResourceName = lockResourceName; connection = db.OpenConnection(); const string cmd = @" DECLARE @result int; EXEC @result = sp_getapplock @Resource = @lockResource, @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = 60000; SELECT @result"; var result = -1000; using (var getAppLock = connection.CreateCommand()) { getAppLock.Parameters.Add(new SqlParameter("lockResource", lockResourceName)); getAppLock.CommandText = cmd; try { Debug.WriteLine("Trying to acquire app lock '{0}' (#{1})", lockResourceName, GetHashCode()); result = (int)getAppLock.ExecuteScalar(); } catch (SqlException exception) { if (exception.Message.StartsWith("Timeout expired.")) { Debug.WriteLine("Timeout expired waiting for sp_getapplock. (#{0})", GetHashCode()); DebugWriteLocks(); return; } throw; } } resultCode = result; if (result >= 0) { Debug.WriteLine("Acquired app lock '{0}' with result {1} (#{2})", lockResourceName, result, GetHashCode()); } else { Debug.WriteLine("Failed to acquire app lock '{0}' with code {1} (#{2})", lockResourceName, result, GetHashCode()); } disposables = Disposable.Create(OnDispose); #if DEBUG Active[this] = this; #endif }
public void Related_events_are_available_via_diagnostics_endpoint() { // arrange var relatedId1 = Any.Guid(); var relatedId2 = Any.Guid(); var relatedId3 = Any.Guid(); var relatedId4 = Any.Guid(); var unrelatedId = Any.Guid(); Console.WriteLine(new { relatedId1, relatedId2, relatedId3, relatedId4, unrelatedId }.ToLogString()); using (var db = new EventStoreDbContext()) { Enumerable.Range(1, 20).ForEach(i => db.Events.Add(new StorableEvent { AggregateId = relatedId1, SequenceNumber = i, Body = new { relatedId2 }.ToJson(), Timestamp = Clock.Now(), StreamName = "one", Type = "Event" + i.ToString() })); Enumerable.Range(1, 20).ForEach(i => db.Events.Add(new StorableEvent { AggregateId = relatedId2, SequenceNumber = i, Body = new { relatedId3 }.ToJson(), Timestamp = Clock.Now(), StreamName = "two", Type = "Event" + i.ToString() })); Enumerable.Range(1, 20).ForEach(i => db.Events.Add(new StorableEvent { AggregateId = relatedId3, SequenceNumber = i, Body = new { relatedId4 }.ToJson(), Timestamp = Clock.Now(), StreamName = "three", Type = "Event" + i.ToString() })); Enumerable.Range(1, 20).ForEach(i => db.Events.Add(new StorableEvent { AggregateId = relatedId4, SequenceNumber = i, Body = new { }.ToJson(), Timestamp = Clock.Now(), StreamName = "three", Type = "Event" + i.ToString() })); Enumerable.Range(1, 20).ForEach(i => db.Events.Add(new StorableEvent { AggregateId = unrelatedId, SequenceNumber = i, Body = new { }.ToJson(), Timestamp = Clock.Now(), StreamName = "three", Type = "Event" + i.ToString() })); db.SaveChanges(); } var client = CreateClient(); // act var response = client.GetAsync("http://contoso.com/api/events/related/" + relatedId1) .Result .ShouldSucceed() .JsonContent(); Console.WriteLine(response); // assert // events.Count().Should().Be(80); // events.Should().Contain(e => e.AggregateId == relatedId1); // events.Should().Contain(e => e.AggregateId == relatedId2); // events.Should().Contain(e => e.AggregateId == relatedId3); // events.Should().Contain(e => e.AggregateId == relatedId4); // events.Should().NotContain(e => e.AggregateId == unrelatedId); // TODO: (Related_events_are_available_via_diagnostics_endpoint) write test Assert.Fail("Test not written yet."); }
/// <summary> /// Returns the Id of the latest event written to the event store, or 0 if the event store is empty. /// </summary> public static long HighestEventId(this EventStoreDbContext db) => db.Events.Max <StorableEvent, long?>(e => e.Id) ?? 0;