How best to handle data fetching needed for FluentValidation - asp.net-core

In the app I'm working on, I'm using Mediatr and its pipelines to handle database interaction, some minor business logic, validation, etc.
There's a few checks for things like access control I can handle in the pipeline, since I'm using a context object as described here https://jimmybogard.com/sharing-context-in-mediatr-pipelines/ to go from ASP.Net identity to a custom context object with user information and claims.
One problem I'm having is that since this application is multi-tenant, I need to ensure that even if an object exists, it belongs to that tenant, and the only way to be sure of that is to grab the object from the database and check it. It seems to me the validation shouldn't have side effects, so I don't want to rely on that to populate the context object. But then that pushes a bunch of validation down into the Mediatr handlers as they check for object existence, and so on, leading to a lot of repeated code. I don't really want to query the database multiple times since some queries can be expensive.
Another issue with doing the more complicated validation in the actual request handlers is getting what are essentially validation errors back out. Currently, if one of these checks fail I throw a ValidationException, which is then caught by middleware and turned into a ProblemDetails that's returned to the API caller. This is basically exceptions as flow control, and a validation failure really isn't "exceptional" anyhow.
The thoughts I'm having on how to solve this are:
Somewhere in the pipeline, when I'm building the context, include attempting to fetch the objects needed from the database. Validation then fails if any of these are null. This seems like it would make testing harder, as well as needing to decorate the requests somehow (or use reflection) so the pipeline can know to attempt to load these objects.
Have the queries in the validator, but use some sort of cache aware repository so when the same object is queried later, it's served from the cache, and not the database. The handlers would also use this cache aware repository (Currently the handlers interact directly with the EF Core DbContext to query). This then adds the issue of cache invalidation, which I'm going to have to handle at some point, anyhow (quite a few items are seldom modified). For testing, a dummy cache object can be injected that doesn't actually cache anything.
Make all the responses from requests implement an interface (or extend an abstract class) that has validation info, general success flags, etc. This can either be returned through the API directly, or have some pipeline that transforms failures into ProblemDetails. This would add some boilerplate to every response and handler, but avoids exceptions as flow control, and the caching/reflection issues in the other options.
Assume for 1 and 2 that any sort of race conditions are not an issue. Objects don't change owners, and things are seldom actually deleted from the database for auditing/accounting purposes.
I know there's no true one size fits all for problems like this, but I would like to know if there's additional options I'm missing, or any long term maintainability issues anyone with a similar pipeline has encountered if they went with one of these listed options.

We use MediatR IRequestPreProcessor for fetching data that we need both in RequestHandler and in FluentValidation validators.
RequestPreProcessor:
public interface IProductByIdBinder
{
int ProductId { get; }
ProductEntity Product { set; }
}
public class ProductByIdBinder<T> : IRequestPreProcessor<T> where T : IProductByIdBinder
{
private readonly IRepositoryReadAsync<ProductEntity> productRepository;
public ProductByIdBinder(IRepositoryReadAsync<ProductEntity> productRepository)
{
this.productRepository = productRepository;
}
public async Task Process(T request, CancellationToken cancellationToken)
{
request.Product = await productRepository.GetAsync(request.ProductId);
}
}
RequestHandler:
public class ProductDeleteCommand : IRequest, IProductByIdBinder
{
public ProductDeleteCommand(int id)
{
ProductId = id;
}
public int ProductId { get; }
public ProductEntity Product { get; set; }
private class ProductDeleteCommandHandler : IRequestHandler<ProductDeleteCommand>
{
private readonly IRepositoryAsync<ProductEntity> productRepository;
public ProductDeleteCommandHandler(
IRepositoryAsync<ProductEntity> productRepository)
{
this.productRepository = productRepository;
}
public Task<Unit> Handle(ProductDeleteCommand request, CancellationToken cancellationToken)
{
productRepository.Delete(request.Product);
return Unit.Task;
}
}
}
FluentValidation validator:
public class ProductDeleteCommandValidator : AbstractValidator<ProductDeleteCommand>
{
public ProductDeleteCommandValidator()
{
RuleFor(cmd => cmd)
.Must(cmd => cmd.Product != null)
.WithMessage(cmd => $"The product with id {cmd.ProductId} doesn't exist.");
}
}

