IDbContextfactory how to cancel an update? - c#

In my Blazor Server application, there is a radzen datagrid. User can update grid rows. The user may change his/her mind during the update and press cancel button. I want to keep displaying the original values not the modified ones after cancel on the grid.
Here is my cancel code:
public class OrderDetailRepository : IOrderDetailRepository
{
private readonly IDbContextFactory<IMSContext> _db;
public OrderDetailRepository(IDbContextFactory<IMSContext> db)
{
_db = db;
}
...
public void Cancel(OrderDetail orderDetail)
{
using var ctx = _db.CreateDbContext();
var orderDetailEntry = ctx.Entry(orderDetail);
if (orderDetailEntry.State == EntityState.Modified)
{
orderDetailEntry.CurrentValues.SetValues(orderDetailEntry.OriginalValues);
orderDetailEntry.State = EntityState.Unchanged;
}
}
The problem is when I debug, orderDetailEntry state is detached. Shouldn't the state be modified? I look forward to your help on this.
Thank you.
Edit: Wider Scope Added
Here is the radzen grid (I simplified):
<RadzenDataGrid #ref="_grid" AllowFiltering="true" AllowPaging="true" PageSize="20" AllowSorting="true" RowClick="RowClick" ExpandMode="DataGridExpandMode.Single"
Data="#_orders" TItem="Order" EditMode="DataGridEditMode.Single" RowUpdate="#OnUpdateRow" RowCreate="#OnCreateRow" #bind-Value="#SelectedOrders"
ShowExpandColumn="false" ShowPagingSummary="true" AllowColumnResize="true">
<Template Context="order">
<RadzenCard Style="margin-bottom: 20px">
Customer:
<b>#order?.Customer?.Name</b>
</RadzenCard>
<RadzenTabs>
<Tabs>
<RadzenTabsItem Text="Order Details">
<RadzenDataGrid #ref="_gridDetail" AllowFiltering="#(_detailToInsert == null)" AllowPaging="true" PageSize="15" AllowSorting="#(_detailToInsert == null)" Data="#order.OrderDetails" TItem="OrderDetail" EditMode="DataGridEditMode.Multiple" RowUpdate="#OnUpdateRowDetail" RowCreate="#OnCreateRowDetail" AllowColumnResize="true" AllowColumnPicking="true" ShowPagingSummary="true" ColumnWidth="150px" Density="Density.Compact">
<Columns>
<RadzenDataGridColumn TItem="OrderDetail" Property="CustomerOrderNumber" Title="Customer Order" OrderIndex="20">
<EditTemplate Context="orderDetail">
<RadzenTextBox #bind-Value="orderDetail.CustomerOrderNumber" Style="width: 100%; display: block" Name="CustomerOrderNumber" />
</EditTemplate>
</RadzenDataGridColumn>
<AuthorizeView Roles="Administrators">
<RadzenDataGridColumn TItem="OrderDetail" Property="PaymentStatus" Title="Payment Status" OrderIndex="21">
<EditTemplate Context="orderDetail">
<RadzenDropDown AllowClear="true" TValue="string" #bind-Value="orderDetail.PaymentStatus" Class="w-100" Data=#paymentStatus Name="PaymentStatus" />
</EditTemplate>
</RadzenDataGridColumn>
</AuthorizeView>
<RadzenDataGridColumn TItem="OrderDetail" Property="OrderId" Title="Order Id" OrderIndex="22" />
<RadzenDataGridColumn TItem="OrderDetail" Context="orderDetail" Filterable="false" Sortable="false" TextAlign="TextAlign.Center" Width="200px" OrderIndex="22">
<Template Context="detail">
<RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Primary" Class="m-1" Click="#(args => EditRowDetail(detail))" #onclick:stopPropagation="true" Size="ButtonSize.Small">
</RadzenButton>
</Template>
<EditTemplate Context="detail">
<RadzenButton Icon="check" ButtonStyle="ButtonStyle.Primary" Class="m-1" Click="#(args => SaveRowDetail(detail))" Size="ButtonSize.Small">
</RadzenButton>
<RadzenButton Icon="close" ButtonStyle="ButtonStyle.Light" Class="m-1" Click="#(args => CancelEditDetail(detail))" Size="ButtonSize.Small">
</RadzenButton>
<RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Class="m-1" Click="#(args => ShowInlineDialog(detail))" Size="ButtonSize.Small">
</RadzenButton>
</EditTemplate>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
Here is OnInitializedAsync:
Enumerable<Order?> _orders = new List<Order?>();
IEnumerable<Vendor?> _vendors;
IEnumerable<Customer?> _customers;
RadzenDataGrid<Order?> _grid;
RadzenDataGrid<OrderDetail> _gridDetail;
protected override async Task OnInitializedAsync()
{
user = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User;
//userName = user.Identity.Name;
if (!user.Identity.IsAuthenticated)
{
NavigationManager.NavigateTo("/Identity/Account/Login", false);
}
if (DataLoading)
{
return;
}
try
{
DataLoading = true;
await UpdateOrdersIsCompletedUseCase.ExecuteAsync();
await UpdateOrdersIsContinuesUseCase.ExecuteAsync();
_vendors = await ViewAllVendorsUseCase.ExecuteAsync();
_customers = await ViewAllCustomersUseCase.ExecuteAsync();
_orders = await ViewAllOrdersUseCase.ExecuteAsync(user);
}
catch (Exception e)
{
throw;
}
finally
{
DataLoading = false;
}
SelectedOrders = new List<Order?> { _orders.FirstOrDefault() };
SelectedStatus = "";
}
Here is making grid editable:
async Task EditRowDetail(OrderDetail orderDetail)
{
await _gridDetail.EditRow(orderDetail);
}
Here is the cancel in the grid:
private void CancelEditDetail(OrderDetail orderDetail)
{
if (orderDetail == _detailToInsert)
{
_detailToInsert = null;
}
_gridDetail.CancelEditRow(orderDetail);
CancelOrderDetailUseCase.Execute(orderDetail);
}
Here is the service:
public class CancelOrderDetailUseCase : ICancelOrderDetailUseCase
{
private readonly IOrderDetailRepository _orderDetailRepository;
public CancelOrderDetailUseCase(IOrderDetailRepository orderDetailRepository)
{
this._orderDetailRepository = orderDetailRepository;
}
public void Execute(OrderDetail orderDetail)
{
_orderDetailRepository.EditCancel(orderDetail);
}
}

We would need to see a wider scope of exactly how you are loading your data for the view, then calling this "Cancel" method. However, based on what I see I would recommend Not passing entities around outside of the scope of the DbContext that raised them.
What is happening right now is that when you load your view you are loading an entity and sending it to the view to render and update. That entity left the boundary of the DbContext instance that read it, so it is Detached if it was loaded with AsNoTracking() or detached from the DbContext, or deserialized. It is effectively a view model, being a copy of data that was once a living breathing EF entity.
Creating a new DbContext instance and calling context.Entry<detachedEntity> will just associate the entity with that new DbContext but a detached entity is always just detached, it's not going to track modifications until after it is re-attached to a DbContext.
Typically for something like a Cancel action you would want to return a model for the view to refresh using rather than passing a reference, or simply purge any state the view is tracking and trigger it to reload with fresh state rather than going through the trouble of reverting changes.
Reverting data changes would involve something like (with the help of Automapper)
var existingOrderDetail = ctx.OrderDetails.Single(x => x.OrderDetailId == orderDetail.OrderDetailId);
Mapper.Map(existingOrderDetail, orderDetail);
Basically copy the values from the current data state loaded by the DbContext into the detached copy reference. However, this gets considerably more complex if you are dealing with an object graph. (if order detail has a collection of other entities) This would ultimately revert the values in the passed in reference, but that may not necessarily flow through automatically to update a view's display. A better, and simpler solution will almost always be "dump state, reload view".
Update: Ok Blazor server in a nutshell- Blazor can maintain state between updates (requests) by attempting to maintain a circuit for the user's session. At best this is a convenience but cannot be counted on so your application has to be robust enough to handle that scenario.
If you were relying on server state, you might try using a single DbContext instance between requests with a tracked OrderDetail that the view might use, but I suspect the OrderDetail entity coming in from the View action may still be a deserialized copy of the tracked entity. In any case it would be unreliable since requests can end up on a different circuit so it's more reliable not to count on that state to be available.
In your case your repository appears to be using a different DbContext for each request. This means the call to
_orders = ViewAllOrdersUseCase.ExecuteAsync(user);
would be loading orders using a new DbContext, so any calls to other Repositories via UseCases passing any one of these entities would be dealing with untracked, detached entities. An untracked entity sent to a view is effectively a copy of data that won't be tracking changes. It's simply the class with it's getters and setters with no EF Proxy around it to do the tracking and provide EntityState or OriginalValues etc. It's a DTO using the entity's Type.
Detached entities can be re-attached, but in doing so you have to manually tell EF to expect values to have been modified so EF knows to build an UPDATE statement. This is normally the case when trying to update an entity that has been altered. You would Attach it, set it's EntityState to "Modified", then call SaveChanges. This isn't a good practice because it treats all values as modified (overwriting everything) and is vulnerable to overwrite tampering. (Never trust anything from the client without validating, and only accept what you allow to be changed) It also doesn't apply for your example because what you want to do is revert the changes. EF cannot do that with a re-attached entity because the detached original never had tracking changes details to begin with.
The code I mentioned here:
var existingOrderDetail = ctx.OrderDetails.Single(x => x.OrderDetailId == orderDetail.OrderDetailId);
Mapper.Map(existingOrderDetail, orderDetail);
may work in your case if the object reference (orderDetail) passed into the Cancel action actually results in the client-side view refreshing after the Cancel action is completed. This updates the reference that was passed in, but I don't know if that would automatically, or could manually refresh the UI elements for the view. (It may work, but it may not) I personally don't do a lot of work with front-end binding like this but typically I would expect a Cancel action for a single line in a grid to simply return the current data state from the DB then the client would replace the content of that data row with the returned data.
public orderDetail Cancel(int orderDetailId)
{
using var ctx = _db.CreateDbContext();
var orderDetail = ctx.OrderDetails.Single(x => x.OrderDetailId = orderDetailId);
return orderDetail;
}
Where the client-side calling Cancel would receive the current data state OrderDetail and just refresh that row based on a template or manually updating values. (Sorry, I have no love for DataGrids of any type so I cannot really be specific on how that might be done with Blazor bindings)

Related

ASP.NET C#: Entity updating is being blocked

Experiencing an issue about updating mysql DB through EF. It's not the first time I'm dealing with it, so I had some ideas about why isn't my data getting changed. I tried changing an element in goods array; tried editing an object, recieved through LINQ-request (seen some examples of this method); made some attempts on marking element found in the database before editing (like EntityState and Attach()). Nothing of these made any difference, so I tried removing <asp:UpdatePanel> from Site.Master to see what happens (responsive for postback blocking to prevent page shaking on update), but nothing changed (while btnRedeemEdit.IsPostBack having its default value).
Code below is the function I use for updates.
protected void btnRedeemEdit_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(Request.QueryString["id"]))
{
var db = new GoodContext();
var goods = db.Goods.ToList();
Good theGood = goods.FirstOrDefault(x => x.Id == int.Parse(Request.QueryString["id"]));
//db.Goods.Attach(theGood);//No effect
//db.Entry(theGood).State = System.Data.Entity.EntityState.Modified; //No effect
if (theGood != default)
{
theGood.AmountSold = GetInput().AmountSold;
theGood.APF = GetInput().APF;
theGood.Barcode = GetInput().Barcode;
theGood.Description = GetInput().Description;
theGood.ImagesUrl = GetInput().ImagesUrl;//"https://i.pinimg.com/564x/2d/b7/d8/2db7d8c53b818ce838ad8bf6a4768c71.jpg";
theGood.Name = GetInput().Name;
theGood.OrderPrice = GetInput().OrderPrice;
theGood.Profit = GetInput().Profit;
theGood.RecievedOn = GetInput().RecievedOn;//DateTime.Parse(GetInput().RecievedOn).Date.ToString();
theGood.TotalAmount = GetInput().TotalAmount;
theGood.WeightKg = GetInput().WeightKg;
//SetGoodValues(goods[editIndex],GetInput());//Non-working
db.SaveChanges();
Response.Redirect("/AdminGoods");
}
else Response.Write($"<script>alert('Good on ID does not exist');</script>");
}
else Response.Write($"<script>alert('Unable to change: element selected does not exist');</script>");
}
Notice, that no alerts appear during execution, so object in database can be found.
Are there any more things, that can be responsible for blocking database updates?
A few things to update & check:
Firstly, DbContexts should always be disposed, so in your case wrap the DbContext inside a using statement:
using (var db = new GoodContext())
{
// ...
}
Next, there is no need to load all goods from the DbContext, just use Linq to retrieve the one you want to update:
using (var db = new GoodContext())
{
Good theGood = db.Goods.SingleOrDefault(x => x.Id == int.Parse(Request.QueryString["id"]));
if (theGood is null)
{
Response.Write($"<script>alert('Good on ID does not exist');</script>");
return;
}
}
The plausible suspect is what does "GetInput()" actually do, and have you confirmed that it actually has the changes you want? If GetInput is a method that returns an object containing your changes then it only needs to be called once rather than each time you set a property:
(Inside the using() {} scope...)
var input = GetInput();
theGood.AmountSold = input.AmountSold;
theGood.APF = input.APF;
theGood.Barcode = input.Barcode;
theGood.Description = input.Description;
// ...
db.SaveChanges();
If input has updated values but after calling SaveChanges you aren't seeing updated values in the database then there are two things to check.
1) Check that the database connection string at runtime matches the database that you are checking against. The easiest way to do that is to get the connection string from the DbContext instance's Database.
EF 6:
using (var db = new GoodContext())
{
var connectionString = db.Database.Connection.ConnectionString; // Breakpoint here and inspect.
EF Core: (5/6)
using (var db = new GoodContext())
{
var connectionString = db.Database.GetConnectionString();
Often at runtime the DbContext will be initialized with a connection string from a web.config / .exe.config file that you don't expect so you're checking one database expecting changes while the application is using a different database / server. (More common than you'd expect:)
2) Check that you aren't disabling tracking proxies. By default EF will enable change tracking which is how it knows if/when data has changed for SaveChanges to generate SQL statements. Sometimes developers will encounter performance issues and start looking for ways to speed up EF including disabling change tracking on the DbContext. (A fine option for read-only systems, but a pain for read-write)
EF6 & EF Core: (DbContext initialization)
Configuration.AutoDetectChangesEnabled = false; // If you have this set to false consider removing it.
If you must disable change tracking then you have to explicitly set the EntityState of the entity to Modified before calling SaveChanges():
db.Entry(theGood).State = EntityState.Modified;
db.SaveChanges();
Using change tracking is preferable to using EntityState because with change tracking EF will only generate an UPDATE statement if any values have changed, and only for the values that changed. With EntityState.Modified EF will always generate an UPDATE statement for all non-key fields regardless if any of them had actually changed or not.

The instance of the entity type cannot be tracked because another instance with the keyvalue is being tracked

I am basically trying to implement CRUD using EntityFrameWork core and .Net core 3.1. I have an issue with my update operation where I am not able update the context with the modified value.
I am using postman to initiate the request.
As you can see in the code below, I am trying to check if that customer exist and if it does pass the modified object to the context.
Function code
[FunctionName("EditCustomer")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous,"post", Route = "update-customer")] HttpRequest req)
{
var customer = JsonConvert.DeserializeObject<CustomerViewModel>(new StreamReader(req.Body).ReadToEnd());
await _repo.UpdateCustomer(customer);
return new OkResult();
}
Repository method
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
_context.Customers.Update(_mapper.Map<Customers>(customerViewModel));
await _context.SaveChangesAsync();
}
}
}
Mapping
public class CustomerManagerProfile : Profile
{
public CustomerManagerProfile()
{
CreateMap<CustomerDetails, CustomerDetailsViewModel>().ReverseMap();
CreateMap<CustomerOrders, CustomerOrdersViewModel>().ReverseMap();
CreateMap<CustomerOrderDetails, OrderDetailsViewModel>().ReverseMap();
CreateMap<Customers, CustomerViewModel>().ReverseMap();
}
}
Solution
public async Task UpdateCustomer(CustomerViewModel customerViewModel)
{
if (customerViewModel.CustomerId != null)
{
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
if (customer == null)
{
throw new Exception("customer not found");
}
else
{
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Entry<Customers>(customer).State = EntityState.Detached;
_context.Entry<Customers>(customerModel).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
}
}
Entity Framework tracks your entities for you. For simplicity's sake, think of it like keeping a dictionary (for every table) where the dictionary key is equal to your entity's PK.
The issue is that you can't add two items of the same key in a dictionary, and the same logic applies to EF's change tracker.
Let's look at your repository:
var customer = _context
.Customers
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
The fetched customer is retrieved from the database and the change tracker puts it in his dictionary.
var mappedCustomer = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update();
I split your code in two steps for the sake of my explanation.
It's important to realize that EF can only save changes to tracked objects. So when you call Update, EF executes the following check:
Is this the same (reference-equal) object as one I have I my change tracker?
If yes, then it's already in my change tracker.
If not, then add this object to my change tracker.
In your case, the mappedCustomer is a different object than customer, and therefore EF tries to add mappedCustomer to the change tracker. Since customer is already in there, and customer and mappedCustomer have the same PK value, this creates a conflict.
The exception you see is the outcome of that conflict.
Since you don't need to actually track your original customer object (since EF doesn't do anything with it after fetching it), the shortest solution is to tell EF to not track customer:
var customer = _context
.Customers
.AsNoTracking()
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
Since customer is now not put into the change tracker, mappedCustomer won't cause a conflict anymore.
However, you don't actually need to fetch this customer at all. You're only interested in knowing whether it exists. So instead of letting EF fetch the entire customer object, we can do this:
bool customerExists = _context
.Customers
.Any(c => c.CustomerId.Equals(customerViewModel.CustomerId));
This also solves the issue since you never fetch the original customer, so it never gets tracked. It also saves you a bit of bandwidth in the process. It's admittedly negligible by itself, but if you repeat this improvement across your codebase, it may become more significent.
The most simple adjustment that you could make would be to avoid tracking your Customers on retrieval like this:
var customer = _context
.Customers
.AsNoTracking() // This method tells EF not to track results of the query.
.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId))
.FirstOrDefault();
It's not entirely clear from the code, but my guess is your mapper returns a new instance of Customer with the same ID, which confuses EF. If you would instead modify that same instance, your call to .Update() should work as well:
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
_context.Customers.Update(customer);
await _context.SaveChangesAsync();
As a matter of fact, if you track your Customer you don't even need to explicitly call .Update() method, the purpose of tracking is to be aware of what changes were made to the entities and should be saved to the database. Therefore this will also work:
// Customer is being tracked by default.
var customer = _context.Customers.Where(c => c.CustomerId.Equals(customerViewModel.CustomerId)).FirstOrDefault();
customer.Name = "UpdatedName"; // An example.
await _context.SaveChangesAsync();
EDIT:
The solution you yourself provide begins by tracking the results of your query (the Customer) instance, then stops tracking it (a.k.a. gets detached) before writing to database and instead starts tracking the instance that represents the updated Customer and also marks it as modified. Obviously that works as well, but is just a less efficient and elegant way of doing so.
As a matter of fact if you use this bizarre approach, I don't see the reason for fetching your Customer at all. Surely you could just:
if (!(await _context.Customers.AnyAsync(c => c.CustomerId == customerViewModel.CustomerId)))
{
throw new Exception("customer not found");
}
var customerModel = _mapper.Map<Customers>(customerViewModel);
_context.Customers.Update(customerModel);
await _context.SaveChangesAsync();
You use AutoMapper wrong way. It is not created to map from View model or DTO to Entity classes. It makes many problems and you are facing with only one of them now.
If you have more complex bussiness logic in you app (not just udpate all fields), it will be horrible to manage, test and debug what actually is happening in your code. You should write you own logic with some bussiness validation in case when you want to make some other update than CRUD.
If I were you I would create UpdateFields method in Customer class which would update them and finally call SaveChanges. It depends on whether you use anemic entity (anti)pattern or not. If you do not want your entity class to have any method you can create just method which manually map you VM do entity with some domain validation

