Where to implement Entity Framework database transactions in modern web applications? - angularjs

Let’s assume that the primary components in your application are an Angular client, which calls an ASP.NET Web API, which uses Entity Framework to perform CRUD operations on your database. So, for example, in your API controllers, the Post (Add) method adds a new entity to the database context and then commits it to the database by calling the Entity Framework SaveChanges method.
This works fine when only one record needs to be added to the database at a time.
But, what if, for example, you want to add several records of different entity types to your database in one transaction? Where do you implement the Database.BeginTransaction and Database.CommitTransaction/RollbackTransaction? If you add a service layer to accomplish this, then what does the Angular client call?
PLEASE SEE BELOW FOR FURTHER DETAIL AND QUESTIONS.
I want to provide more detail about my current approach to solving this problem and ask the following questions:
(1) Is this a good approach, or is there a better way?
(2) My approach does not port to .NET Core, since .NET Core does not support OData yet (see https://github.com/OData/WebApi/issues/229). Any thoughts or ideas about this?
I have stated the problems that I faced and the solutions that I chose below. I will use a simple scenario where a customer is placing an order for several items – so, there is one Order record with several OrderDetail records. The Order record and associated OrderDetail records must be committed to the database in a single transaction.
Problem #1: What is the best way to send the Order and OrderDetail records from the Angular client to the ASP.NET Web API?
Solution #1: I decided to use OData batching, so that I could send all the records in one POST. I am using the datajs library to perform the batching (https://www.nuget.org/packages/datajs).
Problem #2: How do I wrap a single transaction around the Order and OrderDetail records?
Solution #2: I set up an OData batch endpoint in my Web API, which involved the following:
(1) In the client, configure a batch request route.
// Configure the batch request route.
config.Routes.MapODataServiceRoute(
routeName: "batch",
routePrefix: "batch",
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: conventions,
batchHandler: new TransactionalBatchHandler(GlobalConfiguration.DefaultServer));
}
(2) In the Web API, implement a custom batch handler, which wraps a database transaction around the given OData batch. The batch handler starts the transaction, calls the appropriate ODataController to perform the CRUD operation, and then commits/rolls back the transaction, depending on the results.
/// <summary>
/// Custom batch handler specialized to execute batch changeset in OData $batch requests with transactions.
/// The requests will be executed in the order they arrive, that means that the client is responsible for
/// correctly ordering the operations to satisfy referential constraints.
/// </summary>
public class TransactionalBatchHandler : DefaultODataBatchHandler
{
public TransactionalBatchHandler(HttpServer httpServer)
: base(httpServer)
{
}
/// <summary>
/// Executes the batch request and wraps the execution of the whole changeset within a transaction.
/// </summary>
/// <param name="requests">The <see cref="ODataBatchRequestItem"/> instances of this batch request.</param>
/// <param name="cancellation">The <see cref="CancellationToken"/> associated with the request.</param>
/// <returns>The list of responses associated with the batch request.</returns>
public async override Task<IList<ODataBatchResponseItem>> ExecuteRequestMessagesAsync(
IEnumerable<ODataBatchRequestItem> requests,
CancellationToken cancellation)
{
if (requests == null)
{
throw new ArgumentNullException("requests");
}
IList<ODataBatchResponseItem> responses = new List<ODataBatchResponseItem>();
try
{
foreach (ODataBatchRequestItem request in requests)
{
OperationRequestItem operation = request as OperationRequestItem;
if (operation != null)
{
responses.Add(await request.SendRequestAsync(Invoker, cancellation));
}
else
{
await ExecuteChangeSet((ChangeSetRequestItem)request, responses, cancellation);
}
}
}
catch
{
foreach (ODataBatchResponseItem response in responses)
{
if (response != null)
{
response.Dispose();
}
}
throw;
}
return responses;
}
private async Task ExecuteChangeSet(
ChangeSetRequestItem changeSet,
IList<ODataBatchResponseItem> responses,
CancellationToken cancellation)
{
ChangeSetResponseItem changeSetResponse;
// Since IUnitOfWorkAsync is a singleton (Unity PerRequestLifetimeManager) used by all our ODataControllers,
// we simply need to get a reference to it and use it for managing transactions. The ODataControllers
// will perform IUnitOfWorkAsync.SaveChanges(), but the changes won't get committed to the DB until the
// IUnitOfWorkAsync.Commit() is performed (in the code directly below).
var unitOfWorkAsync = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUnitOfWorkAsync)) as IUnitOfWorkAsync;
unitOfWorkAsync.BeginTransaction();
// This sends each request in the changeSet to the appropriate ODataController.
changeSetResponse = (ChangeSetResponseItem)await changeSet.SendRequestAsync(Invoker, cancellation);
responses.Add(changeSetResponse);
if (changeSetResponse.Responses.All(r => r.IsSuccessStatusCode))
{
unitOfWorkAsync.Commit();
}
else
{
unitOfWorkAsync.Rollback();
}
}
}