I see nothing wrong with handling business logic validation in the handler layer.
Moreover, I do not think it is right to throw exceptions for them, as you said it is exceptions as flow control.
Introducing a cache seems like overkill for the use case too. The most reasonable option is the third IMHO.
Instead of implementing an interface you can use the nifty OneOf library and have something like
using HandlerResponse = OneOf<Success, NotFound, ValidationResponse>;
public class MediatorHandler : IRequestHandler<Command, HandlerResponse>
{
public async Task<HandlerResponse> Handle(
Command command,
CancellationToken cancellationToken)
{
Resource resource = await _userRepository
.GetResource(command.Id);
if (resource is null)
return new NotFound();
if (!resource.IsValid)
return new ValidationResponse(new ProblemDetails());
return new Success();
}
And then map it in your API Layer like
public async Task<IActionResult> PostAsync([FromBody] DummyRequest request)
{
HandlerResponse response = await _mediator.Send(
new Command(request.Id));
return response.Match<IActionResult>(
success => Created(),
notFound => NotFound(),
failed => new UnprocessableEntityResult(failed.ProblemDetails))
);
}

Related

I get "A second operation was started on this context before a previous operation completed" error just in one request

In my netcoreapp3.1 project I got the error just from one request I've recently added to my project: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext.
I couldn't find solution because I wrote await before every async request and my db context is transient.
My Db Context:
services.AddDbContext<MyContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
},
ServiceLifetime.Transient
);
And this is where I got the error:
public class ProductDataRepository : UpdatableRepository<PRODUCT_DATA>, IProductDataDAL
{
private readonly MyContext _context;
public ProductDataRepository(MyContext context) : base(context)
{
_context = context;
}
public async Task<PRODUCT_DATA> GetProductById(string productId)
{
return await _context.PRODUCT_DATA.AsNoTracking().FirstOrDefaultAsync(pd => pd.PRODUCTID == productId);
}
public async Task<bool> IsProductMeltable(string productId)
{
// here is where I got the error
return await _context.MELTABLE_PRODUCTS.AsNoTracking().AnyAsync(x => x.PRODUCTID.Equals(productId));
}
}
And my DI:
services.AddScoped<IProductDataDAL, ProductDataRepository>();
services.AddScoped<IProductDataService, ProductDataManager>();
In manager:
public Task<bool> IsProductMeltable(string productId)
{
return await _productDataDAL.IsProductMeltable(productId);
}
In controller:
myModel.IS_MELTABLE = await _commonService.ProductDataService.IsProductMeltable(productData.PRODUCTID);
I also changed my methods from async to sync but still got the same error.
Thanks for your help in advance
Without seeing all the places that these methods might be called, it is difficult to find the source.
But, two things that may help:
The error reported does indicate that the same context is being called multiple times.
Transient means that the DI container will provide a brand new instance each time one is requested.
Regarding that second point, be aware that you are injecting it into a 'scoped' service.
So, this means that whilst your context is 'transient' that does not mean a brand new context is provided each time it is called. It means that a new one is requested each time a context is requested.
As your other services are 'scoped' this means that they only request a context once per request scope. So, even though your context is registered as transient, the SAME context will be used throughout the lifetime of a scoped service that requests it.
I notice that your calling your repository from different layers. One from controller and one from a manager service. This is likely to cause challenges.
Try to keep each layer having different responsibilities.
Best to use the controller as a very thin layer to simply receive HttpRequests and immediately pass responsibilty over to a service layer to do business logic and interact with repositories.
Cleaning that up a little may help you identify the problem.

Blazor concurrency problem using Entity Framework Core

