I'm sending an email from an MVC controller.
[HttpPost]
public async Task<ActionResult> Send(SendModel model)
{
var userId = HttpContext.User.Identity.GetUserId();
// This method takes current user ID and retrieves the user from the DB
var thisUser = await GetThisApplicationUserAsync();
if (thisUser.FeedbackSendLimit <= 0) return RedirectToActionWithMessage(MessageType.Error, "You can't send emails anymore! You have exceeded your send limit!", "Send");
// Handling the case when the passed model is invalid
if (!ModelState.IsValid) return View(model);
using (var transaction = _dbContext.Database.BeginTransaction())
{
// _dbContext is a DbContext initialized in the controller's constructor
var companiesToSend = _dbContext
.Companies
.Where(x => model.CompanyIds.Contains(x.Id))
.ToArray();
try
{
// Each user has a limit of emails they can send monthly
thisUser.FeedbackSendLimit -= 1;
// Each company has a limit of how many emails we can address them as well
foreach (var company in companiesToSend)
{
company.FeedbackCounter -= 1;
}
var newSend = new FeedbackSend
{
Id = Guid.NewGuid(),
UserId = userId,
SentAt = DateTime.Now,
Companies = companiesToSend
};
_dbContext.FeedbackSends.Add(newSend);
await _dbContext.SaveChangesAsync();
// This generates an HTML email and sends it to specified email address
await SendFeedbackEmailAsync(model.ToEmail, thisUser, companiesToSend);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
return RedirectToActionWithMessage(MessageType.Error, "An error occurred while trying to send feedback", "Send");
}
}
return RedirectToActionWithMessage(MessageType.Success, "Sent successfully", "Send");
}
Here are two questions:
1. Do I really need a transaction here? Wouldn't using _dbContext.SaveChanges() be enough in this case? I used a transaction to revert everything back in case SendFeedbackEmailAsync failed and no email sent.
transaction.Commit() doesn't seem to be updating thisUser.FeedbackSendLimit. Should I retrieve the user in the transaction using block to get it working?
Technologies:
Entity Framework 6.0
ASP.NET MVC 5
Do you need the explicit transaction: No. If your Try block completes and calls SaveChanges, the changes will be committed as one effective transaction. If the exception gets caught, no SaveChanges happens so the transaction is rolled back when the Context is disposed.
Why your User change isn't saved? This is most likely because your user was loaded by a different DbContext instance in the GetThisApplicationUserAsync() method, or was loaded AsNoTracking() or otherwise detached.
When retrieving data and performing updates, do it within the scope of a single DbContext instance.
using (var context = new MyDbContext())
{
var thisUser = context.Users.Where(x => x.Id == userId).Single();
var companiesToSend = context.Companies
.Where(x => model.CompanyIds.Contains(x.Id))
.ToArray();
//....
From there when the Context SaveChanges is called, that user is tracked by the Context and will be persisted. The uglier way to deal with it is to check if the thisUser is tracked by the context (no) or a user with the same PK is tracked by the context (no, if a new DbContext) and if not, Attach the user to that context, however the user instance needs to be first detached from any DbContext instance it may still be attached to. Messy.
I'm not a fan of initializng a module-level DbContext but rather ensuring instances are instantiated and disposed in the scope they are needed. Module level contexts make it harder to predict method chains where changes may be inadvertently saved when some method decides to call SaveChanges, and leads to odd placement of explicit transactions and such to try and normalize behaviour. Using blocks make that a lot easier. If you want to DI a context then I recommend considering either a Repository pattern and/or a DbContextFactory/UnitOfWork dependency to enable mocking the results (Repository) or mocking the DbContext (Factory).
My go-to pattern is Repository (non-generic) /w the DbContextScopeFactory/Locator pattern for Unit of Work by Mehdime.
Related
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
I am using EF5 and Data First approach to Update entities.
I am using approach suggested by other questions to conditionally update only modified properties in the Entities.
Oki so here's the scenario My controller call Service with POCO objects and gets POCO objects from Service, The Service layer talks with Data layer which internally uses EF5 to retrieve entity from DB and Update them in DB.
The View data is loaded by controller from DTO object retrieved from Service layer.
User makes changes to View and Posts back JSON data to controller which gets mapped to DTO object in controller (courtesy MVC).
The controller makes call to Service layer with the DTO object (POCO) object.
The Service maps the POCO object to EF entity object and calls the Data layer's(i.e Repository) Update method passing in the EF entity.
In the Repository I fetch the existing entity from DB and call ApplyCurrentvaluesValues method, then I check if any properties are modified .
If properties are modified then I apply my custom logic to other entities which are not related to current entity and also Update the "UpdatedAdminId" & "UpdationDate" of current entity.
Post this I call "SaveChanges" method on Centext.
Every thing above I mentioned is working fine , except if I insert a break point in "SaveChanges" call and update some field modified by User to different value then "DbUpdateConcurrencyException" is not thrown by EF5.
i.e. I can get conditional Update & fire my custom logic when properties of my interest are modified to work perfectly.
But I am not getting error in case of the concurrency i.e the EF is not raising "DbUpdateConcurrencyException" in case a record is updated in between me fetching the record from DB , updating the record and saving it.
In real scenario there is a offline cron running which checks for newly created campaign and creates portfolio for them and marks the IsPortfolioCreated property below as true, in the mean time user can edit the campaign and the flag can be set to false even though the cron has created the portfolios.
To replicate the concurrency scenario I put a break point on SaveChanges and then Update the IsPortfolioCreated feild from MS-Sql enterprise manager for the same entity, but the "DbUpdateConcurrencyException" is not thrown even though the Data in Store has been updated.
Here's my code for reference,
Public bool EditGeneralSettings(CampaignDefinition campaignDefinition)
{
var success = false;
//campaignDefinition.UpdatedAdminId is updated in controller by retreiving it from RquestContext, so no its not comgin from client
var updatedAdminId = campaignDefinition.UpdatedAdminId;
var updationDate = DateTime.UtcNow;
CmsContext context = null;
GlobalMasterContext globalMasterContext = null;
try
{
context = new CmsContext(SaveTimeout);
var contextCampaign = context.CampaignDefinitions.Where(x => x.CampaignId == campaignDefinition.CampaignId).First();
//Always use this fields from Server, no matter what comes from client
campaignDefinition.CreationDate = contextCampaign.CreationDate;
campaignDefinition.UpdatedAdminId = contextCampaign.UpdatedAdminId;
campaignDefinition.UpdationDate = contextCampaign.UpdationDate;
campaignDefinition.AdminId = contextCampaign.AdminId;
campaignDefinition.AutoDecision = contextCampaign.AutoDecision;
campaignDefinition.CampaignCode = contextCampaign.CampaignCode;
campaignDefinition.IsPortfolioCreated = contextCampaign.IsPortfolioCreated;
var campaignNameChanged = contextCampaign.CampaignName != campaignDefinition.CampaignName;
// Will be used in the below if condition....
var originalSkeForwardingDomain = contextCampaign.skeForwardingDomain.ToLower();
var originalMgForwardingDomain = contextCampaign.mgForwardingDomain.ToLower();
//This also not firing concurreny exception....
var key = ((IObjectContextAdapter) context).ObjectContext.CreateEntityKey("CampaignDefinitions", campaignDefinition);
((IObjectContextAdapter)context).ObjectContext.AttachTo("CampaignDefinitions", contextCampaign);
var updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
ObjectStateEntry entry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(updated);
var modifiedProperties = entry.GetModifiedProperties();
//Even tried this , works fine but no Concurrency exception
//var entry = context.Entry(contextCampaign);
//entry.CurrentValues.SetValues(campaignDefinition);
//var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList();
// If any fields modified then only set Updation fields
if (modifiedProperties.Count() > 0)
{
campaignDefinition.UpdatedAdminId = updatedAdminId;
campaignDefinition.UpdationDate = updationDate;
//entry.CurrentValues.SetValues(campaignDefinition);
updated = ((IObjectContextAdapter)context).ObjectContext.ApplyCurrentValues(key.EntitySetName, campaignDefinition);
//Also perform some custom logic in other entities... Then call save changes
context.SaveChanges();
//If campaign name changed call a SP in different DB..
if (campaignNameChanged)
{
globalMasterContext = new GlobalMasterContext(SaveTimeout);
globalMasterContext.Rename_CMS_Campaign(campaignDefinition.CampaignId, updatedAdminId);
globalMasterContext.SaveChanges();
}
}
success = true;
}
catch (DbUpdateConcurrencyException ex)
{
//Code never enters here, if it does then I am planning to show the user the values from DB and ask him to retry
//In short Store Wins Strategy
//Code in this block is not complete so dont Stackies don't start commenting about this section and plague the question...
// Get the current entity values and the values in the database
var entry = ex.Entries.Single();
var currentValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
// Choose an initial set of resolved values. In this case we
// make the default be the values currently in the database.
var resolvedValues = databaseValues.Clone();
// Update the original values with the database values and
// the current values with whatever the user choose.
entry.OriginalValues.SetValues(databaseValues);
entry.CurrentValues.SetValues(resolvedValues);
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw ex.InnerException;
throw;
}
finally
{
if (context != null) context.Dispose();
if (globalMasterContext != null) globalMasterContext.Dispose();
}
return success;
}
Entity framework it's not doing anything special about concurrency until you (as developer) configure it to check for concurrency problems.
You are trying to catch DbUpdateConcurrencyException, the documentation for this exception says: "Exception thrown by DbContext when it was expected that SaveChanges for an entity would result in a database update but in fact no rows in the database were affected. ", you can read it here
In a database first approach, you have to set the property 'Concurrency Mode' for column on 'Fixed' (the default is None). Look at this screenshot:
The column Version is a SQL SERVER TIMESTAMP type, a special type that is automatically updated every time the row changes, read about it here.
With this configuration, you can try with this simple test if all is working as expected:
try
{
using (var outerContext = new testEntities())
{
var outerCust1 = outerContext.Customer.FirstOrDefault(x => x.Id == 1);
outerCust1.Description += "modified by outer context";
using (var innerContext = new testEntities())
{
var innerCust1 = innerContext.Customer.FirstOrDefault(x => x.Id == 1);
innerCust1.Description += "modified by inner context";
innerContext.SaveChanges();
}
outerContext.SaveChanges();
}
}
catch (DbUpdateConcurrencyException ext)
{
Console.WriteLine(ext.Message);
}
In the example above the update from the inner context will be committed, the update from the outer context will thrown a DbUpdateConcurrencyException, because EF will try to update the entity using 2 columns as a filters: the Id AND the Version column.
Hope this helps!
I am writing some tests to excersize the repository layer of a library built on Telerik OpenAccess ORM and am running into some problems with managing the Context.
I am creating a new RegionEntity object and adding it to the database. I use the using statement so that the context cleans up after itself. I additionally create a Detached copy of the added RegionEntity so that it can be re-attached to a context later on.
private RegionEntity AddTestRegionToTable()
{
String regionName = Guid.NewGuid().ToString();
RegionEntity newRegion = new RegionEntity () { /*...property assignment goes here ...*/ };
RegionEntity ret = null;
using (DbContext ctx = new DbContext())
{
ctx.Add(newRegion);
ctx.SaveChanges();
ret = ctx.CreateDetachedCopy<RegionEntity>(newRegion);
}
return ret;
}
So far ... no problem. In my TestMethod below I call the above method and receive a Detached RegionEntity. (I have pulled out my assert statements as they are inconsequential to the issue). I then pass the entity to the Respository method I want to test.
[TestMethod]
public void RemoveRegion_Success()
{
//
// Assemble
RegionEntity origEntity = AddTestRegionToTable();
//
// Act
deletedEntity = RegionRepository.RemoveEntity<RegionEntity>(origEntity);
//
// Assert
/* asserts go here */
}
For the sake of completeness, below I have included ALL the remaining code, exactly as it appears in my application. The repository methods are Generic (again ... should not be relevant to the issue). The first method is the one that is called by the test method, passing in the region as the entityToRemove parameter. This method, in turn calls the DBUtils method, GetContext(), that will either retrieve the DbContext from the entity, or ... if one is not able to be derived... create a new context to be used. In our example a new context is being created.
public class RegionRepository
{
public static T RemoveEntity<T>(T entityToRemove) where T : class
{
T ret = null;
using (DbContext ctx = DbUtils.GetContext<T>(entityToRemove))
{
ret = RemoveEntity<T>(ctx, entityToRemove);
ctx.SaveChanges();
}
return ret;
}
public static T RemoveEntity<T>(DbContext ctx, T entityToRemove) where T : class
{
//
// first chcek to see if the listingToUpdate is attached to the context
ObjectState state = OpenAccessContext.PersistenceState.GetState(entityToRemove);
//
//If the object is detached then attach it
if (state.HasFlag(ObjectState.Detached))
{
ctx.AttachCopy<T>(entityToRemove);
}
//
// confirm that the DETACHED flag is no longer present.
ObjectState state2 = OpenAccessContext.PersistenceState.GetState(entityToRemove);
if (state2.HasFlag(ObjectState.Detached))
{
throw new Exception("Unable to attach entity to context");
}
ctx.Delete(entityToRemove);
return entityToRemove;
}
}
public class DBUtils
{
public static DbContext GetContext<T>(T entity)
{
DbContext ret = OpenAccessContextBase.GetContext(entity) as DbContext;
if(ret == null)
{
ret = new DbContext();
}
return ret;
}
}
Anyway, the method then passes this context and the entity as parameters to an overload. This method takes the DbContext as an additional parameter (allows a single context to be used in multi-step workflows). So the context that is used should still be the one we extracted from the entity or created in our GetContext() method. I then check to see if the entity is attached to the context or not. In this scenario I AM getting a flag of "Detached" as one of the state flags (others are MaskLoaded | MaskManaged | MaskNoMask) so the process then attaches the entity to the context and upon the second check I confirm that the Detached flag is no longer present.
As it turns out the entity is NOT being attached ... and the exception is being thrown.
I have read the Telerik documentation on Detaching and attaching objects to a context ... Attaching and Detaching Objects
By design ObjectState is flags enum that contains both the basic values that form the persistent states of Data Access and the persistent states themselves.
In this enum, Detached is a value that participates in the three detached persistent states: DetachedClean, DetachedDirty, and DetachedNew. You can find more information about the values and the states in this article.
When you detach an object from the context, its state is DetachedClean. If at this point you change any of the properties, the state of the object will become DetachedDirty. If you attach the object back, it will remain in the state before the attachment. Simply put, the action of attaching the object does not change its state.
In other words, checking for Detached is the reason why you get the "Unable to attach entity to context" exception. This value will always be available in the state of your object.
As I am reading the code forward, on this line:
ctx.Delete(entityToRemove);
You will get an exception anyway, because Data Access does not allow you to delete objects that are retrieved through another instances of the context. The exception is:
InvalidOperationException: Object references between two different object scopes are not allowed.
I hope this helps.
-= EDIT =-
When you attach a certain object to an instance of the context and call the SaveChanges() method, Data Access will automatically decide whether to insert a new row in the database or to update an existing row. In this connection, the insert and update scenarios are handled by the Attach / Detach API.
Regarding the delete scenario, you have two options:
To retrieve the object from the database and to delete it through the Delete() method (and call SaveChanges()), like this:
var myObj = ctx.RegionEntities.First(r => r.Id == entityToRemove.Id);
ctx.Delete(myObj);
ctx.SaveChanges();
To use the BulkDelete feature like this:
var myObj = ctx.RegionEntities.Where(r => r.Id == entityToRemove.Id);
int deletedObjects = myObj.DeleteAll();
Something you need to consider with the first option is whether to call SaveChanges() after you attach the object. It is a good idea to do so if there are changes you would like to persist before deleting the object. Additionally, when you use the Delete() method of the context you need to commit the change through the SaveChanges() method before you dispose the current instance of the context. If you do not do this, the transaction will be rolled back, meaning that the object will not be deleted. Details about the transaction handling are available here.
The second option, Bulk Delete, executes the delete operations in a separate transaction upon the call to the DeleteAll() method. Therefore, any other uncommitted changes are not affected. Nevertheless, you need to consider a call to SaveChanges() after attaching the object, especially if the attached object and the deleted one are one and the same object.
I'm working on setting up a new MVC payment site with a dependency-injected database connection in a separate project, and experimenting with some new things as I do. Currently, I'm trying to load an existing transaction from the database, authorize the card payment, and then save the result back to the database. Simple and straightforward, but when I call SaveChanges(), nothing gets saved, and I've run out of things to try.
The database interaction for this is handled by a CheckoutDataProvider:
public class CheckoutDataProvider : ICheckoutDataProvider
{
private readonly CheckoutEntities _context;
public CheckoutDataProvider(CheckoutEntities _context)
{
this._context = _context;
}
public ITransaction GetTransactionDetails(Guid transactionId)
{
var trans = _context.Transactions.FirstOrDefault(x => x.CheckoutTransactionId == transactionId);
return trans; // It's OK if trans == null, because the caller will expect that.
}
public void AddAuthorization(ITransaction transaction, IAuthorizationHistory history)
{
try
{
var trans = (Transaction)transaction;
var hist = (AuthorizationHistory)history;
trans.AuthorizationHistories.Add(hist);
_context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
throw new InvalidDataException(ex.EntityValidationErrors.First().ValidationErrors.First().ErrorMessage, ex);
}
}
}
Transaction and AuthorizationHistory are EF objects and correspond directly to the database tables. CheckoutEntities is the context, as injected by Ninject.
GetTransactionDetails() works flawlessly. I give it the transactionId, I get the object, and then I use that data to run the card and generate the AuthorizationHistory class. Then I call AddAuthorization() to attach it to the transaction and save it to the database. But both the new AuthorizationHistory object and any changes to the original Transaction fail to save.
I can tell from inspecting the _context object that it's not aware of any changes, and if I make changes withing GetTransactionDetails() (before it gets returned as an interface) they will persist. So it looks like a problem with the casting (which makes me feel icky anyway, so I'd love to find out that that's the problem).
Am I missing something obvious? Is there something missing to get this to work? Is there a way to avoid the casting in AddAuthorization()?
Probably you are not sharing the same DBContext Between GetTransactionDetails and AddAuthoritzation. Due to this reason Entity Framework is not able to track the changes.
Set the scope life of DBContext for web request, you can do it with Ninject with .InRequestScope() https://github.com/ninject/ninject/wiki/Object-Scopes , with this option the same DBContext will be used during a web request.
It appears that is a best practice to put all the database calls into a transaction. So, I wanted to put a select action in a transaction, but I can't find how to do this.
I have tried this code, but I get an error:
using (var session = GetSession().SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
// var session = GetSession();
var result = session.Query<I>().Where(condition);
transaction.Commit();
return result;
}
Error:
Session is closed!
Object name: 'ISession'.
It's not a matter of Transaction itself, although I only use transactions for save/update calls, not selects, but that might be a matter of preference (or I simply don't know something important).
The thing is, you're not 'materializing' the collection before closing the session.
This should work:
var result = session.Query<I>.Where(condition).List();
return result;
The Where does not do anything by itself. Which means you're just deferring execution of the filter until you do something with it - e.g. iterate over it. If you're out of Session scope by then (and it seems you are), you'll get the exception, since you can't call the database when the session is closed.
Although you probably won't be able to access lazily loaded child items without eagerly Fetching them first - you can't call database through proxy when you're not inside an open session. :)
Disclaimer
By the way, same thing would happen in EF with LINQ:
IEnumerable<I> myObjects;
using(var context = new MyDbContext())
{
myObjects = context.Set<I>.Where(x => x.Name == "Test");
}
foreach(obj in myObjects)
{
var name = obj.Name; //BOOM! Context is disposed.
}