Rollingback vs Refreshing vs Reloading Entity data

I have an WPF project, MVVM with an EF6 dataset and I'm looking to implement a way to rollback all changes.
The following code shows how ViewModel loads the data:
protected async override void GetData()
{
ThrobberVisible = Visibility.Visible;
ObservableCollection<MobileDeviceRequestVM> _requests = new ObservableCollection<MobileDeviceRequestVM>();
var requests = await (from c in dbContext.MobileDeviceRequests
orderby c.RequestDate
select c)
.ToListAsync();
foreach (MobileDeviceRequest req in requests)
{
_requests.Add(new MobileDeviceRequestVM { IsNew = false, TheEntity = req });
}
MobileDeviceRequests = _requests;
RaisePropertyChanged("MobileDeviceRequests");
ThrobberVisible = Visibility.Collapsed;
}
The following code shows how the ViewModel rolls back any changes:
protected override void RollbackData()
{
var changedEntries = dbContext.ChangeTracker.Entries()
.Where(x => x.State != EntityState.Unchanged).ToList();
foreach (var entry in changedEntries)
{
switch (entry.State)
{
case EntityState.Modified:
entry.CurrentValues.SetValues(entry.OriginalValues);
entry.State = EntityState.Unchanged;
break;
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Deleted:
entry.State = EntityState.Unchanged;
break;
}
}
//Somewhere in here the OC: MobileDeviceRequests needs to get refreshed
//with the context as items may have been added and/or deleted
RaisePropertyChanged("MobileDeviceRequests");
}
The following code shows how the ViewModel refreshes the data, which may or may not rollback data depending on if something has changed:
protected virtual void RefreshData()
{
GetData();
}
The following creates a new context
protected virtual void ReloadData()
{
dbContext= new BAContext();
GetData();
}
What I'm wondering about is:
Rolling Back
vs
Refreshing
vs
Reloading
They all seem to do virtually the same thing, the ReloadData() being the more expensive.
I guess what I'm asking is, if refresh does the requery and populates the OC, is there any point in having a rollback. If there is, then how would you repopulate the OC and would it be any different than the refresh?
The aforementioned methods are not equivalent.
When executing LINQ to Entities tracking query, EF will requery the database, but then will take into account the current change tracker state of the returned entities, and will return the local entity data rather than actual data. The concept is similar to the RefreshMode.ClientWins options of the older ObjectContext.Refresh method.
The net effect of your refresh will be that it eventually will bring the new data (added to the database through different context instance or another process/user). But the current modifications will stay in effect.
So IMO you the options are just two - rollback the current or use new context. The way you implemented it, rollback method seems to work, but entity queries still will not apply changes made outside the context instance. But this applies to the tracked entity queries in a long lived context anyway, so probably might not be considered a defect.
Which one of the two you choose depends on your requirements. But the only guaranteed way to start really fresh is to use a new context instance.