You do not need to implement Database.BeginTransaction and Database.CommitTransaction/RollbackTransaction if you are using Entity Framework. Entity Framework implements UnitOfWork. The only thing that you should care about is to work with a different instance of DbContext for every web request, but exaclty 1 instance for 1 request and call SaveChanges only 1 time when you made all the changes you need.
In case of any Exception during SaveChanges all the changes will be rolled back.
The angular client should not care about this, it only sends the data and checks if everything was fine.
This is very easy to do if you use an IoC framework, like Unity and let your DbContext injected in your Controller or Service.
In this case you should use the following settings (if you use Unity):
container.RegisterType<DbContext, YourDbContext>(new PerRequestLifetimeManager(), ...);
Then you can do this if you want to use it in a Controller:
public class YourController : Controller
{
private YourDbContext _db;
public YourController(DbContext context)
{
_db = context;
}
...

No need to over-complicate things. Add the code to the WebApi project. Pass around your Transaction object and re-use it. See https://msdn.microsoft.com/en-us/library/dn456843(v=vs.113).aspx for an example.

Related

Spring MongoTemplate not a part of ongoing transaction

I am attempting to transition to using MongoDB Transactions via Spring Data Mongo now that MongoDB 4.0 supports transactions, and Spring Data Mongo 2.1.5.Release supports it as well.
According to the Spring Data Mongo Documentation, you should be able to use the Spring MongoTransactionManager and have the MongoTemplate recognize and participate in ongoing transactions: https://docs.spring.io/spring-data/mongodb/docs/2.1.5.RELEASE/reference/html/#_transactions_with_mongotransactionmanager
However, this following test fails:
#Autowired
private TestEntityRepository testEntityRepository;
#Autowired
private MongoTemplate mongoTemplate;
#BeforeTransaction
public void beforeTranscation() {
cleanAndInitDatabase();
}
#Test
#Transactional
public void transactionViaAnnotation() {
TestEntityA entity1 = new TestEntityA();
entity1.setValueA("a");
TestEntityA entity2 = new TestEntityA();
entity2.setValueA("b");
testEntityRepository.save(entity1);
testEntityRepository.save(entity2);
// throw new RuntimeException("prevent commit");
List<TestEntityA> entities = testEntityRepository.findAll(Example.of(entity1));
Assertions.assertEquals(1, entities.size()); // SUCCEEDS
entities = testEntityRepository.findAll(Example.of(entity2));
Assertions.assertEquals(1, entities.size()); // SUCCEEDS
entities = mongoTemplate.findAll(TestEntityA.class);
Assertions.assertEquals(2, entities.size()); // FAILS - expected: <2> but was: <0>
}
It appears that the testEntityRepository works fine with the transaction. The asserts succeed, and if I uncomment the exception line, neither of the records are persisted to the database.
However, trying to use the mongoTemplate directly to do a query doesn't work as it appears to not participate in the transaction.
The documentation I have linked shows using the template directly within a #Transactional method like I am attempting. However, the text says
MongoTemplate can also participate in other, ongoing transactions.
which could be interpreted to mean the template can be used with different transactions, and not necessarily the implicit transaction. But that is not what the example would indicate.
Any ideas what is happening or how to get the template to participate in the same implicit transaction?

How to profile Entity Framework activity against SQL Server?

It's easy to use SQL Server Profiler to trace stored procedures activity. But how to trace SQL queries issued by LINQ via Entity Framework? I need to identify such queries (LINQ code) that consume a lot of time, are called most frequently and therefore are the first candidates for optimization.
Add this key to your connection string:
Application Name=EntityFramework
And filter by this in Profiler
Adding #ErikEJ's answer : if you are using .net Core, so you are using EFCore. There are no Database.Log property. You should use OnConfiguring override of your DbContext class and then
optionsBuilder.LogTo(Console.WriteLine);
Sample :
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.LogTo(Console.WriteLine);
}
}
I've found useful DbContext.Database.Log property.
MSDN article Logging and Intercepting Database Operations
The DbContext.Database.Log property can be set to a delegate for any method that takes a string. Most commonly it is used with any TextWriter by setting it to the “Write” method of that TextWriter. All SQL generated by the current context will be logged to that writer. For example, the following code will log SQL to the console:
using (var context = new BlogContext())
{
context.Database.Log = Console.Write;
// Your code here...
}
What gets logged?
When the Log property is set all of the following will be logged:
The approximate amount of time it took to execute the command. Note that this is the time from sending the command to getting the result object back. It does not include time to read the results.
SQL for all different kinds of commands. For example:
Queries, including normal LINQ queries, eSQL queries, and raw queries from methods such as SqlQuery
Inserts, updates, and deletes generated as part of SaveChanges
Relationship loading queries such as those generated by lazy loading
Parameters
Whether or not the command is being executed asynchronously
A timestamp indicating when the command started executing
Whether or not the command completed successfully, failed by throwing an exception, or, for async, was canceled
Some indication of the result value

Handling server exceptions with Breeze SaveChanges () method

