Replace SQLite to LocalDb for unittesting for a ABP.io project - entity-framework-core

I am working with ABP and I used the basic setup which is generated by the abp cli with EF Core and SQL Server.
Now, I would like to replace SQLite (the generated one), with a LocalDb database, since I using spatial data for unit- and integration testing.
However, I am not sure how to make the replacement. I have identified the file which initialize the SQLite, and it is reseeded after each unittest.
When switching to LocalDb, I will not flush / seed database, and I think I have solved that out.
This is the generated file ...\ABPProject\aspnet-core\ABPProject.EntityFrameworkCore.Tests\EntityFrameworkCore\ABPProjectEntityFrameworkCoreTestModule.cs that uses SQLite, can you please give me an example / ideas how to use LocalDb instead of SQLite?
[DependsOn(
typeof(ABPProjectEntityFrameworkCoreModule),
typeof(ABPProjectTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ABPProjectEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
ConfigureInMemorySqlite(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_sqliteConnection.Dispose();
}
private static SqliteConnection CreateDatabaseAndGetConnection()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ABPProjectDbContext>()
.UseSqlite(connection)
.Options;
using (var context = new ABPProjectDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}

This was really simple. Just do this:
namespace ABPProject.EntityFrameworkCore;
[DependsOn(
typeof(ABPProjectEntityFrameworkCoreModule),
typeof(ABPProjectTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ABPProjectEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
//ConfigureInMemorySqlite(context.Services);
ConfigureLocalDb(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection_Sqlite();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
private void ConfigureLocalDb(IServiceCollection services)
{
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlServer("Server=(LocalDb)\\MSSQLLocalDB;Database=ABPProject_Unittest;Trusted_Connection=True;TrustServerCertificate=True");
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
if (_sqliteConnection is not null)
{
_sqliteConnection.Dispose();
}
}
private static SqliteConnection CreateDatabaseAndGetConnection_Sqlite()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ABPProjectDbContext>()
.UseSqlite(connection, x => x.UseNetTopologySuite())
.Options;
using (var context = new ABPProjectDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}
Then you need to disable re-seeding each time between test in aspnet-core\test\ABPProject.TestBase\ABPProjectTestBaseModule.cs
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
//SeedTestData(context);
}
How to reset the database when starting all tests? Use XUnit collections, which enable setup before all tests are starting and teardown after all tests are executed.
You can read more about it here https://xunit.net/docs/shared-context but basically, you create a new class (NOTE! You need to place this file in each test project, since the class can not be shared between different test projects):
public class DatabaseFixture : ABPProjectTestBase<ABPProjectEntityFrameworkCoreTestModule>, IAsyncLifetime
{
// ... create your shared vars here
public DatabaseFixture()
{
}
public async Task InitializeAsync()
{
IDbContextProvider<ABPProjectDbContext> _dbContextProvider = GetRequiredService<IDbContextProvider<ABPProjectDbContext>>();
IUnitOfWorkManager _unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
IDataSeeder _dataSeeder = GetRequiredService<IDataSeeder>();
using (var uow = _unitOfWorkManager.Begin())
{
var context = await _dbContextProvider.GetDbContextAsync();
var tableNames = context.Model.GetEntityTypes()
.Where(t => t.GetTableName().StartsWith("App"))
.Select(t => t.GetTableName())
.Distinct()
.ToList();
// Disable constraints
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {tableName} NOCHECK CONSTRAINT ALL");
}
// Remove rows
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"DELETE FROM {tableName}");
}
// Re-enable constraints
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {tableName} CHECK CONSTRAINT ALL");
}
}
// Re - seed database
await _dataSeeder.SeedAsync();
}
public async Task DisposeAsync()
{
// ... clean up test data from the database with and use async calls here...
}
}
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
The last thing is to decorate each test class with following:
namespace ABPProject.Things
{
[Collection("Database collection")]
public class ThingAppService_Tests : ABPProjectApplicationTestBase
{
public readonly DatabaseFixture _fixture;
private readonly IThingAppService _thingAppService;
public ThingAppService_Tests(DatabaseFixture fixture)
{
_fixture = fixture;
_thingAppService = GetRequiredService<IThingAppService>();
}
[Fact]
public async Task GetListAsync_Should_Get_all_Things()
{
// This is a test
}
}
}

Related

Dataseeding not working when using WebApplicationFactory in integration test with nUnit

I'm having problems with seeding my inmemory database with test-data in nUnit integration tests.
I've created a custom WebApplicationFactory so i could use an inmemory database:
public class SiriusWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IAuthenticationSchemeProvider));
services.Remove(descriptor);
CleanupDatabaseRegistrations<SiriusContext>(services);
services.AddDbContext<SiriusContext>(options => options.UseInMemoryDatabase($"Testdb-{Guid.NewGuid()}"));
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database contexts
using var scope = sp.CreateScope();
var scopedServices = scope.ServiceProvider;
var context = scopedServices.GetRequiredService<SiriusContext>();
// Ensure the database is created.
context.Database.EnsureCreated();
});
}
private void CleanupDatabaseRegistrations<TDbContext>(IServiceCollection services) where TDbContext : DbContext
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(DbContextOptions));
if (descriptor != null)
{
services.Remove(descriptor);
}
descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(TDbContext));
if (descriptor != null)
{
services.Remove(descriptor);
}
}
}
After this, i've created a base class which is being used by all my integration test classes.
This base class implements the nUnit OneTimeSetup method where i create the HttpClient for testing my controllers:
protected HttpClient HttpClient;
protected SiriusContext DbContext;
[OneTimeSetUp]
public void OneTimeSetUp()
{
_webApplicationFactory = new SiriusWebApplicationFactory();
HttpClient = _webApplicationFactory.CreateClient();
DbContext = _webApplicationFactory.Services.GetService<SiriusContext>();
DoReseed();
}
As you can see i'm exposing my DbContext so that my test classes can seed the test data.
The DoReseed method is just an emppty method used by my integration test classes to provide test data.
protected virtual void DoReseed() { }
The implementation inside one of my testclasses looks like this:
public class AdminAanbestedingApplicatieControllerTests : BaseIntegrationTest
{
[Test]
public async Task AanbestedingApplicaties_GetAll_ShouldReturnData()
{
var result = await HttpClient.GetFromApi_AssertSuccess<List<AanbestedingApplicatie>>(RouteConstants.Lijsten.AdminAanbestedingApplicaties.GetAll);
Assert.IsTrue(result.Count > 1);
}
protected override void DoReseed()
{
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_Actief);
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_InActief);
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_ToDelete);
DbContext.AanbestedingApplicaties.Add(AanbestedingApplicatiesObjectMother.AanbestedingApplicatie_ToUpdate);
DbContext.SaveChanges();
}
}
When i execute my test, the seeding gets executed but the repository cannot see the seeded data?
The DbContext inside my repo does not contain my seeded data...
Anyone has any idea why the DbContext inside my repository does not contain the seeded test data?
The repo looks like this:
public IEnumerable<AanbestedingApplicatie> GetAll()
{
return SiriusContext.AanbestedingApplicaties.OrderBy(l => l.Naam).ProjectTo<AanbestedingApplicatie>(Mapper.ConfigurationProvider)
.ToList();
}
The Context has been injected inside the constructor...

