I have a static Context that is doesn't refresh after updating values from the DB.
Here is a little background on how the systems work.
CRUD is done in a separate app.
After saving to the DB triggers notify the API on the changes.
After getting the changed Entity from the Context in the API, the entity still holds the old values.
API does what ever it needs to.
I'm using EF 6.1.3 and .net 4.6.1
Here is the Code that handles the DB trigger using SqlTblDependency.
public class ActivityListner : DataBaseListiner<Activity>
{
private IActivityService _activityService;
public ActivityListner(IActivityService actService) : base("Activity")
{
actService.ThrowIfNull("Activity Service not passed in DI container");
_activityService = actService;
}
public override void OnChange(object sender, RecordChangedEventArgs<Activity> e)
{
var changedEntity = e.Entity;
//does not get latest data
var activity = _activityService.GetActivityByID(changedEntity.ActivityID);
//these 2 methods work but are not ideal
// 1._activityService = DependencyResolver.Current.GetService<IActivityService>();
// 2. I call Context.Refresh extention method in the GetActivityByID() call above
if (e.ChangeType == TableDependency.Enums.ChangeType.Insert)
{
//...
}
else if (e.ChangeType == TableDependency.Enums.ChangeType.Update)
{
//...
}
else if (e.ChangeType == TableDependency.Enums.ChangeType.Delete)
{
//...
}
}
}
This is the extension method in the comment above
public static class ContextHelpers
{
public static void RefreshContext(this MyContext ctx)
{
var context = ((IObjectContextAdapter)ctx).ObjectContext;
var refreshableObjects = ctx.ChangeTracker.Entries().Select(c => c.Entity).ToList();
context.Refresh(RefreshMode.StoreWins, refreshableObjects);
}
}
This is the GetActivityByID() method
public Activity GetActivityById(Guid actId)
{
Context.RefreshContext();
var act = Context.Activities.FirstOrDefault(x => x.ActivityID == actId);
return act;
}
Here are the problem I would like to solve.
Is there a better way of disposing and re-creating a new context?
In step 3 above where would be the best place to new up a context? Does Entity Framework have an Event that triggers when I read from the DB where I can Dispose and re-create a new context? If that's not possible can I refresh the Context from a central location? I thought of refreshing inside of the GenericRepository or creating an extension method but I am hoping EF might have an event.
Related
Documentation Says : The model for that context is cached and is for all further instances of the context in the app domain. This caching can be disabled by setting the ModelCaching property on the given ModelBuidler
But i can't find way to do it. I have to disable caching because I am adding Model at runtime and loading all the models from assembly and creating database.
I found this link which says one way of achieving this is using DBModelBuilding - adding model mannually to context but it is for Entity Framework, Not helped for EF Core.
Entity Framework 6. Disable ModelCaching
I hope some one has solution for this.
Thank you
Once a model is successfully created, EF Core will cache it forever, unless you implement a cache manager that is able to tell whether a model is equivalent to another, and therefore it can be cached or not.
The entry point is to implement the cache manager:
internal sealed class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create([NotNull] DbContext context)
{
return GetKey(context);
}
}
The GetKey method which you have to write must return an object that will be used as key. This method should inspect the provided context and return the same key when the models are the same, and something different when they are not. More on IModelCacheKeyFactory Interface.
I understand, this might not be clear (and it wasn't for me either), so I write a full example of what I have in production.
A Working Example
My target is to use the same context for different schemas. What we need to do is
create a new context option
implement the logic in the context
create the cache key factory
make the extension method to specify the schema
call the extension method on the db context
1. Create a new context option
Here there is a boilerplate containing _schemaName only. The boilerplate is necessary as the extension option is immutable by design and we need to preserve the contract.
internal class MySchemaOptionsExtension : IDbContextOptionsExtension
{
private DbContextOptionsExtensionInfo? _info;
private string _schemaName = string.Empty;
public MySchemaOptionsExtension()
{
}
protected MySchemaOptionsExtension(MySchemaOptionsExtension copyFrom)
{
_schemaName = copyFrom._schemaName;
}
public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this);
public virtual string SchemaName => _schemaName;
public virtual void ApplyServices(IServiceCollection services)
{
// not used
}
public virtual void Validate(IDbContextOptions options)
{
// always ok
}
public virtual MySchemaOptionsExtension WithSchemaName(string schemaName)
{
var clone = Clone();
clone._schemaName = schemaName;
return clone;
}
protected virtual MySchemaOptionsExtension Clone() => new(this);
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
private const long ExtensionHashCode = 741; // this value has chosen has nobody else is using it
private string? _logFragment;
public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension)
{
}
private new MySchemaOptionsExtension Extension => (MySchemaOptionsExtension)base.Extension;
public override bool IsDatabaseProvider => false;
public override string LogFragment => _logFragment ??= $"using schema {Extension.SchemaName}";
public override long GetServiceProviderHashCode() => ExtensionHashCode;
public override void PopulateDebugInfo([NotNull] IDictionary<string, string> debugInfo)
{
debugInfo["MySchema:" + nameof(DbContextOptionsBuilderExtensions.UseMySchema)] = (ExtensionHashCode).ToString(CultureInfo.InvariantCulture);
}
}
}
2. The logic in the context
Here we force the schema to all the real entities. The schema is obtained by the option attached to the context
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var options = this.GetService<IDbContextOptions>().FindExtension<MySchemaOptionsExtension>();
if (options == null)
{
// nothing to apply, this is a supported scenario.
return;
}
var schema = options.SchemaName;
foreach (var item in modelBuilder.Model.GetEntityTypes())
{
if (item.ClrType != null)
item.SetSchema(schema);
}
}
3. Create the cache key factory
Here we need to the create the cache factory which will tel EF Core that it can cache all the models on the same context, i.e. all the contexts with the same schema will use the same model:
internal sealed class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create([NotNull] DbContext context)
{
const string defaultSchema = "dbo";
var extension = context.GetService<IDbContextOptions>().FindExtension<MySchemaOptionsExtension>();
string schema;
if (extension == null)
schema = defaultSchema;
else
schema = extension.SchemaName;
if (string.IsNullOrWhiteSpace(schema))
schema = defaultSchema;
// ** this is the magic **
return (context.GetType(), schema.ToUpperInvariant());
}
}
The magic is here is in this line
return (context.GetType(), schema.ToUpperInvariant());
that we return a tuple with the type of our context and the schema. The hash of a tuple combines the hash of each entry, therefore the type and schema name are the logical discriminator here. When they match, the model is reused; when they do not, a new model is created and then cached.
4. Make the extension method
The extension method simply hides the addition of the option and the replacement of the cache service.
public static DbContextOptionsBuilder UseMySchema(this DbContextOptionsBuilder optionsBuilder, string schemaName)
{
if (optionsBuilder == null)
throw new ArgumentNullException(nameof(optionsBuilder));
if (string.IsNullOrEmpty(schemaName))
throw new ArgumentNullException(nameof(schemaName));
var extension = optionsBuilder.Options.FindExtension<MySchemaOptionsExtension>() ?? new MySchemaOptionsExtension();
extension = extension.WithSchemaName(schemaName);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
return optionsBuilder;
}
In particular, the following line applies our cache manager:
optionsBuilder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
5. Call the extension method
You can manually create the context as follows:
var options = new DbContextOptionsBuilder<DataContext>();
options.UseMySchema("schema1")
options.UseSqlServer("connection string omitted");
var context = new DataContext(options.Options)
Alternatively, you can use IDbContextFactory with dependency injection. More on IDbContextFactory Interface.
You'll need to change the cache key to properly represent the model that you are building/make it distinct.
Implement IDbModelCacheKeyProvider Interface on derived DbContext. Check this out
https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.infrastructure.idbmodelcachekeyprovider?redirectedfrom=MSDN&view=entity-framework-6.2.0
Build the model outside the DbContext and then provide it in the options.
I have the following code:
public void someMethod(){
...
var accounts = myRepo.GetAccounts(accountId)?.ToList();
...
foreach (var account in accounts)
{
account.Status="INACTIVE";
var updatedAccount = myRepo.AddOrUpdateAccounts(account);
}
}
public Account AddOrUpdateAccounts(Account account){
//I want to compare account in the Db and what is passed in. So get the account from DB
var accountFromDb = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.
//here accountFromDb.Status is returned as INACTIVE, but in the database the column value is ACTIVE
...
...
}
public IEnumerable<Account> GetAccounts(int id){
return id <= 0 ? null : m_Context.Accounts.Where(x => x.Id == id);
}
Here, inside someMethod() I am calling GetAccounts() that returns data from the Accounts table.
Then I am changing the Status of the account, and calling AddOrUpdateAccounts().
Inside AddOrUpdateAccounts(), I want to compare the account that was passed in and whats in the database. When I call GetAccounts(), it returned a record with STATUS="INACTIVE". I haven't done SaveChanges(). Why didn't GetAccounts() returned the data from the database? In the Db the status is still "ACTIVE"
The repository method should return IQueryable<Account> rather than IEnumerable<Account> as this will allow the the consumer to continue to refine any criteria or govern how the account(s) should be consumed prior to any query executing against the database:
I would consider:
public IQueryable<Account> GetAccountsById(int id){
return m_Context.Accounts.Where(x => x.Id == id);
}
Don't return #null, just the query. The consumer can decide what to do if the data is not available.
From there the calling code looks like:
var accounts = myRepo.GetAccounts(accountId).ToList();
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
Your addOrUpdate wouldn't work:
public Account AddOrUpdateAccounts(Account account){
...
var account = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.
You pass in the Account as "account" then try declaring a local variable called "account". If you remove the var keyword you would load the DbContext's record over top your modified account and your changes would be lost. Loading the account into another variable isn't necessary as long as the account is still associated with the DbContext.
Edit: After changing the var account = ... statement to look like:
public Account AddOrUpdateAccounts(Account account){
...
var accountToUpdate = myRepo.GetAccounts(account.Id); //this doesn't return whats
accountToUpdate will show the modified status rather than what is in the database because that DbContext is still tracking the reference to the entity that you modified. (account) For instance if I do this:
var account1st = context.Accounts.Single(x => x.AccountId == 1);
var account2nd = context.Accounts.Single(x => x.AccountId == 1);
Console.WriteLine(account1st.Status); // I get "ACTIVE"
Console.WriteLine(account2nd.Status); // I get "ACTIVE"
account1st.Status = "INACTIVE";
Console.WriteLine(account2nd.Status); // I get "INACTIVE"
Both references point to the same instance. It doesn't matter when I attempt to read the Account the 2nd time, as long as it's coming from the same DbContext and the context is tracking instances. If you read the row via a different DbContext, or use AsNoTracking() with all of your reads then the account can be read fresh from the database. You can reload an entity, but if those variables are pointing at the same reference it will overwrite your changes and set the entity back to Unmodified. This can be a little confusing when watching an SQL profiler output because in some cases you will see EF run a SELECT query for an entity, but the entity returned has different, modified values than what is in the database. Even when loading from the tracking cache, EF can still execute queries against the DB in some cases, but it returns the tracked entity reference.
/Edit
When it comes to saving the changes, it really just boils down to calling the SaveChanges on the DbContext that the account is associated. The "tricky" part is scoping the DbContext so that this can be done. The recommended pattern for this is the Unit of Work. There are a few different ones out there, and the one I recommend for EF is Mehdime's DbContextScope, however you can implement simpler ones that may be easier to understand and follow. Essentially a unit of work encapsulates the DbContext so that you can define a scope that repositories can access the same DbContext, then commit those changes at the end of the work.
At the most basic level:
public interface IUnitOfWork<TDbContext> : IDisposable where TDbContext : DbContext
{
TDbContext Context { get; }
int SaveChanges();
}
public class UnitOfWork : IUnitOfWork<YourDbContext>
{
private YourDbContext _context = null;
TDbContext IUnitOfWork<YourDbContext>.Context
{
get { return _context ?? (_context = new YourDbContext("YourConnectionString"); }
}
int IUnitOfWork<YourDbContext>.SaveChanges()
{
if(_context == null)
return 0;
return _context.SaveChanges();
}
public void Dispose()
{
try
{
if (_context != null)
_context.Dispose();
}
catch (ObjectDisposedException)
{ }
}
}
With this class available, and using dependency injection via an IoC container (Autofac, Unity, or MVC Core) you register the unit of work as Instance per Request so that when the controller and repository classes request one in their constructor, they receive the same instance.
Controller / Service:
private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
private readonly IYourRepository _repository = null;
public YourService(IUnitOfWork<YourDbContext> unitOfWork, IYourRepository repository)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
_repository = repository ?? throw new ArgumentNullException("repository");
}
Repository
private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
public YourService(IUnitOfWork<YourDbContext> unitOfWork)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
}
private YourDbContext Context { get { return _unitOfWork.Context; } }
Big Disclaimer: This is a very crude initial implementation to explain roughly how a Unit of Work can operate, it is no way production suitable code. It has limitations, specifically around disposing the DbContext but should serve as a demonstration. Definitely look to implement a library that's already out there and addresses these concerns. These implementations properly manage the DbContext disposal and will manage a scope beyond the context, like a TransactionScope so that their SaveChanges is required even if the unitOfWork.Context.SaveChanges() is called.
With a unit of work available to the Controller/Service and Repository, the code to use the repository and update your changes becomes:
var accounts = myRepo.GetAccountsById(accountId).ToList();
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
UnitOfWork.SaveChanges();
With a proper unit of work it will look more like:
using (var unitOfWork = UnitOfWorkFactory.Create())
{
var accounts = myRepo.GetAccountsById(accountId).ToList(); // Where myRepo can resolve the unit of work via locator.
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
unitOfWork.SaveChanges();
}
This way if you were to call different repos to fetch data, perform a number of different updates, the changes would be committed all in one call at the end and rolled back if there was a problem with any of the data.
Consider that I have configured EF with a .NET Core web app:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(...));
I can also download a package to support for example SQLite:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(...));
How can we allow a user to "select" the provider on app install? I mean - for example, in WordPress you can choose from a dropdown.
Is this possible in .NET Core? The only way I see is to restart the app only...
Here is an example on how you can implement a DbContextFactory or a DbContextProxy<T> which will create the correct provider and return it.
public interface IDbContextFactory
{
ApplicationContext Create();
}
public class DbContextFactory() : IDbContextFactory, IDisposable
{
private ApplicationContext context;
private bool disposing;
public DbContextFactory()
{
}
public ApplicationContext Create()
{
if(this.context==null)
{
// Get this value from some configuration
string providerType = ...;
// and the connection string for the database
string connectionString = ...;
var dbContextBuilder = new DbContextOptionsBuilder();
if(providerType == "MSSQL")
{
dbContextBuilder.UseSqlServer(connectionString);
}
else if(providerType == "Sqlite")
{
dbContextBuilder.UseSqlite(connectionString);
}
else
{
throw new InvalidOperationException("Invalid providerType");
}
this.context = new ApplicationContext(dbContextBuilder);
}
return this.context;
}
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing){
if (disposing){
disposing?.Dispose();
}
}
}
Also make sure you implement the disposable pattern as show above, so the context gets disposed as soon as the factory gets disposed, to prevent the DbContext remaining in memory longer than necessary and free unmanaged resources as soon as possible.
Finally register the factory as scoped, as you would the context itself:
services.AddScopedd<IDbContextFactory, DbContextFactory>();
A more advanced and generic/extendable approach is by creating a IDbContextProxy<T> class which uses a bit of reflection to get the correct constructor and the DbContextOptionsBuilder to it.
Also possible to create a IDbContextBuilder which abstracts the provider creation.
public class SqlServerDbContextBuilder IDbContextBuilder
{
public bool CanHandle(string providerType) => providerType == "SqlServer";
public T CreateDbContext<T>(connectionString)
{
T context = ... // Create the context here
return context;
}
}
Then you can pick the correct provider w/o a hard coded if/else or switch block just by doing
// Inject "IEnumerable<IDbContextBuilder> builders" via constructor
var providerType = "SqlServer";
var builder = builders.Where(builder => builder.CanHandle(providerType)).First();
var context = builder.CreateDbContext<ApplicationContext>(connectionString);
and adding new types of provider is as easy as adding the dependencies and an XxxDbContextBuilder class.
See here, here or here for more information about this and similar approaches.
I think you can use repositories which are using a db context you specified and you can pass a parameter to context constructor to choose the endpoint. I am not sure on this but it might work for your situation.
I followed this article for repository pattern, I recommend to read it :)
http://cpratt.co/generic-entity-base-class/
I want to access some data within my overriden SaveChanges() in my dbcontext without passing any parameters. Any suggestions? I'm working with MVC4 and Entity Framework Database-First.
public partial class Entities : DbContext
{
public override int SaveChanges()
{
// i want to get user info from MVC model but no need to pass any parameters when call SaveChanges()
var UserInfo = userInfo;
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}
}
Solution 1: Dependency Injection
This solution is pretty extensible, but you would have to modify the code in your repositories and controllers to use the injected dependencies instead of creating new instances with new.
Install Ninject. In Visual Studio, find the Package Manager Console and run Install-Package Ninject.MVC4 -dependencyVersion Highest in there.
Add constructor injection. Modify your controller, so that it gets an instance of your repository in its constructor. Modify your repository, so that it gets an instance of your entity context in its constructor. Cache your dependencies in private fields. Example code:
// In your controller:
public MyController(MyRepository repo)
{
this.repo = repo;
}
// In your repository:
public MyRepository(Entities context)
{
this.context = context;
}
// In your entities:
public Entities(UserInfo userInfo)
{
this.userInfo = userInfo;
}
Add a UserInfo provider. We need to tell Ninject where to get the UserInfo from. We can use the provider interface here:
public class UserInfoProvider : Provider<UserInfo>
{
protected override UserInfo CreateInstance(IContext context)
{
UserInfo UserInfo = new UserInfo();
// Do some complex initialization here.
return userInfo;
}
}
Add bindings. We need to tell Ninject to use the provider. We also want the lifetime of a UserInfo instance and of our entity context to be bound to the request cycle of MVC. Update your App_Start\NinjectWebCommon.cs:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<Entities>()
.ToSelf()
.InRequestScope();
kernel.Bind<UserInfo>()
.ToProvider<UserInfoProvider>()
.InRequestScope();
}
Run your app. Ninject should use your constructors and provide the requested dependencies.
For further information, visit the Ninject Wiki.
Solution 2: Thread local context
This requires no modification to your repositories, but it makes the code less testable and resembles an anti pattern somehow. This won't work if your controller calls multithreaded code.
Add context class.
public class UserInfoContext : IDisposable
{
private static readonly ThreadLocal<UserInfo> UserInfos = new ThreadLocal<UserInfo>();
public static UserInfo Current
{
get
{
if (UserInfos == null)
{
throw new InvalidOperationException("UserInfoContext has not been set.");
}
return UserInfos.Value;
}
}
public static UserInfoContext Create(UserInfo userInfo)
{
if (userInfo == null)
{
throw new ArgumentNullException("userInfo");
}
if (UserInfos.Value != null)
{
throw new InvalidOperationException("UserInfoContext should not be nested.");
}
UserInfos.Value = userInfo;
return new UserInfoContext();
}
private UserInfoContext() { }
public void Dispose()
{
UserInfos.Value = null;
}
}
Wrap your controller code. Example:
public ActionResult Index()
{
using (UserInfoContext.Create(myUserInfo))
{
// do stuff that calls your repositories
return View();
}
}
Update your Entities class.
public partial class Entities : DbContext
{
public override int SaveChanges()
{
var UserInfo = UserInfoContext.Current;
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}
}
I currently been assigned to a asp mvc project using entity framework. This will be a Line of Business application. I want to develop this app using repository and unit of work pattern. I'm new to this pattern (and new to .net too) and i am having a problem in understanding the pattern and how to implement it.
I have read numerous articles and i think this is how my application should be
Entity Framework -> Repository -> Unit of Work -> Client (Asp MVC)
I attach some code from this article
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
using System;
using ContosoUniversity.Models;
namespace ContosoUniversity.DAL
{
public class UnitOfWork : IDisposable
{
private SchoolContext context = new SchoolContext();
private GenericRepository<Department> departmentRepository;
private GenericRepository<Course> courseRepository;
public GenericRepository<Department> DepartmentRepository
{
get
{
if (this.departmentRepository == null)
{
this.departmentRepository = new GenericRepository<Department>(context);
}
return departmentRepository;
}
}
public GenericRepository<Course> CourseRepository
{
get
{
if (this.courseRepository == null)
{
this.courseRepository = new GenericRepository<Course>(context);
}
return courseRepository;
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Unit of Work will have repositories and will create DBContext upon creation
So controller will create Unit of Work upon creation.
To Show data i will use this code
var department = UoW.departmentRepository.Find(1);
return View(department);
and when client click save button, i will run this code
UoW.departmentRepository.Update(department);
UoW.Save();
My question:
What if it takes hours from data retrieval until the client click save button. From what i know, we have to keep context as short as possible.
Where should i put business logic? Do i put it in repository? So i would call UoW.departmentRepository.Validate(department) before save. But then, what if i need to validate entity which relate to other entity. Do i call UoW.departmentRepository.Validate(course, department)?
Is there a complete sample project for this kind of application?
EDIT
As Ant P adviced, i need to add another layer to put my business logic.
This is what i have come so far
Unit Of Work:
public class UnitOfWork : IDisposable
{
private DBContext _context = new DBContext();
public DBContext Context
{
get
{
return this._context;
}
}
public void Save()
{
_context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Business Logic:
public class SalesBusinessLogic : IDisposable
{
private ICustomerRepository _customerRepo;
private ISalesRepository _salesRepo;
private UnitOfWork _uow;
public SalesBusinessLogic(UnitOfWork uow)
{
this._uow = uow;
}
public ICustomerRepository CustomerRepo
{
get
{
if (this._customerRepo == null)
{
this._customerRepo = new CustomerRepository(this._uow);
}
return this._customerRepo;
}
}
public ISalesRepository SalesRepo
{
get
{
if (this._salesRepo == null)
{
this._salesRepo = new SalesRepository(this._uow);
}
return this._salesRepo;
}
}
public bool Validate(Sales sales)
{
//this is where validation performed
return true;
}
}
Controller:
public SalesController : Controller
{
private UnitOfWork _uow = new UnitOfWork();
private SalesBusinessLogic _bl = new SalesBusinessLogic(this._uow);
public ActionResult Index()
{
var sales = _bl.SalesRepo.Find(1);
sales.CustomerID = 1;
if _bl.Validate(sales)
{
_bl.SalesRepo.Update(sales);
_uow.Save();
}
return View(sales);
}
}
Here UnitOfWork act only as provider of dbcontext, which will be consumed by business logic and repository.
Repository will be in BusinessLogic class.
Server side validation will be handled by BusinessLogic and client side validation will be handled by viewmodel in Web Layer.
My only concern is that dbcontext in UnitofWork is publicly accessible.
Am i in the right direction here?
What if it takes hours from data retrieval until the client click save button. From what i know, we have to keep context as short as possible.
This isn't an issue - the controller is instantiated per request. It doesn't persist while the user views the page. It sounds like you're misunderstanding at what point the controller is instantiated. When you instantiate the UnitOfWork within the controller's constructor, the process flow goes like this:
The user issues a POST request (by clicking 'Save').
The request reaches the server and the controller is instantiated (thereby instantiating the unit of work).
The action method is called.
The unit of work is disposed.
Where should i put business logic? Do i put it in repository? So i would call UoW.departmentRepository.Validate(department) before save. But then, what if i need to validate entity which relate to other entity. Do i call UoW.departmentRepository.Validate(course, department)?
Typically, your business logic would be abstracted into a separate layer that sits between your web application and your repositories. Tutorials that show you repositories injected directly into controllers assume that you have "thin" business logic.
However, validation definitely isn't the job of a repository. You should create a separate view model for each view and validate those in your controller. The repository should be used pretty much solely for CRUD operations.