I am using Breeze with Angular and EF 6 in my project and I have a form where I perform CRUD operations.
I have this entity called: Car2Sale which is a many-to-many table (Id, CarId, SaleId). Id is the PK for this table while CardId and SaleId are FKs to their associated tables and they are grouped together in a unique index. (CarId, SaleId).
When I want to add a new Car2Sale entity with a CarId and SaleId that already exist in the database I get this error on server side: Violation of unique constraint... at this method:
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle) {
return _repository.SaveChanges(saveBundle);
}
which is correct because I wanted to prevent the user from introducing similar keys in the same table.
On the client side I receive the error in the
entity.entityAspect.getValidationErrors();
and I display it using Toastr.js.
I was wondering what is the best practice to do exception handling in the SaveChanges() method in this case on the server side.
I was thinking about using a try catch on the server side and return a SaveResult of my own, like below:
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle) {
SaveResult myResult = null;
try {
myResult = _repository.SaveChanges(saveBundle);
} catch(Exception ex) {
Logger.Log(ex.toString());
}
return myResult;
}
Many thanks
My main objection to your particular approach is that you're swallowing the error and returning what appears to be a "happy" SaveResult to the client. The BreezeJS client will assume that the save succeeded and will update your cached entities accordingly. That's not a good idea!
You can certainly catch it on the server-side and might want to do so in order to reshape the error response. I think that I would do so INSIDE the repository as I'm disinclined to have persistence logic in my controllers. Encapsulating that stuff is the raison d'etre of the repository/Unit-of-Work pattern.
It's not clear to me how this helps you on the client side. You still need to send the error to the client and have the client do something reasonable with it.
You might want to look at the Breeze Labs SaveErrorExtensions for ideas about interpreting errors on the client. To my mind, the hard thing is communicating actionable intelligence to the user. That's a business/design decision that we can't solve for you. We can give you the information; you have to make sense with it.
HTH.

How to know a operations of Google AppEngine datastore are complete

I'm execute method Datastore.delete(key) form my GWT web application, AsyncCallback had call onSuccess() method .Them i refresh http://localhost:8888/_ah/admin immediately , the Entity i intent to delete still exist. Smilar to, I refresh my GWT web application immediately the item i intent to delete still show on web page.Note the the onSuccess() had been call.
So, how can i know when the Entity already deleted ?
public void deleteALocation(int removedIndex,String symbol ){
if(Window.confirm("Sure ?")){
System.out.println("XXXXXX " +symbol);
loCalservice.deletoALocation(symbol, callback_delete_location);
}
}
public AsyncCallback<String> callback_delete_location = new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
public void onSuccess(String result) {
// TODO Auto-generated method stub
int removedIndex = ArryList_Location.indexOf(result);
ArryList_Location.remove(removedIndex);
LocationTable.removeRow(removedIndex + 1);
//Window.alert(result+"!!!");
}
};
SERver :
public String deletoALocation(String name) {
// TODO Auto-generated method stub
Transaction tx = Datastore.beginTransaction();
Key key = Datastore.createKey(Location.class,name);
Datastore.delete(tx,key);
tx.commit();
return name;
}
Sorry i'm not good at english :-)
According to the docs
Returns the Key object (if one model instance is given) or a list of Key objects (if a list of instances is given) that correspond with the stored model instances.
If you need an example of a working delete function, this might help. Line 108
class DeletePost(BaseHandler):
def get(self, post_id):
iden = int(post_id)
post = db.get(db.Key.from_path('Posts', iden))
db.delete(post)
return webapp2.redirect('/')
How do you check the existence of the entity? Via a query?
Queries on HRD are eventually consistent, meaning that if you add/delete/change an entity then immediately query for it you might not see the changes. The reason for this is that when you write (or delete) an entity, GAE asynchronously updates the index and entity in several phases. Since this takes some time it might happen that you don't see the changes immediately.
Linked article discusses ways to mitigate this limitation.

How ria services manage transactions

I learnt how can we configure transactions in Entity Framework using TransactionScope in one other question of mine. However it still confuses me! I mean how does RIA services execute transactions and how can we specify transaction options? I mean, suppose on the client in Silverlight we specify something like this :-
someContext.Add(someEntity1);
someContext.Add(someEntity2);
someContext.Add(someEntity3);
Now when i call someContext.SubmitChanges() this is going to call InsertSomeEntity() on the server in my domain service class. What is the guarantee that all three records will be inserted into the database and if one fails all of them fails? And how can we change these options?
Chand's link has a good example. WCF RIA will submit a ChangeSet for the SubmitChanges containing all 3 Add's. In your DomainService, you can override the PersistChanges method to complete the transaction.
public class SomeEntityDomainService : DomainService
{
SomeEFContext _someEFContext;
public SomeEntityDomainService()
{
_someEFContext = new SomeEFContext();
}
public void InsertSomeEntity(SomeEntity someEntity)
{
// Called 3 times in your example
_someEFContext.SomeEntities.Add(someEntity);
}
protected override bool PersistChangeSet()
{
// Called exactly once per SubmitChanges() in Silverlight
_someEFContext.SaveChanges();
}
}
All of this happens in one request from the client to the server, not 3 requests.

Resources