Testing code in a custom NancyFx Bootstrapper

I have a custom Nancy Bootstrapper which uses StructureMapNancyBootstrapper but the issue is the same regardless of container.
public class CustomNancyBootstrapper : StructureMapNancyBootstrapper
{
protected override void RequestStartup(IContainer container, IPipelines pipelines, NancyContext context)
{
var auth = container.GetInstance<ICustomAuth>();
auth.Authenticate(context);
}
}
I want to write a test to assert that Authenticate is called with the context... something like this...
[Test]
public void RequestStartup_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuthentication = new Mock<ICustomAuth>();
var mockContainer = new Mock<IContainer>();
var mockPipelines = new Mock<IPipelines>();
var context = new NancyContext();
mockContainer.Setup(x => x.GetInstance<ICustomAuth>()).Returns(mockAuthentication.Object);
// exercise
_bootstrapper.RequestStartup(_mockContainer.Object, _mockPipelines.Object, context);
// verify
mockAuthentication.Verify(x => x.Authenticate(context), Times.Once);
}
The problem is that I can't call RequestStartup because it's protected as defined in NancyBootstrapperBase.
protected virtual void RequestStartup(TContainer container, IPipelines pipelines, NancyContext context);
Is there a "proper"/"offical" Nancy way to do this without creating another derived class and exposing the methods as that just seems like a hack?
Thanks
I guess you can "fake" the request by using Browser from Nancy.Testing:
var browser = new Browser(new CustomNancyBootstrapper());
var response = browser.Get("/whatever");
There is a good set of articles about testing NancyFx application:
http://www.marcusoft.net/2013/01/NancyTesting1.html
Turns out Nancy offers a IRequetStartup interface so you can take the code out of the custom bootstrapper and do something like this...
public class MyRequestStart : IRequestStartup
{
private readonly ICustomAuth _customAuthentication;
public MyRequestStart(ICustomAuth customAuthentication)
{
if (customAuthentication == null)
{
throw new ArgumentNullException(nameof(customAuthentication));
}
_customAuthentication = customAuthentication;
}
public void Initialize(IPipelines pipelines, NancyContext context)
{
_customAuthentication.Authenticate(context);
}
}
and the test is easy and concise
[Test]
public void When_Initialize_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuth = new Mock<ICustomAuth>();
var requestStartup = new MyRequestStart(mockAuth.Object);
var mockPipeline = new Mock<IPipelines>();
var context = new NancyContext();
// exercise
requestStartup.Initialize(mockPipeline.Object, context);
// verify
mockAuth.Verify(x => x.Authenticate(context), Times.Once);
}
https://github.com/NancyFx/Nancy/wiki/The-Application-Before%2C-After-and-OnError-pipelines#implementing-interfaces

