I am not an expert on databases and SQL. I am developing a dotnet core application using the Entity Framework to access a (postgresql) database. I have the following tables with the following columns:
Table: Log Columns: Timestamp: timestamp without time zone Value: smallint Status: smallint Table: Status ID: smallint Name: character varying(30)
When I insert a record/row in the Log table, I want to look up the ID belonging to the Name in the Status table and fill in the value in the Status column in the Log table. If the Name is not found, I want to insert -1 in the Status column. I have implemented this as:
Status status = null; try { status = context.Status.Single(a => a.Name == statusName); } catch {} var entry = new Log { Timestamp = timestamp, Value = value, Status = (status != null) ? status.Id : Convert.ToInt16(-1) }; context.Log.Add(entry); context.SaveChanges();
This works. I think it can be implemented more efficient. Also there are some issues with this implementation, I believe. For example when I temporary stop the database, the query for the status id fails and an insert with -1 is performed. When the database is restarted, the inserts are still performed (I think the Entity Framework cashes the actions), but with a -1 and not the correct value. When the insert can be executed as a single statement, I expect/hope that the right values will be inserted. Therefor I would like to know a few things. These are my actual questions:
- How can I achieve this using an SQL statement (on the database administration tool)?
- How can I implement it, using the Entity Framework, such that a single SQL statement is executed on the database (and thereby inserting the correct data when the database is temporary shut down)?
Advertisement
Answer
I did find a way to make one sql query to perform the lookup and execute the insert. I (only) managed to implement it using ExecuteSqlRaw
.
string sqlText = "INSERT INTO "Log" ("Timestamp", "Value", "Status") " + "SELECT {0}, {1}, COALESCE("Stat"."ID", -1) " + "FROM "Status" AS "Stat" " + "RIGHT JOIN (SELECT 'xxx' as CONSTANT) AS "Dummy" " + "ON "Stat"."Name" = {2}"; int rows = 0; try { rows = context.Database.ExecuteSqlRaw(sqlText, timestamp, value, status); if (rows != 1) { Console.WriteLine("Error writing status log to database: rows returned is {0}", rows); } } catch (Exception ex) { Console.WriteLine("Error writing status log to database: {0}", ex.Message); }
Although this works, I went for another solution. Since the Status table does not hold a large list, I load the table into a dictionary in memory and lookup the ID in there for every record that has to be added.
statusValues = context.Status.ToDictionary(kvp => kvp.Name, kvp => kvp.Id);