I am trying to call the SendQueuedEmailsToRecipients method from a Maintenance class. My understanding is that because the Maintenance class depends on the SendQueuedEmailsToRecipients method (which is in a different namespace) then I need to DI the method into the Maintenance class. Is this correct? - I am having trouble doing this.
I am unsure if I am going about this the right way. (Still learning Csharp and ASP.NET core). I do not really know where to call up IBulkEmailQueue. Sorry- I am a bit lost here but am trying to understand.
My code is:
root/BLL/MaintenanceBLL/Maintenance.cs
public class Maintenance : IMaintenance
{
private readonly ApplicationDbContext _context;
public Maintenance(ApplicationDbContext Context)
{
_context = Context;
}
//-------------------**MY DI ATTEMPT**
private readonly BulkEmailQueue _bulkEmailQueue;
public Maintenance(BulkEmailQueue BulkEmailQueue)
{
_bulkEmailQueue = BulkEmailQueue;
}
//-------------
public void DoMaintenance()
{
//Parse Maintenance table and action those items
//where Active=True and ActionDate has pass
//==================================
//Retrieve list of rows in Maintenance table
var maintenances = _context.Maintenance;
foreach (Models.Maintenance m in maintenances)
{
switch (m.Subject)
{
case "Send Queued Emails":
if (m.ActionDateTime <= DateTime.Now)
{
//BulkEmailQueue bulkEmailQueue = new BulkEmailQueue();//Create new instance
//BulkEmailQueue.SendQueuedEmailsToRecipients();
_bulkEmailQueue.SendQueuedEmailsToRecipients(); **ERROR DISPLAYS ON THIS LINE**. System.NullReferenceException: 'Object reference not set to an instance of an object.'
m.ActionDateTime = DateTime.Now.AddMinutes(15);
m.LastActionDateTime = DateTime.Now;
}
break;
default:
// code block
break;
}
}
My root/startup contains:
services.AddTransient<IMaintenance, Maintenance>();
services.AddTransient<IBulkEmailQueue, BulkEmailQueue>();
My root/Services/IBulkEmailQueue.cs contains
public interface IBulkEmailQueue
{
public void SendQueuedEmailsToRecipients();
public void DeleteRecord();
public void AddToQueue();
}
It is all fine except that Only one constructor shall be required to inject the dependency IMaintenance and ApplicationDbContext. Also, make sure that ApplicationDbContext is also setup in the startup.cs as done for IMaintenance and IBulkEmailQueue
The MODIFIED code goes as below.
public class Maintenance : IMaintenance
{
private readonly ApplicationDbContext _context;
private readonly IBulkEmailQueue _bulkEmailQueue; // this should be interface
public Maintenance(ApplicationDbContext Context,IBulkEmailQueue BulkEmailQueue)
{
_context = Context;
_bulkEmailQueue = BulkEmailQueue;
}
//-------------------**MY DI ATTEMPT**
public void DoMaintenance()
{
//Parse Maintenance table and action those items
//where Active=True and ActionDate has pass
//==================================
//Retrieve list of rows in Maintenance table
var maintenances = _context.Maintenance;
foreach (Models.Maintenance m in maintenances)
{
switch (m.Subject)
{
case "Send Queued Emails":
if (m.ActionDateTime <= DateTime.Now)
{
_bulkEmailQueue.SendQueuedEmailsToRecipients(); **NO ERROR SHALL DISPLAY ON THIS LINE**.
m.ActionDateTime = DateTime.Now.AddMinutes(15);
m.LastActionDateTime = DateTime.Now;
}
break;
default:
// code block
break;
}
}
Related
Is there a way the tell the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method to try to use other constructors if the first one can't be constructed?
I have a class with multiple constructors:
public ViewModelB(SomeDependency someDependency): this one only takes SomeDependency which is registered in a DI container
public ViewModelB(SomeDependency someDependency, GetUserRequest request): this one takes SomeDependency which is registered in a DI container and a GetUserRequest which has to be passed in manually
And I'm trying to activate them and resolve dependencies like so:
IServiceProvider serviceProvider; //this gets passed from somewhere
Guid userId; //this gets passed manually by the caller
//works
var instanceAWithoutParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider);
//works
var instanceAWithParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider, new[] { new GetUserRequest { UserId = userId } });
//does NOT work, it tries to use the first constructor and fails
var instanceBWithoutParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider);
//works
var instanceBWithParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider,, new[] { new GetUserRequest { UserId = userId } });
The activation of instanceBWithoutParams fails because it can't resolve the request parameter. It tries to use the first constructor and doesn't check other ones when the activation fails.
Here's what the services look like, they're the same with one difference: the order of the constructors.
public class ViewModelA
{
private readonly SomeDependency _someDependency;
private readonly GetUserRequest? _request;
public ViewModelA(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public ViewModelA(SomeDependency someDependency, GetUserRequest request)
{
_someDependency = someDependency;
_request = request;
}
}
public class ViewModelB
{
private readonly SomeDependency _someDependency;
private readonly GetUserRequest? _request;
public ViewModelB(SomeDependency someDependency, GetUserRequest request)
{
_someDependency = someDependency;
_request = request;
}
public ViewModelB(SomeDependency someDependency)
{
_someDependency = someDependency;
}
}
public class GetUserRequest
{
public Guid UserId { get; set; }
}
Thanks.
I struggled with the same issue. Eventually I came up with this solution:
I would use something like a factory which is able to construct ServiceB by calling a method.
For example:
var serviceBFactory = ActivatorUtilities.CreateInstance<ServiceBFactory>(serviceProvider);
var instanceBWithoutParams = serviceBFactory.CreateServiceB();
var instanceBWithParams = serviceBFactory.CreateServiceB(new Request());
This way you keep you DI clean. But this means that the ServiceBFactory need to know which services need to be injected in a ServiceB. (so that will be a tight coupling) They come as a package.
I've chosen to re-design the view models instead of trying to pass optional parameters next to services from DI (thanks to Steven for the helpful articles: 1 and 2).
There also seems to be no way of making the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method try other constructors after one fails, so here's what my edited solution looks like.
I've moved the initialization of the optional parameter out of the constructor, that way I only have one constructor that only takes injectables. The parameter is then passed separately via the TakeParameter method. The only downside I can think of is that the parameter can no longer be readonly and I can live with that.
My custom activator utility:
public interface IAcceptParameter<T>
{
void TakeParameter(T parameter);
}
public static class CustomActivator
{
public static T CreateInstance<T>()
{
return ActivatorUtilities.CreateInstance<T>(_serviceProvider);
}
public static T CreateInstanceWithParam<T, K>(K parameter) where T : IAcceptParameter<K>
{
var instance = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
instance.TakeParameter(parameter);
return instance;
}
}
Changed view model
public class SomeViewModel : IAcceptParameter<Guid>
{
private readonly SomeDependency _someDependency;
private Guid? _userId;
public SomeViewModel(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public void TakeParameter(Guid parameter){
_userId = parameter;
}
}
How I use it
var instanceWithoutParam = CustomActivator.CreateInstance<SomeViewModel>(serviceProvider);
Guid userId;
var instanceWithParam = CustomActivator.CreateInstanceWithParam<SomeViewModel, Guid>(serviceProvider, userId);
Let say you have a class like this:
public class a
{
public string p { get; set; }
public a()
{
p = "default constructor";
}
public a(string pv)
{
p = pv;
}
}
You can use .GetConstructor method to use a specific constructor:
public class Program
{
static void Main(string[] args)
{
var c = typeof(a).GetConstructor(new Type[] { typeof(string) });
if (c != null)
{
var myA = (a)c.Invoke(new object[] { "new value" });
Console.WriteLine($"Value of p is {myA.p}");
}
}
}
How does one share temporary objects across component graphs executed at different times?
I have a state engine from some old legacy code. Each state is represented by an IState and is responsible for creating the next state in a process.
public interface IState
{
Guid Session { get; }
IState GetNextState();
}
The starting state is initialized by a model:
public class Model
{
private readonly IStateFactory _stateFactory;
public Model(IStateFactory stateFactory)
{
_stateFactory = stateFactory;
}
public IState GetFirstState()
{
return _stateFactory.GetStateA();
}
}
Each state contains a session context (simplified to only contain a GUID here).
public class Context : IDisposable
{
public static int CreatedCount = 0;
public static int DisposedCount = 0;
//Has other DI injected dependencies.
public Context()
{
CreatedCount++;
}
public Guid SessionGuid { get; } = Guid.NewGuid();
public void Dispose()
{
DisposedCount++;
}
}
The "CreatedCount" and "DisposedCount" have been added to assist in demonstrating the problem. Note that they are static ints.
An implementation of a State might be as such:
public class MyState : IState
{
private readonly Context _context;
private readonly IStateFactory _stateFactory;
public MyState(IStateFactory stateFactory, Context context)
{
_context = context;
_stateFactory = stateFactory;
}
public Guid Session => _context.SessionGuid;
public IState GetNextState()
{
var nextState = _stateFactory.GetStateB(_context);
_stateFactory.DestroyState(this);
return nextState;
}
}
The state factory is a simple Castle Windsor implemented TypedFactory interface.
public interface IStateFactory
{
IState GetFirstState();
IState GetStateB(Context context);
void DestroyState(IState state);
}
The idea is that each "state" can initiate the next state based on some action, and that the current states "context" should be used in the next state.
The container is built in the expected way:
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Context>().LifestyleTransient(),
Component.For<IState>().ImplementedBy<MyState>().Named("stateA").LifestyleTransient(),
Component.For<IState>().ImplementedBy<MyState>().Named("stateB").LifestyleTransient(),
Component.For<IStateFactory>().AsFactory()
);
Essentially, I want "stateB" to take ownership of the Context. But when I release "stateA" (through a call to MyState.GetNextState), the Context is released and disposed! How do I tell Castle.Windsor to transfer ownership to the next state?
var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState(); //releases the initial State.
Assert.That(initialState.Session, Is.EqualTo(nextState.Session)); //The context was 'shared' by stateA by passing it into the factory method for stateB.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(0)); //FAIL! Castle Windsor should see that the shared "Context" was passed into the constructor of modelB, and added a reference to it.
container.Release(model);
container.Release(nextState); //usually done by the model.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(1));
It should be noted that state transition can be initiated from another thread, but invoked on the creating thread. This messes up the CallContext used by the default Castle Windsor scoped lifestyle. This is for a desktop application so the default WCF and web-request scope lifestyles do not apply.
Use LifestyleScoped:
Component.For<Context>().LifestyleScoped()
using (container.BeginScope())
{
var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState();
Assert.That(Context.CreatedCount, Is.EqualTo(0));
}
Assert.That(Context.CreatedCount, Is.EqualTo(1));
I've come up with another solution to this problem that works where Castle Windsors scope does not. I've created a lifestyle that keeps a singleton around only as long as something is using it by using a reference count. Once the reference count goes to 0, the object is released.
public class ReferenceCountedSingleton : AbstractLifestyleManager, IContextLifestyleManager
{
private readonly object _lock = new object();
private Burden _cachedBurden;
private int _referenceCount;
public override void Dispose()
{
var localInstance = _cachedBurden;
if (localInstance != null)
{
localInstance.Release();
_cachedBurden = null;
}
}
public override object Resolve(CreationContext context, IReleasePolicy releasePolicy)
{
lock(_lock)
{
_referenceCount++;
if (_cachedBurden != null)
{
Debug.Assert(_referenceCount > 0);
return _cachedBurden.Instance;
}
if (_cachedBurden != null)
{
return _cachedBurden.Instance;
}
var burden = CreateInstance(context, false);
_cachedBurden = burden;
Track(burden, releasePolicy);
return burden.Instance;
}
}
public override bool Release(object instance)
{
lock (_lock)
{
if (_referenceCount > 0) _referenceCount--;
if (_referenceCount > 0) return false;
_referenceCount = 0;
_cachedBurden = null;
return base.Release(instance);
}
}
protected override void Track(Burden burden, IReleasePolicy releasePolicy)
{
burden.RequiresDecommission = true;
base.Track(burden, releasePolicy);
}
public object GetContextInstance(CreationContext context)
{
return context.GetContextualProperty(ComponentActivator);
}
}
It is used like this:
container.Register(Component.For<Context>).LifestyleCustom<ReferenceCountedSingleton>());
I have made an application that uses SignalR to occasionally fetch new data and show it to the users in real time. The problem is that my services are static (in my case I have named them Utilities) and inconsistently I receive errors which lead me to thinking that I have a concurrency problem with the context. Logically my services are best to be static. Here's my code, can you suggest me a better design pattern or something so I can still have my services static, but don't run into the errors from concurrent EF usage.
This is where I give each of my static services a DbContext to work with:
public static class Common
{
[ThreadStatic]
private static CryptoStatisticsDbContext _dbcontext;
internal static CryptoStatisticsDbContext DbContext => _dbcontext ?? (_dbcontext = new CryptoStatisticsDbContext());
}
Here are some of my services
TickerUtility
public static class TickerUtility
{
public delegate void UpdateTickerDelegate(int currencyId, decimal bid, decimal ask);
public static event UpdateTickerDelegate TickerUpdatedEvent;
public static List<Ticker> LastTickers { get; private set; } = new List<Ticker>();
public static IList<Ticker> GetAllTickers(Action<Ticker> actionToDoWithEachTicker)
{
var tickers = new ConcurrentBag<Ticker>();
var allCurrencies = Common.DbContext.Currencies.Include(c => c.Market).ToList();
Parallel.ForEach(allCurrencies, currency =>
//foreach (var currency in allCurrencies)
{
var ticker = GetTicker(currency);
tickers.Add(ticker);
actionToDoWithEachTicker?.Invoke(ticker);
});
return tickers.ToList();
}
}
MarketUtility
public static class MarketUtility
{
public static List<Market> Get()
{
return Common.DbContext.Markets.Include(m => m.Currencies).ToList();
}
}
SignalRUtility
public static class SignalRUtility
{
private static readonly IHubContext PublicMarketsHub = GlobalHost.ConnectionManager.GetHubContext<PublicMarketsHub>();
private static readonly IHubContext AdminMarketsHub = GlobalHost.ConnectionManager.GetHubContext<AdminMarketsHub>();
public static void SendNewTickersToClients(object state)
{
TickerUtility.GetAllTickers(ticker =>
{
if (ticker.IsVisibleForPublic)
PublicMarketsHub.Clients.Group(ticker.CurrencyId.ToString()).addNewTicker(ticker);
AdminMarketsHub.Clients.Group(ticker.CurrencyId.ToString()).addNewTicker(ticker);
});
}
public static void UpdateBalances(object state)
{
BalancesUtility.GetAvailableBalances(balance => AdminMarketsHub.Clients.All.showBalanceForMarket(balance),
(market) => AdminMarketsHub.Clients.All.showError($"Error getting {market} balances")
);
}
}
The BackgroundTimer which calls the static functions
public class BackgroundServerTimer : IRegisteredObject
{
private Timer ratesTimer;
private Timer balancesTimer;
private readonly TimeSpan ratesTimerInterval = new TimeSpan(0, 0, 0, 20);
private readonly TimeSpan balancesTimerInterval = new TimeSpan(0, 0, 0, 60);
public BackgroundServerTimer()
{
StartTimers();
}
private void StartTimers()
{
var delayStartby = new TimeSpan(0,0,0, 1);
ArbitrageUtility.AttachListenerToTickerChanges();
ratesTimer = new Timer(SignalRUtility.SendNewTickersToClients, null, delayStartby, ratesTimerInterval);
balancesTimer = new Timer(SignalRUtility.UpdateBalances, null, delayStartby, balancesTimerInterval);
}
public void Stop(bool immediate)
{
ratesTimer.Dispose();
balancesTimer.Dispose();
HostingEnvironment.UnregisterObject(this);
}
}
To elaborate on my problem see my HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
var markets = MarketUtility.Get().Select(m =>
{
m.Currencies = m.Currencies.OrderBy(c => c.Id).ToList();
return m;
});
var isUserAdmin = User.IsInRole("admin");
if (!isUserAdmin)
{
markets = markets.Select(m =>
{
m.Currencies = m.Currencies.Where(c => c.IsVisibleForPublic).ToList();
return m;
}).ToList();
markets = markets.Where(m => m.Currencies.Any()).ToList();
}
ViewBag.Markets = markets;
return isUserAdmin ? View("AdminIndex") : View();
}
}
One of my markets has 4 currencies. 2 public and 2 private. Sometimes when I view the app as a guest it shows me the 2 public, but after that I login with the admin but MarketUtility.Get() again returns me only the 2 public currencies (without me implying any .Where() clause). After some refreshes it gives me all 4 currencies. The same happens on edit and delete operations. I edit some entity (for example a currency) and the first couple of refreshes show different data. Sometimes the old data, sometimes the new data until (I suppose) EF clears it's cache and then I start to get the fresh data. I have disabled LazyLoading and ProxyCreation, now the problems occurs more rarely, but I know this is not the solution.
Parallel.ForEach(allCurrencies, currency =>
//foreach (var currency in allCurrencies)
{
var ticker = GetTicker(currency);
tickers.Add(ticker);
actionToDoWithEachTicker?.Invoke(ticker);
});
This is the line that throws exceptions like "Collection was Modified", "“The underlying provider failed on Open.” (this one I fixed using MARS, but again this is just a workaround). I don't get how can it throw collection was modified error when I use .ToList()
var allCurrencies = Common.DbContext.Currencies.Include(c => c.Market).ToList();
I want to make things the right way, to dispose the context when it's not needed, to consume less memory and everything, but still have my services static because it makes it easier for me (and I don't see a point in making them non static provided that all my methods are static
I have a project where there is a mostly linear workflow. I'm attempting to use the .NET Stateless library to act as workflow engine/state machine. The number of examples out there is limited, but I've put together the following code:
private StateMachine<WorkflowStateType, WorkflowStateTrigger> stateMachine;
private StateMachine<WorkflowStateType, WorkflowStateTrigger>.TriggerWithParameters<Guid, DateTime> registrationTrigger;
private Patient patient;
public Patient RegisterPatient(DateTime dateOfBirth)
{
configureStateMachine(WorkflowState.Unregistered);
stateMachine.Fire<DateTime>(registrationTrigger, dateOfBirth);
logger.Info("State changed to: " + stateMachine.State);
return patient;
}
private void configureStateMachine(WorkflowState state)
{
stateMachine = new StateMachine<WorkflowState, WorkflowTrigger>(state);
registrationTrigger = stateMachine.SetTriggerParameters<DateTime>(WorkflowTrigger.Register);
stateMachine.Configure(WorkflowState.Unregistered)
.Permit(WorkflowTrigger.Register, WorkflowStateType.Registered);
stateMachine.Configure(WorkflowState.Registered)
.Permit(WorkflowTrigger.ScheduleSampling, WorkflowState.SamplingScheduled)
.OnEntryFrom(registrationTrigger, (dateOfBirth) => registerPatient(dateOfBirth));
}
private void registerPatient(DateTime dateOfBirth)
{
//Registration code
}
As you can see, I'm using the Stateless Fire() overload that allows me to pass in a trigger. This is so I can have the state machine process business logic, in this case, code to register a new patient.
This all works, but now I'd like to move all the state machine code into another class to encapsulate it and I'm having trouble doing this. The challenges I've had in doing this are:
instantiating a StateMachine object requires you to specify state and State is a readonly property that can only be set at instantiation.
my registrationTrigger has to be instantiated during state machine configuration and also has to be available by the calling class.
How can I overcome these items and encapsulate the state machine code?
There is an article by Scott Hanselman with an example and introduction to a library. Also there few examples available on their GitHub including Bug implementation example mentioned in Scott's article that encapsulates the state machine.
Below is an example of how the state can be extracted from behavior:
public class PatientRegistrationState
{
private StateMachine<WorkflowState, WorkflowTrigger> stateMachine;
private StateMachine<WorkflowState, WorkflowStateTrigger>.TriggerWithParameters<DateTime> registrationTrigger;
public PatientRegistrationState(State initialState = default(State)) {
stateMachine = new StateMachine<WorkflowState, WorkflowTrigger>(initialState);
stateMachine.Configure(WorkflowState.Unregistered)
.Permit(WorkflowTrigger.Register, WorkflowStateType.Registered);
stateMachine.Configure(WorkflowState.Registered)
.Permit(WorkflowTrigger.ScheduleSampling, WorkflowState.SamplingScheduled)
.OnEntryFrom(registrationTrigger, (date) => OnPatientRegistered(date));
}
public WorkflowState State => stateMachine.State;
public Action<DateTime> OnPatientRegistered {get; set;} = (date) => { };
// For state changes that do not require parameters.
public void ChangeTo(WorkflowTrigger trigger)
{
stateMachine.Fire<DateTime>(trigger);
}
// For state changes that require parameters.
public void ChangeToRegistered(DateTime dateOfBirth)
{
stateMachine.Fire<DateTime>(registrationTrigger, dateOfBirth);
}
// Change to other states that require parameters...
}
public class PatientRegistration
{
private PatientRegistrationState registrationState;
private Patient patient;
public PatientRegistration()
{
registrationState = PatientRegistrationState(WorkflowState.Unregistered)
{
OnPatientRegistered = RegisterPatient;
}
}
public Patient RegisterPatient(DateTime dateOfBirth)
{
registrationState.ChangeToRegistered(dateOfBirth);
logger.Info("State changed to: " + registrationState.State);
return patient;
}
private void RegisterPatient(DateTime dateOfBirth)
{
// Registration code
}
}
This is how I achieved it in my project.
Separated workflow logic to separate class. I had couple of workflows based on one of the flags present in the request object; below is one of the workflow classes:
public class NationalWorkflow : BaseWorkflow
{
public NationalWorkflow(SwiftRequest request) : this(request, Objects.RBDb)
{ }
public NationalWorkflow(SwiftRequest request, RBDbContext dbContext)
{
this.request = request;
this.dbContext = dbContext;
this.ConfigureWorkflow();
}
protected override void ConfigureWorkflow()
{
workflow = new StateMachine<SwiftRequestStatus, SwiftRequestTriggers>(
() => request.SwiftRequestStatus, state => request.SwiftRequestStatus = state);
workflow.OnTransitioned(Transitioned);
workflow.Configure(SwiftRequestStatus.New)
.OnEntry(NotifyRequestCreation)
.Permit(SwiftRequestTriggers.ProcessRequest, SwiftRequestStatus.InProgress);
workflow.Configure(SwiftRequestStatus.InProgress)
.OnEntry(ValidateRequestEligibility)
.Permit(SwiftRequestTriggers.AutoApprove, SwiftRequestStatus.Approved)
.Permit(SwiftRequestTriggers.AdvancedServicesReview, SwiftRequestStatus.PendingAdvancedServices);
.....................
}
Which is triggered from the controller/any other layer:
private static void UpdateRequest(SwiftRequestDTO dtoRequest)
{
var workflow = WorkflowFactory.Get(request);
workflow.UpdateRequest();
}
As mentioned above, I had different workflow rules based on conditions in the request object and hence used a factory pattern WorkflowFactory.Get(request); you may create an instance of your workflow/inject it as desired
And inside the workflow class (BaseWorkflow class in my case), I have exposed the actions:
public void UpdateRequest()
{
using (var trans = this.dbContext.Database.BeginTransaction())
{
this.actionComments = "Updating the request";
this.TryFire(SwiftRequestTriggers.Update);
SaveChanges();
trans.Commit();
}
}
protected void TryFire(SwiftRequestTriggers trigger)
{
if (!workflow.CanFire(trigger))
{
throw new Exception("Cannot fire " + trigger.ToString() + " from state- " + workflow.State);
}
workflow.Fire(trigger);
}
I currently have a long-lived datacontext in my data layer like this:
public class DataRepository
{
private readonly NorthwindDatacontext db;
public DataRepository()
{
db = new NorthwindDatacontext();
}
public void insert(Order o)
{
db.Oreder.InsertOnSubmit(o);
db.SubmitChanges();
}
}
from what I understand it is preferred to have short-lived data context
but what I don't understand is when working with short-lived data context is how I handle the next example.
Some method on a client doing this:
public void AddOrderDetails(IEnumrable<OrderDetails> od, Order o)
{
DataRepository repo = new DataRepository();
o.OrderDeatils.AddRange(od);
repo.Update(o);
}
And now my DataRepository is like that:
public class DataRepository
{
public Order GetOrder(int id)
{
using ( var db = New NorthwindDataContext() )
{
db.ObjectTrackingEnabled = false;
var order = db.Oreder.Where(o => o.id == id ).SingleOrDefault();
return order;
}
}
public void Update (Order o)
{
using ( var db = New NorthwindDataContext() )
{
db.Order.Attach(o,true);
db.SubmitChanges();
}
}
}
Will it update the relations ? what if some of the OrderDeatils are new (no id yet) and some are just updated. How should I handle all situations ?
"Short lived" doesn't mean "it lives as long as the method is executed". It also can mean "the context lives at least as long as your repository".
And this seems to be the option preferred in most implementations:
public class DataRepository()
{
private NorthwindContext _context;
public DataRepository( NorthwindContext context )
{
this._context = context;
}
public Order GetOrder( int id )
{
return this._context.....
}
Note that by injecting the context into the repository, rather than creating a context instance per repository, you not only have the same context in all repository methods but also you can share the same instance of the context between different repositories.