How do you mock adding items to a repository or DbContext using moq?

The examples I've seen for using moq for a repository only show how to mock things being returned. I have a somewhat strange requirement: when a query is executed, if a condition exists, a certain item should be added to the repository. I am wondering how to test this without querying the database. I know how to mock the condition existing, but then how do you setup the mock so that you can test that the certain item is added?
Try to use fake in memory repository instead of moq, for example universal generic repository for all entities:
public interface IInMemoryRepository<T> where T : class
{
IQueryable<T> GetAll();
void Create(T item);
void Update(T item);
T GetItem(Expression<Func<T, bool>> expression);
void Delete(T item);
}
public class InMemoryRepository<T> : IInMemoryRepository<T> where T : class
{
private int _incrementer = 0;
public Dictionary<int, T> List = new Dictionary<int, T>();
public IQueryable<T> GetAll()
{
return List.Select(x => x.Value).AsQueryable();
}
public void Create(T item)
{
_incrementer++;
item.GetType().GetProperties().First(p => p.Name == "Id").SetValue(item, _incrementer, null);
List.Add(_incrementer, item);
}
public void Update(T item)
{
var key = (int)item.GetType().GetProperties().First(p => p.Name == "Id").GetValue(item, null);
List[key] = item;
}
public T GetItem(Expression<Func<T, bool>> expression)
{
return List.Select(x => x.Value).SingleOrDefault(expression.Compile());
}
public void Delete(T item)
{
var key = (int)item.GetType().GetProperties().First(p => p.Name == "Id").GetValue(item, null);
List.Remove(key);
}
}
You would not mock the repository; you would have an alternate repository that would use an in-memory store instead of the database, then use IoC to select the correct repository implementation for tests/code.
This blog article might be of use, although my design has changed somewhat since I wrote the post and I really need to update it. I used teh generic repository pattern in a way that enables the DbContext to be mocked. This allows the data access layer to be tested 'right up to the edges'.
Times have changed -- since the release of Entity Framework 6 it has become much easier to mock database context and datasets. This article outlines the particulars.
Testing non-query scenarios
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Data.Entity;
namespace TestingDemo
{
[TestClass]
public class NonQueryTests
{
[TestMethod]
public void CreateBlog_saves_a_blog_via_context()
{
var mockSet = new Mock<DbSet<Blog>>();
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(m => m.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet");
mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
}
}
}
Testing query scenarios
Query testing is pretty sweet now, because you can build up test data sets in code and then execute your tests against them:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace TestingDemo
{
[TestClass]
public class QueryTests
{
[TestMethod]
public void GetAllBlogs_orders_by_name()
{
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
}.AsQueryable();
var mockSet = new Mock<DbSet<Blog>>();
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(0 => data.GetEnumerator());
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
var blogs = service.GetAllBlogs();
Assert.AreEqual(3, blogs.Count);
Assert.AreEqual("AAA", blogs[0].Name);
Assert.AreEqual("BBB", blogs[1].Name);
Assert.AreEqual("ZZZ", blogs[2].Name);
}
}
}
You can do this by mocking the DbSet.Add() method, like so:
[Fact]
public void CreateBlog_saves_a_blog_via_context()
{
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
};
var mockSet = new Mock<DbSet<Blog>>();
mockSet.Setup(blogs => blogs.Add(It.IsAny<Blog>)).Returns<Blog>(blog =>
{
data.Add(blog);
return blog;
});
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(m => m.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
var blog = service.AddBlog("_ADO.NET Blog", "http://blogs.msdn.com/adonet");
var blogs = service.GetAllBlogs();
mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
Assert.NotNull(blog)
Assert.Equal(4, blogs.Count);
Assert.Equal("AAA", blogs(1).Name);
Assert.Equal("BBB", blogs(2).Name);
Assert.Equal("ZZZ", blogs(3).Name);
}
This is adapted from the documentation found here.