Entity Framework Navigation Properties not loading til new DbContext is established

I'm still relatively new to EF, so forgive me if I'm missing an obvious concept.
Let me see if I can simplify this...Old question is in edit history, but I think I can distill this down:
FWIW, DbContext is PER REQUEST, not static, which is why the first example works. Not using DI/IoC on the controller at this point.
public class OrdersController : ApiController {
private MyDbContext db = new MyDbContext();
//controller methods....
protected override void Dispose(bool disposing) {
db.Dispose();
}
}
Works (2 separate requests):
//request 1: client code sends in a new order to be added to db
public Order Post([FromBody]Order order) {
db.Orders.Add(order);
db.SaveChanges();
return order;
}
//request 2: someone punches a button to send an email to CS about this order
public void NotifyCustomerService(int orderid) {
var order = db.Orders.Find(orderid);
//do email code here
}
Broken (single request):
//request: client code sends in a new order to be added to db AND notifies CS at same time
public Order Post([FromBody]Order order) {
db.Orders.Add(order);
db.SaveChanges();
//notify CS via email here (nav properties are not populating)
return order;
}
Works (single request) (but i know this is horrible practice):
//request: client code sends in a new order to be added to db AND notifies CS at same time (using a new dbcontext in the notification code)
public Order Post([FromBody]Order order) {
db.Orders.Add(order);
db.SaveChanges();
using(var db2 = new MyDbContext()) {
var sameOrderWTF = db.Orders.Find(order.ID);
//notify CS via email using sameOrderWTF instance here (nav properties ARE populating)
}
return order;
}
So, it seems to me, that there's some side effect of adding a new never-before-seen entity to the context, and then trying to get it's nav properties to populate. But if you create a new DbContext... even in the same request, it has to directly hit the DB for that entity, not use the in-mem copy, so then the nav properties magically work. That's the part that has me stumped.
Working Solution
//request: client code sends in a new order to be added to db AND notifies CS at same time
public Order Post([FromBody]Order o) {
Order order = db.Orders.Create();
db.Orders.Add(order);
//copy values from input to proxy instance
db.Entry(order).CurrentValues.SetValues(o);
//copy input lines to proxy instance (same process as order for each line)
o.OrderLines.ToList().ForEach(l => {
var line = db.OrderLines.Create();
db.OrderLines.Add(line);
db.Entry(line).CurrentValues.SetValues(l);
order.OrderLines.Add(line);
});
db.SaveChanges();
//notify CS via email here (nav properties are not populating)
return order;
}
So while we'll consider this question answered (thanks Uber Bot), the need to go through all of that seems more laborious than my other (admittedly short) experience with ASP.NET MVC and EF. I guess maybe I should be using ViewModels and mapping the VM properties to a proxy instance instead of trying to use the EF classes directly, but I just can't really see the benefit for a simple call like this.
Your new Order entity instance is not wrapped by proxy and so lazy loading will not work.
You can force context to load navigational property.
db.Entry(order).Reference(o => o.YouProperty).Load();
Or you can create an instance by using context's factory to overcome this problem.
db.Orders.Create();
A proxy instance will not be created if you create an instance of an
entity using the new operator. This may not be a problem, but if you
need to create a proxy instance (for example, so that lazy loading or
proxy change tracking will work) then you can do so using the Create
method of DbSet.
https://msdn.microsoft.com/en-us/data/jj592886.aspx
Just an FYI... could be simplified like..... you should add validation.
//only handles new orders...as is
//Assumes!!! Order is of a type which is a db Entity! (Table)
//Assumes that you populated "order" with all the required properties.
public Order Post([FromBody]Order order)
{
db.Orders.Add(order);
db.SaveChanges();
return order;
}
Checking your code.... again it seems like "Order" in the Method signature is not a db Order entity, not sure tho if it isn't you should rename it so that it's not confusing. I.e. rename it as OrderDTO or something else which is not the same name as the db Order. It will make understanding of your code much clearer.