My goal
I want to create a new IdentityUser and show all the users already created through the same Blazor page. This page has:
a form through you will create an IdentityUser
a third-party's grid component (DevExpress Blazor DxDataGrid) that shows all users using UserManager.Users property. This component accepts an IQueryable as a data source.
Problem
When I create a new user through the form (1) I will get the following concurrency error:
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread-safe.
I think the problem is related to the fact that CreateAsync(IdentityUser user) and UserManager.Users are referring the same DbContext
The problem isn't related to the third-party's component because I reproduce the same problem replacing it with a simple list.
Step to reproduce the problem
create a new Blazor server-side project with authentication
change Index.razor with the following code:
#page "/"
<h1>Hello, world!</h1>
number of users: #Users.Count()
<button #onclick="#(async () => await Add())">click me</button>
<ul>
#foreach(var user in Users)
{
<li>#user.UserName</li>
}
</ul>
#code {
[Inject] UserManager<IdentityUser> UserManager { get; set; }
IQueryable<IdentityUser> Users;
protected override void OnInitialized()
{
Users = UserManager.Users;
}
public async Task Add()
{
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" });
}
}
What I noticed
If I change Entity Framework provider from SqlServer to Sqlite then the error will never show.
System info
ASP.NET Core 3.1.0 Blazor Server-side
Entity Framework Core 3.1.0 based on SqlServer provider
What I have already seen
Blazor A second operation started on this context before a previous operation completed: the solution proposed doesn't work for me because even if I change my DbContext scope from Scoped to Transient I still using the same instance of UserManager and its contains the same instance of DbContext
other guys on StackOverflow suggests creating a new instance of DbContext per request. I don't like this solution because it is against Dependency Injection principles. Anyway, I can't apply this solution because DbContext is wrapped inside UserManager
Create a generator of DbContext: this solution is pretty like the previous one.
Using Entity Framework Core with Blazor
Why I want to use IQueryable
I want to pass an IQueryable as a data source for my third-party's component because its can apply pagination and filtering directly to the Query. Furthermore IQueryable is sensitive to CUD
operations.
UPDATE (08/19/2020)
Here you can find the documentation about how to use Blazor and EFCore together
UPDATE (07/22/2020)
EFCore team introduces DbContextFactory inside Entity Framework Core .NET 5 Preview 7
[...] This decoupling is very useful for Blazor applications, where using IDbContextFactory is recommended, but may also be useful in other scenarios.
If you are interested you can read more at Announcing Entity Framework Core EF Core 5.0 Preview 7
UPDATE (07/06/2020)
Microsoft released a new interesting video about Blazor (both models) and Entity Framework Core. Please take a look at 19:20, they are talking about how to manage concurrency problem with EFCore
General solution
I asked Daniel Roth BlazorDeskShow - 2:24:20 about this problem and it seems to be a Blazor Server-Side problem by design.
DbContext default lifetime is set to Scoped. So if you have at least two components in the same page which are trying to execute an async query then we will encounter the exception:
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread-safe.
There are two workaround about this problem:
(A) set DbContext's lifetime to Transient
services.AddDbContext<ApplicationDbContext>(opt =>
opt.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);
(B) as Carl Franklin suggested (after my question): create a singleton service with a static method which returns a new instance of DbContext.
anyway, each solution works because they create a new instance of DbContext.
About my problem
My problem wasn't strictly related to DbContext but with UserManager<TUser> which has a Scoped lifetime. Set DbContext's lifetime to Transient didn't solve my problem because ASP.NET Core creates a new instance of UserManager<TUser> when I open the session for the first time and it lives until I don't close it. This UserManager<TUser> is inside two components on the same page. Then we have the same problem described before:
two components that own the same UserManager<TUser> instance which contains a transient DbContext.
Currently, I solved this problem with another workaround:
I don't use UserManager<TUser> directly instead, I create a new instance of it through IServiceProvider and then it works. I am still looking for a method to change the UserManager's lifetime instead of using IServiceProvider.
tips: pay attention to services' lifetime
This is what I learned. I don't know if it is all correct or not.
I downloaded your sample and was able to reproduce your problem. The problem is caused because Blazor will re-render the component as soon as you await in code called from EventCallback (i.e. your Add method).
public async Task Add()
{
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" });
}
If you add a System.Diagnostics.WriteLine to the start of Add and to the end of Add, and then also add one at the top of your Razor page and one at the bottom, you will see the following output when you click your button.
//First render
Start: BuildRenderTree
End: BuildRenderTree
//Button clicked
Start: Add
(This is where the `await` occurs`)
Start: BuildRenderTree
Exception thrown
You can prevent this mid-method rerender like so....
protected override bool ShouldRender() => MayRender;
public async Task Add()
{
MayRender = false;
try
{
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" });
}
finally
{
MayRender = true;
}
}
This will prevent re-rendering whilst your method is running. Note that if you define Users as IdentityUser[] Users you will not see this problem because the array is not set until after the await has completed and is not lazy evaluated, so you don't get this reentrancy problem.
I believe you want to use IQueryable<T> because you need to pass it to 3rd party components. The problem is, different components can be rendered on different threads, so if you pass IQueryable<T> to other components then
They might render on different threads and cause the same problem.
They most likely will have an await in the code that consumes the IQueryable<T> and you'll have the same problem again.
Ideally, what you need is for the 3rd party component to have an event that asks you for data, giving you some kind of query definition (page number etc). I know Telerik Grid does this, as do others.
That way you can do the following
Acquire a lock
Run the query with the filter applied
Release the lock
Pass the results to the component
You cannot use lock() in async code, so you'd need to use something like SpinLock to lock a resource.
private SpinLock Lock = new SpinLock();
private async Task<WhatTelerikNeeds> ReadData(SomeFilterFromTelerik filter)
{
bool gotLock = false;
while (!gotLock) Lock.Enter(ref gotLock);
try
{
IUserIdentity result = await ApplyFilter(MyDbContext.Users, filter).ToArrayAsync().ConfigureAwait(false);
return new WhatTelerikNeeds(result);
}
finally
{
Lock.Exit();
}
}
Perhaps not the best approach but rewriting async method as non-async fixes the problem:
public void Add()
{
Task.Run(async () =>
await UserManager.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" }))
.Wait();
}
It ensures that UI is updated only after the new user is created.
The whole code for Index.razor
#page "/"
#inherits OwningComponentBase<UserManager<IdentityUser>>
<h1>Hello, world!</h1>
number of users: #Users.Count()
<button #onclick="#Add">click me. I work if you use Sqlite</button>
<ul>
#foreach(var user in Users.ToList())
{
<li>#user.UserName</li>
}
</ul>
#code {
IQueryable<IdentityUser> Users;
protected override void OnInitialized()
{
Users = Service.Users;
}
public void Add()
{
Task.Run(async () => await Service.CreateAsync(new IdentityUser { UserName = $"test_{Guid.NewGuid().ToString()}" })).Wait();
}
}
I found your question looking for answers about the same error message you had.
My concurrency issue appears to have been due to a change that triggered a re-rendering of the visual tree to occur at the same time as (or due to the fact that) I was trying to call DbContext.SaveChangesAsync().
I solved this by overriding my component's ShouldRender() method like this:
protected override bool ShouldRender()
{
if (_updatingDb)
{
return false;
}
else
{
return base.ShouldRender();
}
}
I then wrapped my SaveChangesAsync() call in code that set a private bool field _updatingDb appropriately:
try
{
_updatingDb = true;
await DbContext.SaveChangesAsync();
}
finally
{
_updatingDb = false;
StateHasChanged();
}
The call to StateHasChanged() may or may not be necessary, but I've included it just in case.
This fixed my issue, which was related to selectively rendering a bound input tag or just text depending on if the data field was being edited. Other readers may find that their concurrency issue is also related to something triggering a re-render. If so, this technique may be helpful.
Well, I have a quite similar scenario with this, and I 'solve' mine is to move everything from OnInitializedAsync() to
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
//Your code in OnInitializedAsync()
StateHasChanged();
}
{
It seems solved, but I had no idea to find out the proves. I guess just skip from the initialization to let the component success build, then we can go further.
/******************************Update********************************/
I'm still facing the problem, seems I'm giving a wrong solution to go. When I checked with this Blazor A second operation started on this context before a previous operation completed I got my problem clear. Cause I'm actually dealing with a lot of components initialization with dbContext operations. According to #dani_herrera mention that if you have more than 1 component execute Init at a time, probably the problem appears.
As I took his advise to change my dbContext Service to Transient, and I get away from the problem.
#Leonardo Lurci Had covered conceptually. If you guys are not yet wanting to move to .NET 5.0 preview, i would recommend looking at Nuget package 'EFCore.DbContextFactory', documentation is pretty neat. Essential it emulates AddDbContextFactory. Ofcourse, it creates a context per component.
So far, this is working fine for me so far without any problems...
I ensure single-threaded access by only interacting with my DbContext via a new DbContext.InvokeAsync method, which uses a SemaphoreSlim to ensure only a single operation is performed at a time.
I chose SemaphoreSlim because you can await it.
Instead of this
return Db.Users.FirstOrDefaultAsync(x => x.EmailAddress == emailAddress);
do this
return Db.InvokeAsync(() => ...the query above...);
// Add the following methods to your DbContext
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1);
public TResult Invoke<TResult>(Func<TResult> action)
{
Semaphore.Wait();
try
{
return action();
}
finally
{
Semaphore.Release();
}
}
public async Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> action)
{
await Semaphore.WaitAsync();
try
{
return await action();
}
finally
{
Semaphore.Release();
}
}
public Task InvokeAsync(Func<Task> action) =>
InvokeAsync<object>(async () =>
{
await action();
return null;
});
public void InvokeAsync(Action action) =>
InvokeAsync(() =>
{
action();
return Task.CompletedTask;
});
#Leonardo Lurci has a great answer with multiple solutions to the problem. I will give my opinion about every solution and which I think it is the best one.
Making DBContext transient - it is a solution but it is not optimized for this cases..
Carl Franklin suggestion - the singleton service will not be able to control the lifetime of the context and will depend on the service requester to dispose the context after use.
Microsoft documentation they talk about injecting DBContext Factory into a component with the IDisposable interface to Dispose the context when the component is destroied. This is not a very good solution, because a lot of problems happen with it, like: performing a context operation and leaving the component before it finishes that operation, will dispose the context and throw exception..
Finally. The best solution so far is to inject the DBContext Factory in the component yes, but whenever you need it, you create a new instance with using statement like bellow:
public async Task GetSomething()
{
using var context = DBFactory.CreateDBContext();
return await context.Something.ToListAsync();
}
Since DbFactory is optimazed when creating new context instances, there is no significante overhead, making it a better choice and better performing than Transient context, it also disposes the context at the end of the method because of "using" statement.
Hope it was useful.

Tracing MediatR using Application Insights in ASP.NET Core

What is the currently recommended way to trace MediatR using Application Insights in an ASP.NET Core Application?
Our goal is to drill down the individual pipelines in an application from Request, Mediator Call to Response.
Way 1: Wrap Mediator in an action
[HttpGet]
[Route("", Name = MachinesRoutes.Names.GetMachines)]
public async Task<IActionResult> GetMachines()
{
MachinesProjection p;
using (IOperationHolder<RequestTelemetry> operation = _telemetryClient.StartOperation<RequestTelemetry>(nameof(GetMachinesCommand)))
{
p = await _mediator.Send(new GetMachinesCommand());
}
// ....
}
Way 2: own dispatcher
public class MediatorDispatcher : IEventDispatcher
{
private readonly IMediator _mediator;
private readonly TelemetryClient _telemetryClient;
public MediatorDispatcher(IMediator mediator, TelemetryClient telemetryClient )
{
_mediator = mediator;
_telemetryClient = telemetryClient;
}
public async Task<TResponse> Send<TResponse>(IRequest<TResponse> command, CancellationToken cancellationToken = default)
{
using IOperationHolder<RequestTelemetry> operation = _telemetryClient.StartOperation<RequestTelemetry>(command.GetType().FullName);
return await _mediator.Send(command, cancellationToken).ConfigureAwait(false);
}
Way 3 ...?
In the end, we want to have a way to recognize requests that take an unusually long time.
It should also be possible to recognize what caused the request duration, i.e. a drill down of the operation.
Thanks.
I Agree with #Peter Bons . Behavior would be the most optimum way in your scenario.
behaviors, which allow you to build your own pipeline directly inside of MediatR without resolving to using decorators around your handlers. It's a more natural way to enhance your handlers with behavior and better supported in containers.
A pipeline behavior is an implementation of IPipelineBehavior. It represents a similar pattern to filters in ASP.NET MVC/Web API or pipeline behaviors in NServiceBus. Your pipeline behavior needs to implement one method:
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next);
Two built-in behaviors exist:
RequestPreProcessorBehavior will execute IRequestPreProcessor implementations before any handlers are called
RequestPostProcessorBehavior will execute IRequestPostProcessor implementations after all handlers are called
You can find these in the MediatR.Pipeline namespace, and will need to be explicitly registered with your container as open generics.
Additionally, any IRequestPreProcessor and IRequestPostProcessor will need to be registered with the container.
You can find the sample pipeline here.
Hope it helps.

Writing an event based SignalR Notification Service using DBContext ChangeTracker - separation of concerns

I have a controller that modifies appointments in a calendar. I want to use my SignalR hub to notify users à la "User X changed {appointmentTitle}: List: {Property} {OriginalValue} {NewValue}"
I'm a beginner in C# (Syntax-wise it's ok, but OOP concepts are new); I'm trying to use events to achieve the above.
Below are the handlers and arguments, an extract from the controller and a summary of my questions.
Code is abbreviated!
EventArgs
public class AppointmentChangeEventArgs : EventArgs
{
public EntityState AppointmentState = EntityState.Unchanged;
public EntityEntry Entity = null;
public ScheduleData Appointment = null;
}
EventHandler
// maybe this could be just one, and let the consumer decide based on EntityState?
public EventHandler<AppointmentChangeEventArgs> AppointmentChanged;
public EventHandler<AppointmentChangeEventArgs> AppointmentAdded;
public EventHandler<AppointmentChangeEventArgs> AppointmentRemoved;
protected virtual void OnAppointment(AppointmentChangeEventArgs appointmentChangeEventArgs)
{
switch (appointmentChangeEventArgs.AppointmentState)
{
case EntityState.Added:
AppointmentAdded?.Invoke(this, appointmentChangeEventArgs);
break;
case EntityState.Deleted:
AppointmentRemoved?.Invoke(this, appointmentChangeEventArgs);
break;
case EntityState.Modified:
AppointmentChanged?.Invoke(this, appointmentChangeEventArgs);
break;
default:
break;
}
}
Controller
public async Task<IActionResult> Batch([FromBody] ScheduleEditParameters param)
switch (param.Action) {
case "insert":
await _dbContext.Appointments.AddAsync(appointment);
break;
case "update":
// .. get Appointment from DB
appointment.Subject = value.Subject;
appointment.StartTime = value.StartTime;
// ...
case "remove":
// .. get Appointment from DB
_dbContext.Appointments.Remove(appointment);
}
var modifiedEntries = _dbContext.ChangeTracker
.Entries()
.Where(x => x.State != EntityState.Unchanged && x.State != EntityState.Detached)
.Select(x => new AppointmentChangeEventArgs() { Entity = (EntityEntry) x.Entity, AppointmentState = x.State, Appointment = appointment })
.ToList();
if (modifiedEntries.Any())
{
var notificationService = new NotificationService(signalRHub, notificationLogger);
AppointmentAdded += notificationService.OnAppointmentChanged;
AppointmentChanged += notificationService.OnAppointmentChanged;
AppointmentRemoved += notificationService.OnAppointmentChanged;
}
await _dbContext.SaveChangesAsync();
Questions
Is it ok to use EntityEntry and EntityState in event arguments?
for each modified Entry, I can obtain _dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList(); - but does this belong in the NotificationService class? In order to do that, I'd also need to pass the DbContext over to NotificationService.
Might there be a simpler way to achieve this? Adding and Removing handlers are easy ("User X has added|removed ... appointment {Title}"), but in order to figure out the exact changes I'll have to look at the modified properties.
I'd be grateful if you could provide an insight into how you would structure & handle this task. Thank you.
To start off, I would generally recommend you not to use events here. Events are something that may sound very useful but due to the way they work (synchronously), they aren’t really the best way to achieve this in a web context, especially in a primarily asynchronous framework like ASP.NET Core.
Instead, I would recommend you to simply declare your own type, e.g. IAppointmentChangeHandler like this:
public interface IAppointmentChangeHandler
{
Task AddAppointment(ScheduleData appointment);
Task UpdateAppointment(ScheduleData appointment);
Task RemoveAppointment(ScheduleData appointment);
}
Your NotificationService can just implement that interface to be able to handle those events (obviously just send whatever you need to send there):
public class NotificationService : IAppointmentChangeHandler
{
private readonly IHubContext _hubContext;
public NotificationService(IHubContext hubContext)
{
_hubContext = hubContext;
}
public AddAppointment(ScheduleData appointment)
{
await _hubContext.Clients.InvokeAsync("AddAppointment", appointment);
}
public UpdateAppointment(ScheduleData appointment)
{
await _hubContext.Clients.InvokeAsync("UpdateAppointment", appointment);
}
public RemoveAppointment(ScheduleData appointment)
{
await _hubContext.Clients.InvokeAsync("RemoveAppointment", appointment);
}
}
And inside of your controller, you just inject that IAppointmentChangeHandler then and call the actual method on it. That way you have both the controller and the notification service completely decoupled: The controller does not need to construct the type first and you also do not need to subscribe to some events (which you would also have to unsubscribe from at some point again btw). And you can leave the instantiation completely to the DI container.
To answer your individual questions:
Is it ok to use EntityEntry and EntityState in event arguments?
I would avoid using it in a context outside of your database. Both are an implementation detail of your database setup, since you are using Entity Framework here. Not only would this couple your event handlers strongly with Entity Framework (meaning that everyone that wanted to be an event handler would need to reference EF even if they didn’t do anything with it), you are also leaking possibly internal state that may change later (you don’t own the EntityEntry so who knows what EF does with it afterwards).
for each modified Entry, I can obtain _dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList();
If you look at your code, you are first calling Add, Update or Remove on your database set; and then you are using some logic to look at some internal EF stuff to figure out the exact same thing really. You could make this a lot less complex if you constructed the AppointmentChangeEventArgs within those three switch cases directly.
but does this belong in the NotificationService class? In order to do that, I'd also need to pass the DbContext over to NotificationService.
Does a notification service have anything to do with a database? I would say no; unless you are persisting those notifications into the database. When I think about a notification service, then I expect to be able to call something on it to actively trigger a notification, instead of having some logic within the service to figure out what notifications it could possibly trigger.
Might there be a simpler way to achieve this? Adding and Removing handlers are easy ("User X has added|removed ... appointment {Title}"), but in order to figure out the exact changes I'll have to look at the modified properties.
Think about it in the simplest way first: Where do you update the values of the database entity? Within that update case. So at that point, where you are copying over values from the passed object, you can also just check which properties you are actually changing. And with that, you can record easily which properties you need to notify about.
Decouple this completely from EF and you will be a lot more flexible in the long run.

MVC 3/EF repository pattern and proper data access

Being rather new to MVC 3 and EF, I'm trying to understand the best architectural approach to developing an application for my company. The application will be a large-scale application that potentially handles hundreds of users at the same time, so I want to make sure I understand and am following proper procedures. So far, I've determined that a simple repository pattern (such as Controller -> Repository -> EF) approach is the best and easiest to implement, but I'm not sure if that is definitely the best way to do things. The application will basically return data that is shown to a user in a devexpress grid and they can modify this data/add to it etc.
I found this article and it is rather confusing for me at this time, so I'm wondering if there is any reason to attempt to work with a disconnected EF and why you would even want to do so: http://www.codeproject.com/Articles/81543/Finally-Entity-Framework-working-in-fully-disconne?msg=3717432#xx3717432xx
So to summarize my question(s):
Is the code below acceptable?
Should it work fine for a large-scale MVC application?
Is there a better way?
Will unnecessary connections to SQL remain open from EF? (SQL Profiler makes it look like it stays open a while even after the using statement has exited)
Is the disconnected framework idea a better one and why would you even want to do that? I don't believe we'll need to track data across tiers ...
Note: The repository implements IDisposable and has the dispose method listed below. It creates a new instance of the entity context in the repository constructor.
Example Usage:
Controller (LogOn using Custom Membership Provider):
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
User newUser = new User();
using (AccountRepository repo = new AccountRepository())
{
newUser = repo.GetUser(model.UserName);
...
}
}
Membership Provider ValidateUser:
public override bool ValidateUser(string username, string password)
{
using (AccountRepository repo = new AccountRepository())
{
try
{
if (string.IsNullOrEmpty(password.Trim()) || string.IsNullOrEmpty(username.Trim()))
return false;
string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5");
bool exists = false;
exists = repo.UserExists(username, hash);
return exists;
}catch{
return false;
}
}
}
Account Repository Methods for GetUser & UserExists:
Get User:
public User GetUser(string userName)
{
try
{
return entities.Users.SingleOrDefault(user => user.UserName == userName);
}
catch (Exception Ex)
{
throw new Exception("An error occurred: " + Ex.Message);
}
}
User Exists:
public bool UserExists(string userName, string userPassword)
{
if (userName == "" || userPassword == "")
throw new ArgumentException(InvalidUsernamePassword);
try
{
bool exists = (entities.Users.SingleOrDefault(u => u.UserName == userName && u.Password == userPassword) != null);
return exists;
}
catch (Exception Ex)
{
throw new Exception("An error occurred: " + Ex.Message);
}
}
Repository Snippets (Constructor, Dispose etc):
public class AccountRepository : IDisposable
{
private DbContext entities;
public AccountRepository()
{
entities = new DbContext();
}
...
public void Dispose()
{
entities.Dispose();
}
}
What's acceptable is pretty subjective, but if you want to do proper data access I suggest you do NOT use the repository pattern, as it breaks down as your application gets more complex.
The biggest reason is minimizing database access. So for example look at your repository and notice the GetUser() method. Now take a step back from the code and think about how your application is going to be used. Now think about how often you are going to request data from the user table without any additional data. The answer is almost always going to be "rarely" unless you are creating a basic data entry application.
You say it your application will show a lot of grids. What data is in that Grid? I'm assuming (without knowing your application domain) that the grids will combine user data with other information that's relevant for that user. If that's the case, how do you do it with your repositories?
One way is to call on each repository's method individually, like so:
var user = userRepository.GetUser("KallDrexx");
var companies = companyRepository.GetCompaniesForUser(user.Id);
This now means you have 2 database calls for what really should be just one. As your screens get more and more complex, this will cause the number of database hits to increase and increase, and if your application gets significant traffic this will cause performance issues. The only real way to do this in the repository pattern is to add special methods to your repositories to do that specific query, like:
public class UserRepository
{
public User GetUser(string userName)
{
// GetUser code
}
public User GetUserWithCompanies(string userName)
{
// query code here
}
}
So now what happens if you need users and say their contact data in one query. Now you have to add another method to your user repository. Now say you need to do another query that also returns the number of clients each company has, so you need to add yet another method (or add an optional parameter). Now say you want to add a query that returns all companies and what users they contain. Now you need a new query method but then comes the question of do you put that in the User repository or the Company repository? How do you keep track of which one it's in and make it simple to choose between GetUserWithCompany and GetCompanyWithUsers when you need it later?
Everything gets very complex from that point on, and it's those situations that have made me drop the repository pattern. What I do now for data access is I create individual query and command classes, each class represents 1 (and only 1) query or data update command to the database. Each query class returns a view model that only contains the data I need for one specific user usage scenario. There are other data access patterns that will work too (specification pattern, some good devs even say you should just do your data access in your controllers since EF is your data access layer).
The key to doing data access successfully is good planning. Do you know what your screens are going to look like? Do you know how users are going to use your system? Do you know all the data that is actually going to be on each screen? If the answer to any of these is no, then you need to take a step back and forget about the data layer, because the data layer is (or should be for a good application) determined based on how the application is actually going to be used, the UI and the screens should not be dependent on how the data layer was designed. If you don't take your UI needs and user usage scenarios into account when developing the data access, your application will not scale well and will not be performant. Sometimes that's not an issue if you don't plan on your site being big, but it never hurts to keep those things in mind.
No matter what you do, you may consider moving instantiation and disposing of your context to your controller like this:
public class MyController : Controller
{
private Entities context = new Entities();
...
public override void Dispose()
{
context.Dispose();
}
}
You can then pass that context into any method that needs it without duplicating the overhead of creating it.
I disagree that the repository pattern is necessarily bad for the same reason. You create multiple classes to break up your code to make it manageable and still reuse the same context. That could look something like this:
repository.Users.GetUser(userName);
In this case "Users" is a lazy loaded instance of your user repository class which reuses the context from your repository. So the code for that Users property in your repository would look something like this:
private UserRepository users;
public UserRepository Users
{
get
{
If (users == null)
{
users = new UserRepository(this);
}
return users;
}
}
You can then expose your context to these other lazy loaded classes via a property.
I don't think this necessarily conflicts with KallDrexx's pattern. His method simply flips this so instead of
repository.Users.GetUser(userName);
You would have something like
UserQuery query = new UserQuery(repository.Users);
This then becomes an issue of syntax. Do you want this:
repository.Area.Query(value1, value2, ...);
Or this:
AreaQuery query = new AreaQuery { Property1 = value1, ... };
The latter actually works nicer with model binding but obviously is more verbose when you actually have to code it.
Best advice KallDrexx gave is to just put your code I your actions and then figure it out. If you are doing simple CRUD, then let MVC instantiate and populate your model, then all you have to do is attach and save. If you find you can reuse code, move it to where it can be reused. If your application starts getting too complicated, try some of these recommendations until you find what works for you.