Efficient way of checking if many-to-many relationship exists in EF4.1

I have a many-to-many relationship between two entities - Media and MediaCollection. I want to check if a certain Media already exists in a collection. I can do this as follows:
mediaCollection.Media.Any(m => m.id == mediaId)
However, mediaCollection.Media is an ICollection, so to me this looks like it will have to retrieve every Media in the collection from the database just to make this check. As there could be many media in a collection, this seems very inefficient. I'n thinking that I should use a method of IQueryable, but I can't see how to do this for many-to-many relationships.
How can I check for the existence of the relationship without retrieving the whole collection?
EDIT
I am generating the EF data model from my database, then using the built in VS POCO T4 templates to generate my data context and entity classes. I think the problem is that the generated code does not return EntityCollection for the navigation properties, but instead ObjectSet. ObjectSet implements IQueryable, but does not expose a CreateSourceQuery() method.
Here is a stripped down version of the relevant lines from the context:
public partial class Entities : ObjectContext
{
public const string ConnectionString = "name=Entities";
public const string ContainerName = "Entities";
#region Constructors
public Entities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(string connectionString)
: base(connectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public Entities(EntityConnection connection)
: base(connection, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
#endregion
#region ObjectSet Properties
public ObjectSet<MediaCollection> MediaCollections
{
get { return _mediaCollections ?? (_mediaCollections = CreateObjectSet<MediaCollection>("MediaCollections")); }
}
private ObjectSet<MediaCollection> _mediaCollections;
// snipped many more
#endregion
}
And here is a stripped down version of the class for the MediaCollection entity:
public partial class MediaCollection
{
#region Primitive Properties
// snipped
#endregion
#region Navigation Properties
public virtual ICollection<Medium> Media
{
get
{
if (_media == null)
{
var newCollection = new FixupCollection<Medium>();
newCollection.CollectionChanged += FixupMedia;
_media = newCollection;
}
return _media;
}
set
{
if (!ReferenceEquals(_media, value))
{
var previousValue = _media as FixupCollection<Medium>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupMedia;
}
_media = value;
var newValue = value as FixupCollection<Medium>;
if (newValue != null)
{
newValue.CollectionChanged += FixupMedia;
}
}
}
}
private ICollection<Medium> _media;
private void FixupMedia(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Medium item in e.NewItems)
{
if (!item.MediaCollections.Contains(this))
{
item.MediaCollections.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Medium item in e.OldItems)
{
if (item.MediaCollections.Contains(this))
{
item.MediaCollections.Remove(this);
}
}
}
}
// snip
#endregion
}
And finally, here is the FixupCollection that the template also generates:
public class FixupCollection<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
new List<T>(this).ForEach(t => Remove(t));
}
protected override void InsertItem(int index, T item)
{
if (!this.Contains(item))
{
base.InsertItem(index, item);
}
}
}
You can do that but you need a context for that:
bool exists = context.Entry(mediaCollection)
.Collection(m => m.Media)
.Query()
.Any(x => x.Id == mediaId);
Edit:
If you are using ObjectContext API with proxied POCOs instead of DbContext API the former sample will not work. You can try this:
context.ContextOptions.LazyLoadingEnabled = false;
bool exists = ((EntityCollection<Media>)mediaCollection.Media).CreateSourceQuery()
.Any(x => x.Id == mediaId);
context.ContextOptions.LazyLoadingEnabled = true;
So it seems that the built in VS POCO T4 template does not generate anything equivalent to CreateSourceQuery(). No matter! We can code it ourselves. If you add the following code at to the context's .tt file and regenerate:
public ObjectQuery<T> CreateNavigationSourceQuery<T>(object entity, string navigationProperty)
{
var ose = ObjectStateManager.GetObjectStateEntry(entity);
var rm = ObjectStateManager.GetRelationshipManager(entity);
var entityType = (System.Data.Metadata.Edm.EntityType)ose.EntitySet.ElementType;
var navigation = entityType.NavigationProperties[navigationProperty];
var relatedEnd = rm.GetRelatedEnd(navigation.RelationshipType.FullName, navigation.ToEndMember.Name);
return ((dynamic)relatedEnd).CreateSourceQuery();
}
then we can check for the existence of a many-to-many as follows:
var exists = _context.CreateNavigationSourceQuery<Medium>(mediaCollection, "Media")
.Any(m => m.Id == medium.Id);
Props to Rowan's answer on Using CreateSourceQuery in CTP4 Code First for this one.
Try,
mediaCollection.CreateSourceQuery()
.Any(....
CreateSourceQuery will create IQueryable for the association.

How to access repository from IRouter

I'm developing modular application and I'd like for entities from different modules to be able to register their own friendly url slugs.
app.UseMvc(routes =>
{
routes.Routes.Add(new SlugRouter(routes.DefaultHandler));
(...)
});
But following code throws Cannot access a disposed object. Object name: 'CommerceDbContext'. when trying to access slug from the repository.
public class SlugRouter : IRouter
{
private readonly IRouter _target;
public SlugRouter(IRouter target)
{
_target = target;
}
public async Task RouteAsync(RouteContext context)
{
var slugRepository = context.HttpContext.RequestServices.GetService<IRepository<SlugEntity>>();
// ERROR: Cannot access a disposed object. Object name: 'CommerceDbContext'
var urlSlug = await slugRepository.GetAllIncluding(x => x.EntityType).FirstOrDefaultAsync(x => x.Slug == context.HttpContext.Request.Path.Value);
(...)
}
It must be something simple I'm missing to be able to access the repository from router. Thanks for any help.
Begin a unit of work:
public async Task RouteAsync(RouteContext context)
{
var slugRepository = context.HttpContext.RequestServices.GetService<IRepository<SlugEntity>>();
var unitOfWorkManager = context.HttpContext.RequestServices.GetService<IUnitOfWorkManager>();
using (var uow = unitOfWorkManager.Begin())
{
var urlSlug = await slugRepository.GetAllIncluding(x => x.EntityType).FirstOrDefaultAsync(x => x.Slug == context.HttpContext.Request.Path.Value);
await uow.CompleteAsync();
}
}
Access IModel. You do not need dbContext for.
for entities from different modules to be able to register their own
friendly url slugs
I do it this way:
1) move OnModelCreating to static methiod
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
BuildModel(modelBuilder);
}
public static void BuildModel(ModelBuilder modelBuilder)
{
// ...
}
2) Create model where you need:
var conventionSet = new ConventionSet();
var modelBuilder = new ModelBuilder(conventionSet);
AdminkaDbContext.BuildModel(modelBuilder);
var mutableModel = modelBuilder.Model;
There is your meta (in mutableModel ). You can loop through entities (types of entities).