diff --git a/README.md b/README.md index b2f850a..068b202 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # Wilcommerce.Core.Data.EFCore Contains an implementation of the [Wilcommerce.Core](https://github.com/wilcommerce/Wilcommerce.Core) packages using Entity Framework Core as persistence framework. +## Requirements +According to Entity Framework Core 3.0 requirements, this project is built using NETStandard 2.1 as Target Framework (See [https://docs.microsoft.com/it-it/ef/core/what-is-new/ef-core-3.0/breaking-changes#netstandard21](https://docs.microsoft.com/it-it/ef/core/what-is-new/ef-core-3.0/breaking-changes#netstandard21) for further informations). + +This means it will not run on projects which target .NET Framework. + +If you have some specific needs you can [open a issue on GitHub](https://github.com/wilcommerce/Wilcommerce.Core.Data.EFCore/issues) or you can consider to [contribute to Wilcommerce](CONTRIBUTING.md). + ## Installation Nuget package available here @@ -8,11 +15,31 @@ Nuget package available here [https://www.nuget.org/packages/Wilcommerce.Core.Data.EFCore](https://www.nuget.org/packages/Wilcommerce.Core.Data.EFCore) ## Usage -Add the the EventsContext class to your project. +Add the the EventsContext class to your ASP.NET Core project. ``` -services.AddDbContext(options => // Specify your provider); +services.AddDbContext(options => + options.UseSqlServer("[ConnectionStringName]", + b => b.MigrationAssembly("[Your assembly name]"))); +``` +The EventsContext is injected in the EventStore implementation. + +After the DbContext has been registered in the ServiceCollection, you need to add a migration using **donet** CLI or the **Package Manager Console**. +``` +// Using dotnet CLI +dotnet ef migrations add -c Wilcommerce.Core.Data.EFCore.EventsContext Initial + +// Using Package Manager Console +EntityFrameworkCore\Add-Migration -Context Wilcommerce.Core.Data.EFCore.EventsContext Initial +``` + +After this, you can update your database: +``` +// Using dotnet CLI +dotnet ef database update -c Wilcommerce.Core.Data.EFCore.EventsContext + +// Using Package Manager Console +EntityFrameworkCore\Update-Database -Context Wilcommerce.Core.Data.EFCore.EventsContext ``` -The EventsContext is injected in the EventStore implementation ## EventStore Component The EventStore class is the Entity Framework Core implementation of the [IEventStore](https://github.com/wilcommerce/Wilcommerce.Core/blob/master/src/Wilcommerce.Core.Infrastructure/IEventStore.cs) interface. diff --git a/Wilcommerce.Core.Data.EFCore.Test/Events/EventStoreTest.cs b/Wilcommerce.Core.Data.EFCore.Test/Events/EventStoreTest.cs index 02f58e4..dbc4501 100644 --- a/Wilcommerce.Core.Data.EFCore.Test/Events/EventStoreTest.cs +++ b/Wilcommerce.Core.Data.EFCore.Test/Events/EventStoreTest.cs @@ -2,6 +2,7 @@ using System.Linq; using Wilcommerce.Core.Data.EFCore.Events; using Wilcommerce.Core.Data.EFCore.Test.Fixtures; +using Wilcommerce.Core.Infrastructure; using Xunit; namespace Wilcommerce.Core.Data.EFCore.Test.Events @@ -10,37 +11,144 @@ public class EventStoreTest : IClassFixture { private EventsFixtures _fixtures; - private EventStore _eventStore; - public EventStoreTest(EventsFixtures fixtures) { _fixtures = fixtures; - _eventStore = new EventStore(_fixtures.Context); + } + + [Fact] + public void Ctor_Should_Throw_ArgumentNullException_If_EventsContext_Is_Null() + { + EventsContext context = null; + + var ex = Assert.Throws(() => new EventStore(context)); + Assert.Equal(nameof(context), ex.ParamName); } [Fact] public void Find_NewAdministratorCreatedEvent_Should_Return_Rows_And_Match_Email_And_Name() { - var events = _eventStore.Find(DateTime.Now); - int count = events.Count(); + using (var context = _fixtures.BuildContext()) + { + var eventStore = new EventStore(context); + _fixtures.PrepareData(context, new NewAdministratorCreatedEvent[] + { + new NewAdministratorCreatedEvent(_fixtures.UserId, "Administrator", "admin@wilcommerce.com") + }); + + var events = eventStore.Find(DateTime.Now); + int count = events.Count(); + + Assert.True(count > 0); + + bool exists = events.Any(e => e.Email == "admin@wilcommerce.com" && e.Name == "Administrator"); + Assert.True(exists); + + _fixtures.CleanAllData(context); + } + } + + [Fact] + public void FindAll_Should_Return_All_Events_Related_To_Specified_Entity() + { + using (var context = _fixtures.BuildContext()) + { + var eventStore = new EventStore(context); + _fixtures.PrepareData(context, new DomainEvent[] + { + new NewAdministratorCreatedEvent(_fixtures.UserId, "Administrator", "admin@wilcommerce.com"), + new UserEnabledEvent(_fixtures.UserId), + new UserDisabledEvent(_fixtures.UserId) + }); + + var entityType = typeof(User); + var events = eventStore.FindAll(entityType.ToString(), _fixtures.UserId, DateTime.Now); + + int count = events.Count(); + Assert.True(count == 3); + + _fixtures.CleanAllData(context); + } + } + + [Fact] + public void Find_NewAdministratorCreatedEvent_By_EntityType_And_TimeStamp_Should_Return_Rows_And_Match_Email_And_Name() + { + using (var context = _fixtures.BuildContext()) + { + var eventStore = new EventStore(context); + _fixtures.PrepareData(context, new NewAdministratorCreatedEvent[] + { + new NewAdministratorCreatedEvent(_fixtures.UserId, "Administrator", "admin@wilcommerce.com") + }); + + var entityType = typeof(User); + var events = eventStore.Find(entityType.ToString(), DateTime.Now); + int count = events.Count(); + + Assert.True(count > 0); + + bool exists = events.Any(e => e.Email == "admin@wilcommerce.com" && e.Name == "Administrator" && e.AggregateType == entityType); + Assert.True(exists); + + _fixtures.CleanAllData(context); + } + } + + [Fact] + public void Find_NewAdministratorCreatedEvent_By_EntityType_EntityId_And_TimeStamp_Should_Return_Rows_And_Match_Email_And_Name() + { + using (var context = _fixtures.BuildContext()) + { + var eventStore = new EventStore(context); + _fixtures.PrepareData(context, new NewAdministratorCreatedEvent[] + { + new NewAdministratorCreatedEvent(_fixtures.UserId, "Administrator", "admin@wilcommerce.com") + }); + + var entityType = typeof(User); + var events = eventStore.Find(entityType.ToString(), _fixtures.UserId, DateTime.Now); + int count = events.Count(); + + Assert.True(count > 0); + + bool exists = events.Any(e => e.Email == "admin@wilcommerce.com" && e.Name == "Administrator" && e.AggregateId == _fixtures.UserId && e.AggregateType == entityType); + Assert.True(exists); + + _fixtures.CleanAllData(context); + } + } + + [Fact] + public void Save_Should_Throw_ArgumentNullException_If_Event_Is_Null() + { + using (var context = _fixtures.BuildContext()) + { + var eventStore = new EventStore(context); - Assert.True(count > 0); + DomainEvent @event = null; + var ex = Assert.Throws(() => eventStore.Save(@event)); - bool exists = events.Any(e => e.Email == "admin@wilcommerce.com" && e.Name == "Administrator"); - Assert.True(exists); + Assert.Equal(nameof(@event), ex.ParamName); + } } [Fact] public void Save_NewAdministratorCreatedEvent_Should_Increment_EventsNumber() { - int count = _eventStore.Find(DateTime.Now).Count(); + using (var context = _fixtures.BuildContext()) + { + var eventStore = new EventStore(context); + + var ev = new NewAdministratorCreatedEvent(Guid.NewGuid(), "Administrator2", "admin2@wilcommerce.com"); + eventStore.Save(ev); - var ev = new NewAdministratorCreatedEvent(Guid.NewGuid(), "Administrator2", "admin2@wilcommerce.com"); - _eventStore.Save(ev); + int count = eventStore.Find(DateTime.Now).Count(); - int newCount = _eventStore.Find(DateTime.Now).Count(); + Assert.Equal(1, count); - Assert.Equal(count + 1, newCount); + _fixtures.CleanAllData(context); + } } } } diff --git a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/EventsFixtures.cs b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/EventsFixtures.cs index eadcf9e..9064ff3 100644 --- a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/EventsFixtures.cs +++ b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/EventsFixtures.cs @@ -1,67 +1,58 @@ using Microsoft.EntityFrameworkCore; using System; +using System.Collections.Generic; +using System.Linq; using Wilcommerce.Core.Common.Events; +using Wilcommerce.Core.Infrastructure; namespace Wilcommerce.Core.Data.EFCore.Test.Fixtures { public class EventsFixtures : IDisposable { - public EventsContext Context { get; protected set; } - public Guid UserId { get; protected set; } + private DbContextOptions _contextOptions; + public EventsFixtures() { UserId = Guid.NewGuid(); - - BuildContext(); - PrepareData(); + BuildContextOptions(); } - public void Dispose() + public EventsContext BuildContext() { - CleanData(); - - if (Context != null) - { - Context.Dispose(); - } - - GC.SuppressFinalize(this); + return new EventsContext(this._contextOptions); } - protected virtual void PrepareData() + public void PrepareData(EventsContext context, IEnumerable events) { - var administratorCreatedEvent = new NewAdministratorCreatedEvent(UserId, "Administrator", "admin@wilcommerce.com"); - Context.Events.Add(EventWrapper.Wrap(administratorCreatedEvent)); - - for (int i = 0; i < 3; i++) + if (events != null && events.Count() > 0) { - var ev = new UserEnabledEvent(UserId); - Context.Events.Add(EventWrapper.Wrap(ev)); - } + var eventsWrapped = events.Select(ev => EventWrapper.Wrap(ev)).ToArray(); + context.AddRange(eventsWrapped); - for (int i = 0; i < 2; i++) - { - var ev = new UserDisabledEvent(UserId); - Context.Events.Add(EventWrapper.Wrap(ev)); + context.SaveChanges(); } + } - Context.SaveChanges(); + public void CleanAllData(EventsContext context) + { + context.RemoveRange(context.Events); + context.SaveChanges(); } - protected virtual void CleanData() + public void Dispose() { - Context.Events.RemoveRange(Context.Events); + GC.SuppressFinalize(this); } - protected virtual void BuildContext() + protected virtual void BuildContextOptions() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: "InMemory-Events") .Options; - Context = new EventsContext(options); + this._contextOptions = options; } } } diff --git a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/NewAdministratorCreatedEvent.cs b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/NewAdministratorCreatedEvent.cs index 9dcbed2..645bc0c 100644 --- a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/NewAdministratorCreatedEvent.cs +++ b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/NewAdministratorCreatedEvent.cs @@ -10,7 +10,7 @@ public class NewAdministratorCreatedEvent : DomainEvent public string Email { get; private set; } public NewAdministratorCreatedEvent(Guid userId, string name, string email) - : base(userId ,typeof(NewAdministratorCreatedEvent)) + : base(userId ,typeof(User)) { UserId = userId; Name = name; diff --git a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/User.cs b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/User.cs new file mode 100644 index 0000000..a8f4b63 --- /dev/null +++ b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/User.cs @@ -0,0 +1,13 @@ +using System; + +namespace Wilcommerce.Core.Data.EFCore.Test.Fixtures +{ + public class User + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string Email { get; set; } + } +} diff --git a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserDisabledEvent.cs b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserDisabledEvent.cs index 165b59b..0ea493d 100644 --- a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserDisabledEvent.cs +++ b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserDisabledEvent.cs @@ -8,7 +8,7 @@ public class UserDisabledEvent : DomainEvent public Guid UserId { get; private set; } public UserDisabledEvent(Guid userId) - : base(userId, typeof(UserDisabledEvent)) + : base(userId, typeof(User)) { UserId = userId; } diff --git a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserEnabledEvent.cs b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserEnabledEvent.cs index 3238d97..40f743d 100644 --- a/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserEnabledEvent.cs +++ b/Wilcommerce.Core.Data.EFCore.Test/Fixtures/UserEnabledEvent.cs @@ -8,7 +8,7 @@ public class UserEnabledEvent : DomainEvent public Guid UserId { get; private set; } public UserEnabledEvent(Guid userId) - : base(userId, typeof(UserEnabledEvent)) + : base(userId, typeof(User)) { UserId = userId; } diff --git a/Wilcommerce.Core.Data.EFCore.Test/Wilcommerce.Core.Data.EFCore.Test.csproj b/Wilcommerce.Core.Data.EFCore.Test/Wilcommerce.Core.Data.EFCore.Test.csproj index 22aa152..a6eecde 100644 --- a/Wilcommerce.Core.Data.EFCore.Test/Wilcommerce.Core.Data.EFCore.Test.csproj +++ b/Wilcommerce.Core.Data.EFCore.Test/Wilcommerce.Core.Data.EFCore.Test.csproj @@ -1,17 +1,17 @@ - netcoreapp2.1 + netcoreapp3.0 false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..d5cf0f7 Binary files /dev/null and b/icon.png differ diff --git a/src/Wilcommerce.Core.Data.EFCore/Events/EventStore.cs b/src/Wilcommerce.Core.Data.EFCore/Events/EventStore.cs index 40d6f66..3bc86e0 100644 --- a/src/Wilcommerce.Core.Data.EFCore/Events/EventStore.cs +++ b/src/Wilcommerce.Core.Data.EFCore/Events/EventStore.cs @@ -23,7 +23,7 @@ public class EventStore : IEventStore /// The db context instance public EventStore(EventsContext context) { - _context = context; + _context = context ?? throw new ArgumentNullException(nameof(context)); } /// @@ -35,13 +35,13 @@ public EventStore(EventsContext context) public IEnumerable Find(DateTime timestamp) where TEvent : DomainEvent { var eventType = typeof(TEvent); - string eventTypeString = eventType.ToString(); + string eventTypeString = ConvertEntityTypeToString(eventType); var events = _FindBy(e => e.EventType == eventTypeString && e.Timestamp <= timestamp); return events .AsEnumerable() - .Select(e => e.GetEventData(eventType) as TEvent); + .Select(e => e.Event as TEvent); } /// @@ -54,13 +54,13 @@ public IEnumerable Find(DateTime timestamp) where TEvent : Domai public IEnumerable Find(string entityType, DateTime timestamp) where TEvent : DomainEvent { var eventType = typeof(TEvent); - string eventTypeString = eventType.ToString(); + string eventTypeString = ConvertEntityTypeToString(eventType); var events = _FindBy(e => e.EventType == eventTypeString && e.AggregateType == entityType && e.Timestamp <= timestamp); return events .AsEnumerable() - .Select(e => e.GetEventData(eventType) as TEvent); + .Select(e => e.Event as TEvent); } /// @@ -74,13 +74,13 @@ public IEnumerable Find(string entityType, DateTime timestamp) w public IEnumerable Find(string entityType, Guid entityId, DateTime timestamp) where TEvent : DomainEvent { var eventType = typeof(TEvent); - string eventTypeString = eventType.ToString(); + string eventTypeString = ConvertEntityTypeToString(eventType); var events = _FindBy(e => e.EventType == eventTypeString && e.AggregateType == entityType && e.AggregateId == entityId && e.Timestamp <= timestamp); return events .AsEnumerable() - .Select(e => e.GetEventData(eventType) as TEvent); + .Select(e => e.Event as TEvent); } /// @@ -96,7 +96,7 @@ public IEnumerable FindAll(string entityType, Guid entityId, DateTi return events .AsEnumerable() - .Select(e => e.GetEventData(Type.GetType(e.EventType)) as DomainEvent); + .Select(e => e.Event as DomainEvent); } /// @@ -106,6 +106,11 @@ public IEnumerable FindAll(string entityType, Guid entityId, DateTi /// The event to save public void Save(TEvent @event) where TEvent : DomainEvent { + if (@event == null) + { + throw new ArgumentNullException(nameof(@event)); + } + try { var ev = EventWrapper.Wrap(@event); @@ -136,6 +141,16 @@ protected virtual IQueryable _FindBy(Expression e.Timestamp); } + + /// + /// Returns the event type as a string + /// + /// The event type to convert + /// The event type as a string + protected string ConvertEntityTypeToString(Type eventType) + { + return $"{eventType.FullName}, {eventType.Assembly.GetName().Name}"; + } #endregion } } diff --git a/src/Wilcommerce.Core.Data.EFCore/Mapping/EventMapping.cs b/src/Wilcommerce.Core.Data.EFCore/Mapping/EventMapping.cs index 0ae7c5a..9f71207 100644 --- a/src/Wilcommerce.Core.Data.EFCore/Mapping/EventMapping.cs +++ b/src/Wilcommerce.Core.Data.EFCore/Mapping/EventMapping.cs @@ -19,6 +19,9 @@ public static ModelBuilder MapEvents(this ModelBuilder modelBuilder) .ToTable("Wilcommerce_Events") .HasKey(e => e.Id); + modelBuilder.Entity() + .Ignore(e => e.Event); + return modelBuilder; } } diff --git a/src/Wilcommerce.Core.Data.EFCore/Wilcommerce.Core.Data.EFCore.csproj b/src/Wilcommerce.Core.Data.EFCore/Wilcommerce.Core.Data.EFCore.csproj index d42fb50..36c57b1 100644 --- a/src/Wilcommerce.Core.Data.EFCore/Wilcommerce.Core.Data.EFCore.csproj +++ b/src/Wilcommerce.Core.Data.EFCore/Wilcommerce.Core.Data.EFCore.csproj @@ -2,23 +2,32 @@ 1.0.0 - netstandard2.0 + netstandard2.1 Wilcommerce.Core.Data.EFCore Wilcommerce.Core.Data.EFCore wilcommerce;ecommerce;opensource;.netcore;.net;entity framework core - http://www.wilcommerce.com/logo64.png + icon.png https://github.com/wilcommerce/Wilcommerce.Core.Data.EFCore - https://github.com/wilcommerce/Wilcommerce.Core.Data.EFCore/blob/master/LICENSE git https://github.com/wilcommerce/Wilcommerce.Core.Data.EFCore false false false - 1.0.0-rc4 - + 1.0.0 + Move to .NETStandard 2.1 Alberto Mori + Wilcommerce Core implementation for Entity Framework Core + Wilcommerce.Core.Data.EFCore + MIT + + + True + + + + bin\Debug\netstandard2.0\Wilcommerce.Core.Data.EFCore.xml @@ -28,15 +37,18 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + all runtime; build; native; contentfiles; analyzers - +