PostgreSql - EFCore - Transaction not working as expected - postgresql

The project is an ASP.Net core project using EF core en PostgreSql as database.
Basically the flow boils down to :
var transaction = Database.BeginTransaction();
var someEntityA = new EntityA()
{
Id = Guid.NewGuid().ToString();
};
await DataBase.DbSet<EntityA>.AddAsync(someEntityA);
await DataBase.SaveChangesAsync();
var someEntityB = new EntityB()
{
Id = Guid.NewGuid().ToString();
EntityAId = someEntityA.Id;
};
await DataBase.DbSet<EntityB>.AddAsync(someEntityB);
await DataBase.SaveChangesAsync();
transaction.Commit();
When the second SaveChangesAsync is hit, a FK violation is thrown.
Do we execute the same flow without
var transaction = Database.BeginTransaction();
transaction.Commit();
Then it is all fine.
This flow is spread over multiple classes (service and repository classes) so just removing the first SaveChangesAsync is not an option. Hence that is the reason why using the manually transaction handling.
This flow works fine in MS Sql but in PostgreSql apparantly not.
Any suggestions how to make it work in PostgreSql ?

Related

Azure Mobile Offline Sync: Cannot delete an operation from __operations

I'm having a huge issue that I've been trying for days to get through. I have a scenario in which I'm trying to handle an Insert Conflict in my Xamarin project. The issue is that the record in the Cloud DB doesn't exist because there was an issue with a foreign key constraint so I'm in a scenario in which the sync conflict handler needs to delete the local record along with the record in the __operations table in SQLite. I've tried everything. Purge with the override set to 'true' so that it should delete the local record and all operations associated. Doesn't work. I've been just trying to force delete it by accessing the SQL store manually:
var id = localItem[MobileServiceSystemColumns.Id];
var operationQuery = await store.ExecuteQueryAsync("__operations", $"SELECT * FROM __operations WHERE itemId = '{id}'", null).ConfigureAwait(false);
var syncOperation = operationQuery.FirstOrDefault();
var tableName = operation.Table.TableName;
await store.DeleteAsync(tableName, new List<string>(){ id.ToString() });
if (syncOperation != null)
{
await store.DeleteAsync("__operations", new List<string>() { syncOperation["id"].ToString() }).ConfigureAwait(false);
}
I am able to query the __operations table and I can see the ID of the item I want to delete. The DeleteAsync method runs without exception but no status is returned so I have no idea if this worked or not. When I try to sync again the operation stubbornly exists. This seems ridiculous. How do I just delete an operation without having to sync with the web service? I'm about to dig down further and try to force it even harder by using the SQLiteRaw library but I'm really really hoping I'm missing something obvious? Can anyone help? THANKS!
You need to have a subclass of the Microsoft.WindowsAzure.MobileServices.Sync.MobileServiceSyncHandler class, which overrides OnPushCompleteAsync() in order to handle conflicts and other errors. Let's call the class SyncHandler:
public class SyncHandler : MobileServiceSyncHandler
{
public override async Task OnPushCompleteAsync(MobileServicePushCompletionResult result)
{
foreach (var error in result.Errors)
{
await ResolveConflictAsync(error);
}
await base.OnPushCompleteAsync(result);
}
private static async Task ResolveConflictAsync(MobileServiceTableOperationError error)
{
Debug.WriteLine($"Resolve Conflict for Item: {error.Item} vs serverItem: {error.Result}");
var serverItem = error.Result;
var localItem = error.Item;
if (Equals(serverItem, localItem))
{
// Items are the same, so ignore the conflict
await error.CancelAndUpdateItemAsync(serverItem);
}
else // check server item and local item or the error for criteria you care about
{
// Cancels the table operation and discards the local instance of the item.
await error.CancelAndDiscardItemAsync();
}
}
}
Include an instance of this SyncHandler() when you initialize your MobileServiceClient:
await MobileServiceClient.SyncContext.InitializeAsync(store, new SyncHandler()).ConfigureAwait(false);
Read up on the MobileServiceTableOperationError to see other conflicts you can handle as well as its methods to allow resolving them.

Testing EF Core with relational data, schema and transactions

I have a problem creating a unit test with EF Core (2.0.1).
I create my db with the following options:
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.ConfigureWarnings((b) =>
{
b.Ignore(InMemoryEventId.TransactionIgnoredWarning);
})
.Options;
The code I want to test looks something like this:
using (IDbContextTransaction transaction = await context.Database.BeginTransactionAsync())
{
await context.Database.ExecuteSqlCommandAsync("DELETE FROM fooSchema.Customers WHERE ID = {0}", id);
await context.SaveChangesAsync();
// Other stuff...
context.Customers.Add(fooCustomer);
await context.SaveChangesAsync();
}
First I had the issue with InMemory not supporting transactions. I solved it using ConfigureWarnings as shown in the code. But then it turns out InMemory doesn't handle ExecuteSqlCommandAsync. So then I tried SQLLite, but it doesn't handle custom schemas instead.
How do I create a DbContext, without any "real" DB, that handles transactions, schema and ExecuteSqlCommandAsync?
It is OK to suppress the error from ExecuteSqlCommandAsync. But I cannot find the EventId for it. In reality it works great, this is just for the unit test.

Replace GET handler in TableController

