How to avoid adding duplicate data from CSV file to SQL Server database. Using CSVHelper and C# Blazor - sql-server

I have my database table named 'JobInfos' in SQL Server which contains many columns.
JobID - (int) auto populates incrementing value when data added
OrgCode - (string)
OrderNumber - (int)
WorkOrder - (int)
Customer - (string)
BaseModelItem - (string)
OrdQty - (int)
PromiseDate - (string)
LineType -(string)
This table gets written to many times a day using a Blazor application with Entity Framework and CSVHelper. This works perfectly. All rows from the CSV file are added to the database.
if (fileExist)
{
using (var reader = new StreamReader(#path))
using (var csv = new CsvReader(reader, config))
{
var records = csv.GetRecords<CsvRow>().Select(row => new JobInfo()
{
OrgCode = row.OrgCode,
OrderNumber = row.OrderNumber,
WorkOrder = row.WorkOrder,
Customer = row.Customer,
BaseModelItem = row.BaseModelItem,
OrdQty = row.OrdQty,
PromiseDate = row.PromiseDate,
LineType = row.LineType,
});
using (var db = new ApplicationDbContext())
{
while (!reader.EndOfStream)
{
if (lineNumber != 0)
{
db.AddRange(records.ToList());
db.SaveChanges();
}
lineNumber++;
}
NavigationManager.NavigateTo("/", true);
}
}
As these multiple CSV files can contain rows that may already be in the database table, I am getting duplicate records when the table is read from, which causes the users to delete all the newer duplicate rows manually to only keep the original entry.
I have no control over the CSV files or their creation. I am trying to only add rows that contain new data based on the WorkOrder number which can not be the same as any others.
I found another post here on StackOverflow which helps but I am stuck with a remaining error I can't figure out.
The Helpful post
I changed my code here...
if (lineNumber != 0)
{
var recordworkorder = records.Select(x => x.WorkOrder).ToList();
var workordersindb = db.JobInfos.Where(x => recordworkorder.Contains(x.WorkOrder)).ToList();
var workordersNotindb = records.Where(x => !workordersindb.Contains(x.WorkOrder));
db.AddRange(records.ToList(workordersNotindb));
db.SaveChanges();
}
but this line...
var workordersNotindb = records.Where(x => !workordersindb.Contains(x.WorkOrder));`
throws an error at the end (x.WorkOrder) - CS1503 Argument 1: cannot convert from 'int' to 'DepotQ4.Data.JobInfo'
WorkOrder is an int
JobID is the Primary Key and an int
Every record in the table must have a unique WorkOrder
I am not sure what I am not seeing. Could use some help here please?

Your variable workordersindb is a List<JobInfo>. So when you try to select from records.Where(x => !workordersindb.Contains(x.WorkOrder)) you are trying to match the list of JobInfo in workordersindb to the int of x.WorkOrder. workordersindb needs to be a List<int> in order to be able to use it with the Contains. records would have had the same issue, but you solved it by creating the variable recordworkorder and using records.Select(x => x.WorkOrder) to get a List<int>.
if (lineNumber != 0)
{
var recordworkorder = records.Select(x => x.WorkOrder).ToList();
var workordersindb = db.JobInfos.Where(x => recordworkorder.Contains(x.WorkOrder)).Select(x => x.WorkOrder).ToList();
var workordersNotindb = records.Where(x => !workordersindb.Contains(x.WorkOrder));
db.JobInfos.AddRange(workordersNotindb);
db.SaveChanges();
}

Related

Problem when inserting two consecutive lines to the database

I have this function and it is working perfectly
public DemandeConge Creat(DemandeConge DemandeConge)
{
try
{
var _db = Context;
int numero = 0;
//??CompanyStatique
var session = _httpContextAccessor.HttpContext.User.Claims.ToList();
int currentCompanyId = int.Parse(session[2].Value);
numero = _db.DemandeConge.AsEnumerable()
.Where(t => t.companyID == currentCompanyId)
.Select(p => Convert.ToInt32(p.NumeroDemande))
.DefaultIfEmpty(0)
.Max();
numero++;
DemandeConge.NumeroDemande = numero.ToString();
//_db.Entry(DemandeConge).State = EntityState.Added;
_db.DemandeConge.Add(DemandeConge);
_db.SaveChanges();
return DemandeConge;
}
catch (Exception e)
{
return null;
}
}
But just when i try to insert another leave demand directly after inserting one (without waiting or refreshing the page )
An error appears saying that this new demand.id exists
I think that i need to add refresh after saving changes?
Any help and thanks
Code like this:
numero = _db.DemandeConge.AsEnumerable()
.Where(t => t.companyID == currentCompanyId)
.Select(p => Convert.ToInt32(p.NumeroDemande))
.DefaultIfEmpty(0)
.Max();
numero++;
Is a very poor pattern. You should leave the generation of your "numero" (ID) up to the database via an Identity column. Set this up in your DB (if DB First) and set up your mapping for this column as DatabaseGenerated.Identity.
However, your code raises lots of questions.. Why is it a String instead of an Int? This will be a bugbear for using an identity column.
The reason you will want to avoid code like this is because each request will want to query the database to get the "max" ID, as soon as you get two requests running relatively simultaneously you will get 2 requests that say the max ID is "100" before either can reserve and insert 101, so both try to insert 101. By using Identity columns the database will get 2x inserts and give them an ID first-come-first-serve. EF can manage associating FKs around these new IDs automatically for you when you set up navigation properties for the relations. (Rather than trying to set FKs manually which is the typical culprit for developers trying to fetch a new ID app-side)
If you're stuck using an existing schema where the PK is a combination of company ID and this Numero column as a string then about all you can do is implement a retry strategy to account for duplicates:
const int MAXRETRIES = 5;
var session = _httpContextAccessor.HttpContext.User.Claims.ToList();
int currentCompanyId = int.Parse(session[2].Value);
int insertAttemptCount = 0;
while(insertAttempt < MAXRETRIES)
{
try
{
numero = Context.DemandeConge
.Where(t => t.companyID == currentCompanyId)
.Select(p => Convert.ToInt32(p.NumeroDemande))
.DefaultIfEmpty(0)
.Max() + 1;
DemandeConge.NumeroDemande = numero.ToString();
Context.DemandeConge.Add(DemandeConge);
Context.SaveChanges();
break;
}
catch (UpdateException)
{
insertAttemptCount++;
if (insertAttemptCount >= MAXRETRIES)
throw; // Could not insert, throw and handle exception rather than return #null.
}
}
return DemandeConge;
Even this won't be fool proof and can result in failures under load, plus it is a lot of code to work around a poor DB design so my first recommendation would be to fix the schema because coding like this is prone to errors and brittle.

Update dynamically changing number of records in SQL Server database using a single connection

I am using mssql node module to query a SQL Server database.
var update = {"id":"1","status":"active"};
sql.connect(config.then(pool => {
return pool.request()
.query(query)
}).then(result => {
console.dir(result)
if(result)
pool.close();
}).catch(err => {
console.log(err);
})
But now, I need to update multiple records, the number of records to update changes dynamically by opening only one connection. In the below example, there are 2 devices that needs to be updated. The number of update records change depending on the request.
update =[{"id":"1","status":"active"},{"id":"1","status":"active"}]
var update = req.body.update;
var len = update.length;
for (i = 0; i < update.length; i++) {
var query = "UPDATE faults SET current_status = "+ status +" WHERE id = " + update[i].id ;
}
update();
From my understanding there is not multiple statement execution option like in MySql in SQL Server.
Can someone help me with the approach here?
I used connection.execSqlBatch(request) to achieve this.
Tedious

Cakephp2. save data is adding two more rows

public function index($tin=null) {
$this->layout = 'index_1';
$this->loadModel('Insurance');
$this->loadModel('Verification');
$this->loadModel('Insurance_price');
$date = date('Y-m-d');
$verification_fee=$this->Insurance_price->findByInsurance_type("Third Party Auto Insurance");
$ver = $verification_fee['Insurance_price']['verification_amount'];
if(!empty($tin)) {
$this->request->data['tin'] = $tin;
$this->request->data['amount'] = $ver;
$this->request->data['date'] = date('Y-m-d');
$this->Verification->save($this->request->data);
$this->Verification->clear($this->request->data);
}
}
That's my controller. I am trying to save those data once the page loads.
But when I check the database table instead of seeing only 1 new record I see 3 new rows with the same data as 'amount' and 'date', except for tin column which will have 'image'.
What could I be doing wrong?
try inserting
$this->Verification->create();
before
$this->Verification->save($this->request->data);
And check

can't get EnumScript() to generate constraints

I'm trying to get programmatically what I can get manually from SSMS using Tasks > Generate Scripts
The code below works fine, EXCEPT it doesn't generate any constraints. I don't get any ALTER TABLE [foo] ADD CONSTRAINT ... ON DELETE CASCADE etc etc. I've tried a lot of combinations of Dri options and on different databases as well. I'm stumped.
Thanks for insight!
Scripter scrp = new Scripter(srv)
{
Options =
{
ScriptDrops = false,
WithDependencies = false,
Indexes = true,
Triggers = false,
Default = true,
DriAll = true,
//ScriptData = true,
ScriptSchema = true,
}
};
var urns = new List<Urn>();
foreach (Table tb in db.Tables)
{
if (tb.IsSystemObject == false)
{
urns.Add(tb.Urn);
}
}
var inserts = scrp.EnumScript(urns.ToArray());
File.WriteAllLines(path, inserts);
Well, I found a solution, which is to use the Script method of each object to produce the schema and the EnumScript method (with scriptSchema=false) to produce the inserts for the table content.
foreach (Table tb in db.Tables)
{
if (tb.IsSystemObject == false)
{
foreach (var s in tb.Script(schemaOptions))
strings.Add(s);
if (scriptData)
{
foreach (var i in tb.EnumScript(insertOptions))
strings.Add(i);
}
}
}
I confess this solution feels a bit hollow because I never found out why the original method didn't work. It's a Repair without a Diagnosis, but a repair nonetheless.
As to why I wrote this thing in the first place, my database is on a shared server and there isn't any way to get an automated backup that I could use offline or somewhere else. So this is my backup scheme.
The solution above follows the code example given by Microsoft here: Scripting . The problem with this approach is the tables are scripted in No Particular Order, but need to be in order of their dependencies in order for the constraints to be defined and for rows to be inserted. Can't reference a foreign key in a table that doesn't exist yet.
The best solution I have so far is to use DependencyWalker.DiscoverDependencies() to get a
dependency tree, DependencyWalker.WalkDependencies() to get a linear list and iterate over that list, as follows:
var urns = new List<Urn>();
Scripter schemaScripter = new Scripter(srv) { Options = schemaOptions };
Scripter insertScripter = new Scripter(srv) { Options = insertOptions };
var dw = new DependencyWalker(srv);
foreach (Table t in db.Tables)
if (t.IsSystemObject == false)
urns.Add(t.Urn);
DependencyTree dTree = dw.DiscoverDependencies(urns.ToArray(), true);
DependencyCollection dColl = dw.WalkDependencies(dTree);
foreach (var d in dColl)
{
foreach (var s in schemaScripter.Script(new Urn[] { d.Urn }))
strings.Add(s);
strings.Add("GO");
if (scriptData)
{
int n = 0;
foreach (var i in insertScripter.EnumScript(new Urn[] {d.Urn}))
{
strings.Add(i);
if ((++n) % 100 == 0)
strings.Add("GO");
}
}
}
...
File.WriteAllLines(path, strings);
Adding a "GO" every so often keeps the batch size small so SSMS doesn't run out of memory.
To complete the example, the database gets scripted thus:
foreach (var s in db.Script(new ScriptingOptions { ScriptSchema = true }))
strings.Add(s);
strings.Add("GO");
strings.Add("use " + dbName);
strings.Add("GO");
Users, views, stored procedures are scripted thus:
foreach (User u in db.Users)
{
if (u.IsSystemObject == false)
{
foreach (var s in u.Script(new ScriptingOptions { ScriptSchema = true }))
strings.Add(s);
}
}
The file produced by this code can be used to recreate the database. I have it set up on an old laptop to pull a snapshot of my online database every hour. Poor man's log shipping / backups / mirroring.

Audit of what records a given user can see in SalesForce.com

I am trying to determine a way to audit which records a given user can see by;
Object Type
Record Type
Count of records
Ideally would also be able to see which fields for each object/record type the user can see.
We will need to repeat this often and for different users and in different orgs, so would like to avoid manually determining this.
My first thought was to create an app using the partner WSDL, but would like to ask if there are any easier approaches or perhaps existing solutions.
Thanks all
I think that you can follow the documentation to solve it, using a query similar to this one:
SELECT RecordId
FROM UserRecordAccess
WHERE UserId = [single ID]
AND RecordId = [single ID] //or Record IN [list of IDs]
AND HasReadAccess = true
The following query returns the records for which a queried user has
read access to.
In addition, you should add limit 1 and get from record metadata the object type,record type, and so on.
I ended up using the below (C# using the Partner WSDL) to get an idea of what kinds of objects the user had visibility into.
Just a quick'n'dirty utility for my own use (read - not prod code);
var service = new SforceService();
var result = service.login("UserName", "Password");
service.Url = result.serverUrl;
service.SessionHeaderValue = new SessionHeader { sessionId = result.sessionId };
var queryResult = service.describeGlobal();
int total = queryResult.sobjects.Count();
int batcheSize = 100;
var batches = Math.Ceiling(total / (double)batcheSize);
using (var output = new StreamWriter(#"C:\test\sfdcAccess.txt", false))
{
for (int batch = 0; batch < batches; batch++)
{
var toQuery =
queryResult.sobjects.Skip(batch * batcheSize).Take(batcheSize).Select(x => x.name).ToArray();
var batchResult = service.describeSObjects(toQuery);
foreach (var x in batchResult)
{
if (!x.queryable)
{
Console.WriteLine("{0} is not queryable", x.name);
continue;
}
var test = service.query(string.Format("SELECT Id FROM {0} limit 100", x.name));
if(test == null || test.records == null)
{
Console.WriteLine("{0}:null records", x.name);
continue;
}
foreach (var record in test.records)
{
output.WriteLine("{0}\t{1}",x.name, record.Id);
}
Console.WriteLine("{0}:\t{1} records(0)", x.name, test.size);
}
}
output.Flush();
}

Resources