Is this a good way to set the transaction level to serializable? If I set a lower transaction level, I will get duplicate orders. What is better approach? I assume this method will be invoked frequently by different users.
Here is my code
using (var tran = context.Database.BeginTransaction(IsolationLevel.Serializable))
{
try
{
var lastDocument = context.Documents.OrderByDescending(x => x.Id).FirstOrDefault();
int order = 1;
if (lastDocument != null)
{
order = lastDocument.Order + 1;
}
var document = new Document
{
CreatedDate = DateTimeOffset.UtcNow,
Name = Guid.NewGuid().ToString(),
Order = order
};
context.Documents.Add(document);
context.SaveChanges();
tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
}
}
Related
What does the Rollback method in EF Core do? If I didn't use Commit, I don't need it anyway. If I used Commit, the transaction has already been completed.
using (var context = new AppDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
var myObjectOne = new MyObjectOne() { Name = "Book" };
context.MyObjectOnes.Add(myObjectOne);
context.SaveChanges();
var myVal = myObjectOne.Id * 3.14;
var myObjectTwo = new MyObjectTwo() { Name = "Notebook", Price = 100, ReferenceId = myVal };
context.MyObjectTwos.Add(myObjectTwo);
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
}
What does the RollBack Method do? C# EF Core.
For example I am adding the peoples data to database per state (this is not what I am doing exactly but the model is same). We have list of states and each state has millions of people. So initially in code, I am saving the state to get the State ID and then use that ID to bulk insert peoples data.
If something goes wrong while adding the peoples data, let's say 20th million record threw some exception, is there a way to revert back the data already saved in both Peoples and State table?
Any suggestion is highly appreciated..
List <Peoples> PeopleList = new List<Peoples>();
int peopleCounter = 0;
foreach (var stateVal in States)
{
using (var context = new StateEntities())
{
State st = new State();
st.ID = stateVal.ID;
st.Name = stateVal.Name;
context.State.Add(st);
context.SaveChanges();
if (stateVal.Peoples != null )
{
foreach (var _p in stateVal.Peoples)
{
Peoples _people = new Peoples();
_people.Name = _p.Name;
_people.Age = _P.Age;
_people.State_ID = stateVal.ID; // Getting state ID from State object as it already saved to DB
PeopleList.Add(_people)
peopleCounter++;
if (peopleCounter == 100000)
{
InsertPeople(PeopleList, context); // does bulk insert when PeopleList reaches 100k
PeopleList.Clear();
peopleCounter = 0;
}
}
}
}
}
private static void InsertPeople(List<Peoples> PeopleList, StateEntities context)
{
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, new System.TimeSpan(0, 30, 0)))
{
context.BulkInsert(PeopleList, options => options.BatchTimeout = 0);
context.SaveChanges();
transactionScope.Complete();
}
}
You can use transaction of SQL to rollback. It's supported by EF.
using (var context = new SchoolContext())
{
using (DbContextTransaction transaction = context.Database.BeginTransaction())
{
try
{
//**TODO: Do your bulk insert codes here**
// Save changes data in context
context.SaveChanges();
// Commit changes
transaction.Commit();
}
catch (Exception ex)
{
// Rollback all changes
transaction.Rollback();
}
}
}
Ref: https://learn.microsoft.com/en-us/ef/core/saving/transactions
In my EF code and SQL Server, I try to insert a user data while this user is not exist, but EF inserted it 1000 times, just 1 time in SQL Server.
EF code:
static void Main(string[] args)
{
using (var db = new MyDbContext())
{
for (var i = 0; i < 1000; i++)
{
var count = db.Users.Count(f => f.Name == "Test");
if (count > 0) continue;
db.Users.Add(new User
{
Name = "Test",
Gender = "Male",
Phone = "1111111111",
CreateTime = DateTime.Now
});
}
try
{
db.SaveChanges();
//1000 rows effected
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Console.ReadKey();
}
T-SQL code:
declare #i int = 0
begin tran
while(#i < 1000)
begin
if not exists (select 1 from [dbo].[User] where Name = 'Test')
insert into [dbo].[User] values('Test','Male','1111111111',getdate())
set #i = #i + 1
end
if(##ERROR > 0)
rollback tran
else
commit tran
Even if I use a transaction wrap the EF code (but call SaveChanges once), the result is same as 1000 rows affected.
But when I use this code, the result is the same as in SQL Server:
using (var db = new MyDbContext())
{
using (var trans = db.Database.BeginTransaction(ReadCommitted))
{
for (var i = 0; i < 1000; i++)
{
var count = db.Users.Count(f => f.Name == "Test");
if (count > 0) continue;
db.Users.Add(new User
{
Name = "Test",
Gender = "Male",
Phone = "1111111111",
CreateTime = DateTime.Now
});
//1 row effected as sql does
db.SaveChanges();
}
try
{
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
Console.WriteLine(ex.Message);
}
}
}
Please tell me why transaction is different between EF and SQL
db.Users.Add does not insert. It makes for insertion when SaveChanges is called (what did you think SaveChanges did or why it was required if not for writing out changes that were previously not written?). That's why db.Users.Count(f => f.Name == "Test") always returns zero.
This has nothing to do with transactions.
(Btw, you probably should use Any instead of Count here).
I have an web application where I parse a csv file that can have over 200k records in it. I parse each line for information, verify that the key does not exist in the database and then add it to the context. When the count reaches 10,000 records it calls SaveChanges routine. The problem is that there can be duplicates in the context and it errors out. This is running on a Azure VM communicating to an Azure SQL server.
Two questions, how do I handle the duplicate issue and is there any way I can improve the speed as it takes several hours to run?
using (LoanFileEntities db = new LoanFileEntities())
{
db.Configuration.AutoDetectChangesEnabled = false; // 1. this is a huge time saver
db.Configuration.ValidateOnSaveEnabled = false; // 2. this can also save time
while (parser.Read())
{
counter++;
int loan_code = 0;
string loan_code_string = parser["LoanId"];
string dateToParse = parser["PullDate"].Trim();
DateTime date_pulled;
try
{
date_pulled = DateTime.Parse(dateToParse, CultureInfo.InvariantCulture);
}
catch (Exception)
{
throw new Exception("No Pull Date for line " + counter);
}
string originationdate = parser["OriginationDate"].Trim();
DateTime date_originated;
try
{
date_originated = DateTime.Parse(originationdate, CultureInfo.InvariantCulture);
}
catch (Exception)
{
throw new Exception("No Origination Date for line " + counter);
}
dateToParse = parser["DueDate"].Trim();
DateTime date_due;
try
{
date_due = DateTime.Parse(dateToParse, CultureInfo.InvariantCulture);
}
catch (Exception)
{
throw new Exception("No Due Date for line " + counter);
}
string region = parser["Region"].Trim();
string source = parser["Channel"].Trim();
string password = parser["FilePass"].Trim();
decimal principalAmt = Convert.ToDecimal(parser["Principal"].Trim());
decimal totalDue = Convert.ToDecimal(parser["TotalDue"].Trim());
string vitaLoanId = parser["VitaLoanId"];
var toAdd =
db.dfc_LoanRecords.Any(
x => x.loan_code_string == loan_code_string);
if (!toAdd)
{
dfc_LoanRecords loan = new dfc_LoanRecords();
loan.loan_code = loan_code;
loan.loan_code_string = loan_code_string;
loan.loan_principal_amt = principalAmt;
loan.loan_due_date = date_due;
loan.date_pulled = date_pulled;
loan.date_originated = date_originated;
loan.region = region;
loan.source = source;
loan.password = password;
loan.loan_amt_due = totalDue;
loan.vitaLoanId = vitaLoanId;
loan.load_file = fileName;
loan.load_date = DateTime.Now;
switch (loan.region)
{
case "UK":
if (location.Equals("UK"))
{
//db.dfc_LoanRecords.Add(loan);
if (loan.source == "Online")
{
counter_new_uk_online++;
}
else
{
counter_new_uk_retail++;
}
}
break;
case "US":
if (location.Equals("US"))
{
db.dfc_LoanRecords.Add(loan);
if (loan.source == "Online")
{
counter_new_us_online++;
}
else
{
counter_new_us_retail++;
}
}
break;
case "Canada":
if (location.Equals("US"))
{
db.dfc_LoanRecords.Add(loan);
if (loan.source == "Online")
{
counter_new_cn_online++;
}
else
{
counter_new_cn_retail++;
}
}
break;
}
// delay save to speed up load. 3. also saves transactional time
if (counter % 10000 == 0)
{
db.SaveChanges();
}
}
} // end of parser read
db.SaveChanges();
}
}
}
I would suggest removing duplicates in the code before sending it over to .SaveChanges().
Instead of going into detail about duplicate removal, I've put together this list of links to existing questions and answers on StackOverflow that may help:
Delete duplicates using Lambda
Using DISTINCT on a subquery to remove duplicates in Entity Framework
Using LINQ to find / delete duplicates
Hope that helps!
My code to use the function for updating is here and it works also
[HttpPost]
public bool SaveDefCompny(DefCompanyDTO DefCmpny)
{
using (RPDBEntities db = new RPDBEntities())
{
using (TransactionScope trans = new TransactionScope())
{
//the problem is here incase of saving
var UpdateDefCmpnyId = (from CmpnyId in db.DefCompanies
where CmpnyId.Id == DefCmpny.Id
select CmpnyId).First();
List<DefCompany> list = new List<DefCompany>();
list.Add(UpdateDefCmpnyId);
try
{
foreach (DefCompany DefCmpny1 in list)
{
DefCmpny1.Id = DefCmpny1.Id;
DefCmpny1.ShortName = DefCmpny.ShortName;
DefCmpny1.FullName = DefCmpny.FullName;
DefCmpny1.ContactPerson = DefCmpny.ContactPerson;
DefCmpny1.Address1 = DefCmpny.Address1;
DefCmpny1.CompanyCity = DefCmpny.CompanyCity;
DefCmpny1.CompanyState = DefCmpny.CompanyState;
DefCmpny1.CompanyCountry = DefCmpny.CompanyCountry;
DefCmpny1.ZipPostCode = DefCmpny.ZipPostCode;
DefCmpny1.TelArea = DefCmpny.TelArea;
DefCmpny1.CurrentCurrencyCode = DefCmpny.CurrentCurrencyCode;
db.SaveChanges();
trans.Complete();
}
}
catch (Exception ex)
{
}
}
return false;
}
}
when I try to save instead of updating the line of code
var UpdateDefCmpnyId = (from CmpnyId in db.DefCompanies
where CmpnyId.Id == DefCmpny.Id
select CmpnyId).First();
gives null value and hence saving fails because record is new and not present in database so how to handle null in case of saving how to use try catch so that when value is null it proceed to saving code that add
How about something along these lines:
var UpdateDefCmpnyId = (from CmpnyId in db.DefCompanies
where CmpnyId.Id == DefCmpny.Id
select CmpnyId).FirstOrDefault();
if(UpdateDefCmpnyId == null)
{
//insert
//(handle the id however you need to for insert. depending on your setup, you might be able to leave it empty and let the database put it in for you)
}
else
{
//update
//set the id as you do in the question
}