In my table controller, I have:
public IQueryable<MyTable> GetAllMyTable()
I would like to replace the above with my own:
[HttpGet, Route("tables/MyTable")]
public IEnumerable<MyTable> GetAllMyTable()
But I get this response when I call it:
HTTP/1.1 405 Method Not Allowed
Somehow the Web API routing does not reach my method.
Why I'm doing this: the original method produces an inefficient Entity Framework SQL query that takes 3 seconds per call on my local test environment. This is running the query captured from SQL Profiler directly in SQL Mgt Studio. An equivalent query takes less than a second to run. Terrible.
Worse, the inefficient EF queries consumes lots of Azure SQL DTUs, tempting you to up your Azure subscription level if you want a quick fix.
Azure Mobile Apps is wonderful, but the multiple layers of abstraction makes it hard to really see what's going on under the hood, and therefore harder to tune.
Any help would be much appreciated.
HTTP/1.1 405 Method Not Allowed
Per my understanding, the error is obvious. You could send the GET HTTP verb to your endpoint tables/MyTable for retrieving the data. You need to check your request against your mobile app backend via fiddler.
Azure Mobile Apps is wonderful, but the multiple layers of abstraction makes it hard to really see what's going on under the hood, and therefore harder to tune.
For the common table controller, it would look like this:
public IQueryable<Message> GetAllMessage()
{
return Query();
}
The Query() method under EntityDomainManager.cs would equal as follows:
IQueryable<TData> query = this.Context.Set<TData>();
if (!includeDeleted)
{
query = query.Where(item => !item.Deleted);
}
return query;
If it deals with the ODATA queries (e.g. $top, $skip, $filter, etc.), the Nested SQL statement would be generated. We could modify the action to clarify it as follows:
public IEnumerable<Message> GetAllMessage(ODataQueryOptions opt)
{
var message = context.Set<Message>();
var query2=opt.ApplyTo(message, new ODataQuerySettings());
return query2.Cast<Message>().ToList();
}
Here's my rather crude attempt at bypassing the Entity Framework/OData plumbing and using direct SQL. (Wouldn't it be great if Dapper is supported!) This one works well, and is faster than the nested SQL that EF produces. The handling of OData is hacky; I have not had time to investigate using OData to extract the values for UpdatedAt, skip, and top.
I'm only using this approach for one method that needs optimisation. This is the method that the Azure Mobile App client calls when doing a pull.
public IEnumerable<MyTable> GetAllMyTable()
{
var qryValues = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var updatedAtFilter = qryValues["$filter"];
var skip = qryValues["$skip"];
var top = qryValues["$top"];
if (updatedAtFilter != null)
{
var r = new Regex(#"^.+datetimeoffset'(?<time>.+)'.+$", RegexOptions.None);
var m = r.Match(updatedAtFilter);
if (m.Success)
{
var updatedAt = m.Groups["time"].Value.Replace("T", " ");
var sqlString = #"SELECT T0.*
FROM MyTable T0
WHERE T0.UpdatedAt >= #UpdatedAt
ORDER BY UpdatedAt, Id
OFFSET #Skip ROWS
FETCH NEXT #Top ROWS ONLY";
var updatedAtParam = new SqlParameter("UpdatedAt", SqlDbType.DateTimeOffset);
updatedAtParam.Value = updatedAt;
var skipParam = new SqlParameter("Skip", SqlDbType.Int);
skipParam.Value = int.Parse(skip);
var topParam = new SqlParameter("Top", SqlDbType.Int);
topParam.Value = int.Parse(top);
var data = _context.Database.SqlQuery<MyTable>(sqlString, new object[] { updatedAtParam, skipParam, topParam }).AsEnumerable<MyTable>();
return data;
}
}
return null;
}

Returning multiple resultsets with EntityFramework repository

I am working on a code where I need Multiple tables as result of a stored procedure. I am using Entity Framework repository pattern. It returns and bind an IEnumerable object, but I need to bind it with multiple IEnumerables at the same time.
Can anybody help?
This is the code I am using :
db.Database.SqlQuery("procReturnsMultipleResuiltSets")
the ways to achieve your goal are disclosed in this article.
From related article the most common way is:
using (var db = new BloggingContext())
{
// If using Code First we need to make sure the model is built before we open the connection
// This isn't required for models created with the EF Designer
db.Database.Initialize(force: false);
// Create a SQL command to execute the sproc
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";
try
{
db.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
// Read Blogs from the first result set
var blogs = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
foreach (var item in blogs)
{
Console.WriteLine(item.Name);
}
// Move to second result set and read Posts
reader.NextResult();
var posts = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Post>(reader, "Posts", MergeOption.AppendOnly);
foreach (var item in posts)
{
Console.WriteLine(item.Title);
}
}
finally
{
db.Database.Connection.Close();
}
}
please note the important remark: The first result set must be consumed before moving to the next result set.

How to insert data to SQL Server DB using EF in MVC 2?

How do insert data to a SQl Server 2008 database server using Entity Framework from a user input on a web application.
I added a new EF connection o my model. I am able to see all the mappings correctly.
I currently have the following in my view:
m.PhoneNumber) %>
and in my controller
public ActionResult DialTone(Models.TelephoneModels telephone)
{
List<string> callType = new List<string>();
callType.Add("Standard");
callType.Add("Emergency");
ViewData["TypeOfCalls"] = new SelectList(callType);
if (!ModelState.IsValid)
{
return View("DialTone", telephone);
}
ViewData["Number"] = "You are currently connected to: " + telephone.PhoneNumber;
using (LogEntities db = new LogEntities())
{
var log = db.Logs.Single(m => m.PhoneNumber == telephone.PhoneNumber);
TryUpdateModel(log);
db.SaveChanges();
}
return View();
I want to log all phone numbers (or strings) that the user inputs in the TextBox - phonenNumber into the database.
I created a LOG database table with a column called "PhoneNumber". I cant seem to figure out how to insert the data into the table.
Any ideas?
Thanks!
:)
I figure it out. I used:
using (LogEntities db = new LogEntities())
{
Random random = new Random();
Log p = new Log();
p.ID = "400"; //Just a test
p.PhoneNumber = telephone.PhoneNumber;
db.AddToLogs(p);
db.SaveChanges();
}