How Should I be wrapping my select statements in a transaction?

I am going threw my site with nhibernate profiler and I got this message
Alert: Use of implicit transactions is
discouraged
http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions
I see they are on every single select statement.
private readonly ISession session;
public OrderHistoryRepo(ISession session)
{
this.session = session;
}
public void Save(OrderHistory orderHistory)
{
session.Save(orderHistory);
}
public List<OrderHistory> GetOrderHistory(Guid Id)
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList();
return orderHistories;
}
public void Commit()
{
using (ITransaction transaction = session.BeginTransaction())
{
transaction.Commit();
}
}
Should I be wrapping my GetOrderHistory with a transaction like I have with my commit?
Edit
How would I wrap select statements around with a transaction? Would it be like this? But then "transaction" is never used.
public List<OrderHistory> GetOrderHistory(Guid Id)
{
using (ITransaction transaction = session.BeginTransaction())
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList();
return orderHistories;
}
}
Edit
Ninject (maybe I can leverage it to help me out like I did with getting a session)
public class NhibernateSessionFactory
{
public ISessionFactory GetSessionFactory()
{
ISessionFactory fluentConfiguration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("ConnectionString")))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Map>().Conventions.Add(ForeignKey.EndsWith("Id")))
.ExposeConfiguration(cfg => cfg.SetProperty("adonet.batch_size", "20"))
//.ExposeConfiguration(BuidSchema)
.BuildSessionFactory();
return fluentConfiguration;
}
private static void BuidSchema(NHibernate.Cfg.Configuration config)
{
new NHibernate.Tool.hbm2ddl.SchemaExport(config).Create(false, true);
}
}
public class NhibernateSessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var sessionFactory = new NhibernateSessionFactory();
return sessionFactory.GetSessionFactory();
}
}
public class NhibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>().ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
}
}
Edit 3
If I do this
public List<OrderHistory> GetOrderHistory(Guid Id)
{
using (ITransaction transaction = session.BeginTransaction())
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList();
return orderHistories;
}
}
I get this alert
If I do this
public List<OrderHistory> GetOrderHistory(Guid Id)
{
using (ITransaction transaction = session.BeginTransaction())
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList().ConvertToLocalTime(timezoneId);
transaction.Commit();
return orderHistories;
}
}
I can get rid of the errors but can get unexpected results.
For instance when I get orderHistories back I loop through all of them and convert the "purchase date" to the users local time. This is done through an extension method that I created for my list.
Once converted I set it to override the "purchase date" in the object. This way I don't have to create a new object for one change of a field.
Now if I do this conversion of dates before I call the commit nhibernate thinks I have updated the object and need to commit it.
So I am putting a bounty on this question.
How can I create my methods so I don't have to wrap each method in a transaction? I am using ninject already for my sessions so maybe I can leverage that however some times though I am forced to do multiple transactions in a single request.
So I don't know have just one transaction per request is a soultion.
how can I make sure that objects that I am changing for temporary use don't accidentally get commit?
how can I have lazy loading that I am using in my service layer. I don't want to surround my lazy loading stuff in a transaction since it usually used in my service layer.
I am finding it very hard to find examples of how to do it when your using the repository pattern. With the examples everything is always written in the same transaction and I don't want to have transactions in my service layer(it is the job of the repo not my business logic)
The NHibernate community recommends that you wrap everything in a transaction, regardless of what you're doing.
To answer your second question, generally, it depends. If this is a web application, you should look at the session-per-request pattern. In most basic scenarios, what this means is that you'll create a single session per HTTP request in which the session (and transaction) is created when the request is made and committed/disposed of at the end of the request. I'm not saying that this is the right way for you, but it's a common approach that works well for most people.
There are a lot of examples out there showing how this can be done. Definitely worth taking the time to do a search and read through things.
EDIT: Example of how I do the session/transaction per request:
I have a SessionModule that loads the session from my dependency resolver (this is a MVC3 feature):
namespace My.Web
{
public class SessionModule : IHttpModule {
public void Init(HttpApplication context) {
context.BeginRequest += context_BeginRequest;
context.EndRequest += context_EndRequest;
}
void context_BeginRequest(object sender, EventArgs e) {
var session = DependencyResolver.Current.GetService<ISession>();
session.Transaction.Begin();
}
void context_EndRequest(object sender, EventArgs e) {
var session = DependencyResolver.Current.GetService<ISession>();
session.Transaction.Commit();
session.Dispose();
}
public void Dispose() {}
}
}
This is how I register the session (using StructureMap):
new Container(x => {
x.Scan(a => {
a.AssembliesFromApplicationBaseDirectory();
a.WithDefaultConventions();
});
x.For<ISessionFactory>().Singleton().Use(...);
x.For<ISession>().HybridHttpOrThreadLocalScoped().Use(sf => sf.GetInstance<ISessionFactory>().OpenSession());
x.For<StringArrayType>().Use<StringArrayType>();
});
Keep in mind that this is something I've experimented with and have found to work well for the scenarios where I've used NHibernate. Other people may have different opinions (which are, of course, welcome).
Well, i guess you could set a Transaction level that's appropriate for the kind of reads that you perform in your application, but the question is: should it really be required to do that within the application code? My guess is no, unless you have a use case that differs from the default transaction levels that (n)hibernate will apply by configuration.
Maybe you can set transaction levels in your nhibernate config.
Or maybe the settings for the profiler are a bit overzealous? Can't tell from here.
But: Have you tried commiting a read transaction? Should not do any harm.
You're passing the ISession into the repository's constructor, which is good. But that's the only thing I like about this class.
Save just calls session.Save, so it's not needed.
GetOrderHistory appears to be retrieving a single entity by ID, you should use session.Get<OrderHistory>(id) for this. You can put the result into a collection if needed.
The Commit method shouldn't be in a repository.
To answer your questions directly...
How can I create my methods so I don't have to wrap each method in a
transaction? I am using ninject
already for my sessions so maybe I can
leverage that however some times
though I am forced to do multiple
transactions in a single request.
The pattern I recommend is below. This uses manual dependency injection but you could use Ninject to resolve your dependencies.
List<OrderHistory> orderHistories;
var session = GetSession(); // Gets the active session for the request
var repository = new OrderHistory(Repository);
// new up more repositories as needed, they will all participate in the same transaction
using (var txn = session.BeginTransaction())
{
// use try..catch block if desired
orderHistories = repository.GetOrderHistories();
txn.Commit();
}
So I don't know have just one
transaction per request is a soultion.
It's perfectly fine to have multiple transactions in a session. I don't like waiting until the request ends to commit because it's too late to provide good feedback to the user.
how can I make sure that objects that
I am changing for temporary use don't
accidentally get commit?
The only sure way is to use an IStatelessSession. Less sure ways are to Evict the object from the Session or Clear the session. But with NHibernate it's not recommended to modify persistent objects.
how can I have lazy loading that I am using in my service layer. I don't
want to surround my lazy loading stuff
in a transaction since it usually used
in my service layer.
If you're using session-per-request this shouldn't be a problem. But you're right that lazy-loading can happen outside of the transaction. I ignore these warnings. I suppose you could "touch" every child object needed so that lazy loads are in a transaction but I don't bother.
I don't want to have transactions in
my service layer(it is the job of the
repo not my business logic)
I disagree with this. The UI or business logic should manage the transaction. The UI is where the user expresses their intent (save or cancel my changes) and is the natural place to manage the transaction.
Recommended approach is unit of work
(session+transaction) per request.
Sure you can use NInject to manage
session lifecycle, I blogged
recently about similar approach
using Castle Windsor.
Here are 4 options:
Don't change entities temporary
Use stateless session in such cases
Detach objects when you are going to
do temporary change
Rollback transaction
I'd go with first one.
You don't have to worry about lazy loading if you are using session-per-request pattern - it will be executed in same request and wrapped with transaction automatically.

Categories