Getting Id during sql query and using for another Insert Statment - c#

So I have been creating a library that uses dapper and allows user to manipulate a database.
I need some help with finding the best way to achieve the following.
Lets say I have an "order" table and I have a "transaction" table and an "order_line" table.
I want to take the Increment Id of table "order" when inserting and use it to store it in a column in "transaction" and "order_line" table and I want all of this done in a SQL transaction so that I can roll back in case of any issue.
Now since my library is dynamic to any type and action, I am not sure on how to approach something like this.
Here is the code on how you would insert:
I have 2 global variables
private string connectionString { get; set; }
public void newConnection(string connection)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
connectionString = connection;
}
}
private List<KeyValuePair<string, object>> transactions = new List<KeyValuePair<string, object>>();
Here is how you call to have a class to be saved to the database:
public void Add(object item)
{
string propertyNames = "";
string propertyParamaters = "";
Type itemType = item.GetType();
System.Reflection.PropertyInfo[] properties = itemType.GetProperties();
for (int I = 0; I < properties.Count(); I++)
{
if (properties[I].Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase) || properties[I].Name.Equals("AutoId", StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
if (I == properties.Count() - 1)
{
propertyNames += "[" + properties[I].Name + "]";
propertyParamaters += "#" + properties[I].Name;
}
else
{
propertyNames += "[" + properties[I].Name + "],";
propertyParamaters += "#" + properties[I].Name + ",";
}
}
string itemName = itemType.Name;
KeyValuePair<string, object> command = new KeyValuePair<string, object>($"Insert Into[{ itemName}] ({ propertyNames}) Values({ propertyParamaters})", item);
transactions.Add(command);
}
There are more methods and like edit, remove, edit list, remove list etc. but are not relevant in this case.
When you want to commit changes to the database you call:
public void SaveChanges()
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
{
try
{
foreach (KeyValuePair<string, object> command in transactions)
{
sqlConnection.Execute(command.Key, command.Value, sqlTransaction);
}
sqlTransaction.Commit();
}
catch
{
sqlTransaction.Rollback();
throw;
}
finally
{
sqlConnection.Close();
transactions.Clear();
}
}
sqlConnection.Close();
}
transactions.Clear();
}
You can find my library at github.com
https://github.com/pietercdevries/Bamboo.Net

Can it be done... yes... should we be trying to do this ourselves... I wouldn't :) but lets try it any way.
Some ideas that can make this code simpler:
Define helper interfaces and force the data classes to implement them or use attribute declarations to specify id fields and foreign key references
Investigate Injection or code generation techniques so that you can get some of this 'dynamic' coding and lookup executed at compile time, not runtime.
I don't use Dapper and your SqlConnection.Execute() is an extension method I am not familiar with but I assume that it generates DbParameters from the passed in object and applies them to the SqlCommand when it gets executed. Hopefully dapper has some functions to extract the parameters, so that they can be used in this code example, or perhaps you can use some of these concepts and adapt them to your dapper code. I just want to acknowledge that upfront and that I have omitted any code example here that parameterises the objects when executing the commands.
This is the journey that the following snippets will go down
Prepare the generated SQL to capture the Id field
Output the Id value when we save changes
Iterate over all remaining objects in the array and set the foreign key values
Note: these code changes are not tested or exception handled for production, nor would I call this "best practice" its just to prove the concept and help out a fellow coder :)
You have already devised a convention for Id field tracking, lets extend that idea by preparing the sql statement to set the value of an output parameter:
NOTE: in MS SQL, please use SCOPE_IDENTITY() in preference to ##Identity.
What is the difference between Scope_Identity(), Identity(), ##Identity, and Ident_Current?
NOTE: because the generated statements are using parameters, and we are not yet reading the parameter values, we will not need to regenerate the saved SQL statements later after we have found an Id value to insert to other objects... phew...
public void Add(object item)
{
List<string> propertyNames = new List<string>();
Type itemType = item.GetType();
System.Reflection.PropertyInfo[] properties = itemType.GetProperties();
for (int I = 0; I < properties.Count(); I++)
{
if (properties[I].Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase) || properties[I].Name.Equals("AutoId", StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
propertyNames.Add(properties[I].Name);
}
string itemName = itemType.Name;
KeyValuePair<string, object> command = new KeyValuePair<string, object>
($"Insert Into[{itemName}] ({String.Join(",", propertyNames.Select(p => $"[{p}]"))}) Values({String.Join(",", propertyNames.Select(p => $"#{p}"))}); SET #OutId = SCOPE_IDENTITY();", item);
transactions.Add(command);
// Simply append your statement with a set command on an #id parameter we will add in SaveChanges()
}
In Save Changes, implement output parameter to capture the created Id, and if the Id was captured, save it back into the object that the command is associated to.
NOTE: this code snippet shows the references to the solution in item 3.
And the foreach was replaced with a for so we could do forward iterations from the current index
public void SaveChanges()
{
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
{
try
{
for (int i = 0; i < transactions.Count; i++)
{
KeyValuePair<string, object> command = transactions[i];
// 1. Execute the command, but use an output parameter to capture the generated id
var cmd = sqlConnection.CreateCommand();
cmd.Transaction = sqlTransaction;
cmd.CommandText = command.Key;
SqlParameter p = new SqlParameter()
{
ParameterName = "#OutId",
Size = 4,
Direction = ParameterDirection.Output
};
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
// Check if the value was set, non insert operations wil not set this parameter
// Could optimise by not preparing for the parameter at all if this is not an
// insert operation.
if (p.Value != DBNull.Value)
{
int idOut = (int)p.Value;
// 2. Stuff the value of Id back into the Id field.
string foreignKeyName = null;
SetIdValue(command.Value, idOut, out foreignKeyName);
// 3. Update foreign keys, but only in commands that we haven't execcuted yet
UpdateForeignKeys(foreignKeyName, idOut, transactions.Skip(i + 1));
}
}
sqlTransaction.Commit();
}
catch
{
sqlTransaction.Rollback();
throw;
}
finally
{
sqlConnection.Close();
transactions.Clear();
}
}
sqlConnection.Close();
}
transactions.Clear();
}
/// <summary>
/// Update the Id field of the specified object with the provided value
/// </summary>
/// <param name="item">Object that we want to set the Id for</param>
/// <param name="idValue">Value of the Id that we want to push into the item</param>
/// <param name="foreignKeyName">Name of the expected foreign key fields</param>
private void SetIdValue(object item, int idValue, out string foreignKeyName)
{
// NOTE: There are better ways of doing this, including using interfaces to define the key field expectations.
// This logic is consistant with existing code so that you are familiar with the concepts
Type itemType = item.GetType();
foreignKeyName = null;
System.Reflection.PropertyInfo[] properties = itemType.GetProperties();
for (int I = 0; I < properties.Count(); I++)
{
if (properties[I].Name.Equals("Id", StringComparison.CurrentCultureIgnoreCase) || properties[I].Name.Equals("AutoId", StringComparison.CurrentCultureIgnoreCase))
{
properties[I].SetValue(item, idValue);
foreignKeyName = $"{item.GetType().Name}_{properties[I].Name}";
break;
}
}
}
So now your objects have their Id's updated as they are inserted.
Now for the fun part... After updating the Id, you should now iterate through the other objects and update their foreign key fields.
How you go about this in reality depends a lot on what kind of assumptions/conventions you are ready enforce over the data that you are updating. For simplicity sake, lets say that all of the foreign keys that we need to update are named with the convention {ParentClassName}_{Id}.
That means that if in our example we just inserted a new 'Widget', then we can try to forcibly update all other objects in this transaction scope that have a field 'Widget_Id' (or 'Widget_AutoId')
private void UpdateForeignKeys(string foreignKeyName, int idValue, IEnumerable<KeyValuePair<string, object>> commands)
{
foreach(var command in commands)
{
Type itemType = command.Value.GetType();
var keyProp = itemType.GetProperty(foreignKeyName);
if(keyProp != null)
{
keyProp.SetValue(command.Value, idValue);
}
}
}
This is a very simplistic example of how you could go about updating foreign (or reference) keys in OPs data persistence library.
You have probably observed in reality that relational key fields are rarely consistently named using any convention, but even when conventions are followed, my simple convention would not support a table that had multiple references to parents of the same type, for example a Manifest in one of my client's apps has 3 links back to a user table:
public class Manifest
{
...
Driver_UserId { get; set; }
Sender_UserId { get; set; }
Receiver_UserId { get; set; }
...
}
You would need to evolve some pretty advanced logic to tackle all possible linkage combinations.
Some ORMs do this by setting the values as negative numbers, and decrementing the numbers each type a new type is added to the command collection. Then after an insert you only need to update key fields that held the faked negative number with the updated number. You still need to know which fields are key fields, but atleast you don't need to track the precise fields that form the ends of each relationship, we can track with the values.
I like how Entity Framework goes about it though, try inject this linkage information about the fields using attributes on the properties. You may have to invent your own, but it's a clean declarative concept that forces you to describe these relationships up front in the data model classes in a way that all sorts of logic can later take advantage of, not just for generating SQL statements.
I don't want tobe too critical of Dapper, but once you start to go down this path or manually managing referential integrity like this there is a point where you should consider a more enterprise ready ORM like Entity Framework or nHibernate. Sure they come with some baggage but those ORMs have really evolved into mature products that have been optimised by the community. I now have very little manually written or scripted code to customise any interactions with the RDBMS at all, which means much less code to test or maintain. (= less bugs)

It doesn't say which database you are using. If it is MSSQL you can do
var id = connection.Query<int?>("SELECT ##IDENTITY").SingleOrDefault();
after executing the Insert. That gives you the id of the last insert.

Related

How to change items in cache

Hello i want to change and alter values inside the cache of my acumatica cache i would like to know how to do it
for example i want to change the Ext. Cost value pro grammatically of the first line or the second line or can i check if there is already a "Data Backup" on transaction Descr.
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
if (Globalvar.GlobalBoolean == true)
{
PXCache cache = Base.Transactions.Cache;
APTran red = new APTran();
red.BranchID = Base.Transactions.Current.BranchID;
red.InventoryID = 10045;
var curyl = Convert.ToDecimal(Globalvar.Globalred);
red.CuryLineAmt = curyl * -1;
cache.Insert(red);
}
else
{
}
baseMethod();
}
this code add a new line on persist but if it save again it add the same line agaub u wabt ti check if there is already a inventoryID =10045; in the cache
thank you for your help
You can access your cache instance by using a view name or cache type. Ex: (Where 'Base' is the graph instance)
Base.Transactions.Cache
or
Base.Caches<APTran>().Cache
Using the cache instance you can loop the cached values using Cached, Inserted, Updated, or Deleted depending on which type of record you are looking for. You can also use GetStatus() on an object to find out if its inserted, updated, etc. Alternatively calling PXSelect will find the results in cache (PXSelectReadOnly will not).
So you could loop your results like so:
foreach (MyDac row in Base.Caches<MyDac>().Cache.Cached)
{
// logic
}
If you know the key values of the cache object you are looking for you can use Locate to find by key fields:
var row = (MyDac)Base.Transactions.Cache.Locate(new MyDac
{
MyKey1 = "",
MyKey2 = ""
// etc... must include each key field
});
As Mentioned before you can also just use a PXSelect statement to get the values.
Once you have the row to update the values you set the object properties and then call your cache Update(row) before the base persist and you are good to go. Similar if needing to Insert(row) or Delete(row).
So in your case you might end up with something like this in your persist:
foreach (APTran row in Base.Transactions.Cache.Cached)
{
if (Globalvar.GlobalBoolean != true || row.TranDesc == null || !row.TranDesc.Contains("Data Backup"))
{
continue;
}
//Found my row
var curyl = Convert.ToDecimal(Globalvar.Globalred);
row.CuryLineAmt = curyl * -1;
Base.Transactions.Update(row);
}

SSIS Script Component Input0Buffer method no GetName()?

I am looking for a way to obtain my property names in a SSIS data flow task Script Component. I have been searching high and low only finding this. I have been trying to get this code to work, but I am too novice to understand what is happening here and I don't feel it is explained very well(no offense).
The source before this component is using a SQL query joining two tables. Inside the component, I would like to compare column to column. Then call an update method I created to use SqlConnection to perform the update.
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
if (Row.TableALastName != Row.TableBLastName)
// Call the update method if the last name did not match.
this.UpdateRecord("TableBLastName", Row.TableALastName.ToString(), Row.TableAAssociateId.ToString());
}
}
private void UpdateRecord(string columnName, string change, string associateId)
{
SqlConnection sqlConnection;
sqlConnection = new SqlConnection(this.Variables.Connection);
string updateQuery = "UPDATE [SomeDataBase].[dbo].[TableB] SET " + columnName + " = " + change + " WHERE [Associate_ID] = " + associateId;
using (SqlCommand cmd2 = new SqlCommand(updateQuery, sqlConnection))
{
sqlConnection.Open();
cmd2.ExecuteNonQuery();
sqlConnection.Close();
}
}
I would like to somehow get the PropertyName of Row.TableBLastName instead of having to hard code "TableBLastName" for each test I am doing, which will be a lot.
The problem is that the input buffer class does not have Property.GetName() This also means I can't add a method to the class to get the property names, as it is regenerated each run.
public Input0_ProcessInputRow(Input0Buffer Row)
{
Dictionary<string, List<string>> list = new Dictionary<string, List<string>>();
List<string> propertyList = new List<string>();
Type myType = typeof(Input0Buffer);
PropertyInfo[] allPropInfo = myType.GetProperties();
List<PropertyInfo> SqlPropInfo = allPropInfo.Where(x => !x.Name.Contains("AM_")).ToList();
// Loop through all the Sql Property Info so those without AM_
for (int i = 0; i < SqlPropInfo.Count(); i++)
{
List<string> group = new List<string>();
foreach (var propInfo in allPropInfo)
{
if (propInfo.Name.Contains(SqlPropInfo[i].Name))
{
// Group the values based on the property
// ex. All last names are grouped.
group.Add(propInfo.GetValue(Row, null).ToString());
}
}
// The Key is the Sql's Property Name.
list.Add(SqlPropInfo[i].Name, group);
}
foreach (var item in list)
{
// Do a check if there are two values in both SQL and Oracle.
if (item.Value.Count >= 2)
{
if (item.Value.Count() != item.Value.Distinct().Count())
{
// Duplicates exist do nothing.
}
else
{
// The values are different so update the value[0]. which is the SQL Value.
UpdateRecord(item.Key, item.Value[0], Row.AssociateId);
}
}
}
}
I separated the values from the two tables so there are two lists values from TableA and TableB. You can prefix the values from TableA with "AM_" or something distinct so you can use reflection to to get the properties with and without the prefix and find out which values belong to which table. Then I just loop through the properties and group the values with the properties from the target value (so those without the prefix "AM_") I then loop through the grouped list and compare the two values and if it's different, update TableA with the TableB values to match them
You are already in SSIS so I will propose using that (no matter how quick I usually jump to C# to solve problems)
This is a classic conditional split scenario:
Do your test then run the results into a SQL Update statement.

Iterate over Database table row and store values in a Dictionary?

I want to implement a Dictionary cache in my program. How can I store the database result seen in the image below in a Dictionary Collection ?
I want to iterate over the Database table and store the content of LanguageName and IsoCode columns in a Dictionary like this Dictionary<LanguageName,IsoCode>.
My database (ctlang) looks like this:
Here is my code:
private string GetLanguageForIsoCode(string isoCode)
{
//check the isocode column and return the corresponding language
using (var unitOfWork = dataAccessUnitOfWorkFactory.Create())
{
//need to call every time the sql query
string query = "SELECT languagename FROM ctlang WHERE isocode='" + isoCode + "'";
List<string> result = unitOfWork.OwEntities.Database.SqlQuery<string>(query).ToList();
if (result.FirstOrDefault() != null)
{
return result.FirstOrDefault();
}
//if language not available in Database, fallback to German as default language
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("de");
//displayName = Deutsch
return CultureInfo.GetCultureInfo("de").NativeName;
}
}
Bonus question: How can I search for a key in a dictionary using the value ?
It is. There is no problem in returning a key by its value, the only issue would be that there could theoretically be more than one key assigned to that value, since the key is what matters. But in your particular case that should be no problem, since only one isocode represents one language. So there would be no problem to make it like that, with dictionary:
public Dictionary<string, string> languagesAndKeys = new Dictionary<string, string>(); //Create it
Then you can search for keys/values like that:
string myValueByKey = languagesAndKeys["myKey"]; //getting value by key is easy
string myKeyByValue = languagesAndKeys.FirstOrDefault(item => item.Value == "myValue").Key; //getting the key of the FIRST matching value/or returning the default type. You'll need a check to be sure.
Afterwards, you can easily load the data from the sql table into the dictionary. For this goal, you can either use a temporary datatable with dataadapter, which will work well as long as the table is not that big, or you can use a DataReader to loop trough rows in the sql table one by one. I'm gonna be using a temp DataTable:
string cmdText = "SELECT * FROM ctlang"; //As far as I saw your cmd text in the code example, you may still want to take a look tho
string connectionString = ""; //fill the connection string according to your SQL server data
SqlDataAdapter dataAdapter = new SqlDataAdapter(cmdText, connectionString);
DataTable dTable = new DataTable();
dataAdapter.Fill(dTable);
foreach(DataRow row in dTable.Rows)
languagesAndKeys.Add(row[1].ToString(), row[0].ToString());//second column as a key, first column as a value - just like the structure of your table.
This is how I solved the problem. In my Database the Table with the languages is named CTLANG and the columns are LANGUAGENAME and ISOCODE. I wanted to map these to into a Dictionary collection. So that at the end the dictionary looks like this: ["LanguageName","IsoCode"].
private static Dictionary<string, string> languageToIsoCode; //dictionary cache
private void InitializeLanguageCacheDictionary()
{
using (var unitOfWork = dataAccessUnitOfWorkFactory.Create())
{
languageToIsoCode = (from p in unitOfWork.OwEntities.CTLANG
select new {p.LANGUAGENAME, p.ISOCODE}).ToDictionary(p => p.LANGUAGENAME, p => p.ISOCODE);
}
}
Thanks to #D.Petrov I also found a way to search for the key in a dictionary and give its value back.
And this is how I optimized my method to use the dictionary cache.
private string GetLanguageForIsoCode(string isoCode)
{
if (languageToIsoCode == null)//if cache empty initialize it
{
InitializeLanguageCacheDictionary();
}
//searches inside the dictionary, look for value and then return key
//might be bad if there are more than one value asigned to a key
//because value does not habe to be unique
string languageFromIsoCodeFromCache = languageToIsoCode.FirstOrDefault(item => item.Value == isoCode).Key;
if (languageFromIsoCodeFromCache == null)
{
//fallback and use the German language as default
return CultureInfo.GetCultureInfo("de").NativeName;
}
return languageFromIsoCodeFromCache;
}

How to get the first child value using dapper (typically querying count(*) as the only result)

I want to implement some function like this:
public static string GetResult(string sql) {
// TODO:
// result = connection.Query(....);
// return result.FirstRow().FirstChild().ToString();
}
And call like this:
string myName = GetResult("SELECT userName from tb_Users WHERE ID = 1");
// or
int totalRows = Convert.ToInt32(GetResult("SELECT count(*) FROM tb_List"));
How can I implement TODO section using Dapper ?
Dapper has ExecuteScalar[<T>] which can be used if you are reading one column, one row, one grid. So:
var name = connection.ExecuteScalar<string>("select 'abc'");
int count = connection.ExecuteScalar<int>("select 123");
There is also Query{First|Single}[OrDefault][<T>] for all the usual "sort of one row, multiple columns" scenarios.
A word of caution on your API: anything that only accepts a string of sql (and no separate parameters) makes me very nervous that you are about to cause sql injection problems.

Inserting rows of database into XML/XSD class C#

Summary: I used xsd.exe to create a class off of an xml template. Let's suppose there's a class named human with two attributes: name(string) and id(int). My table, humans, has two columns named name and id. I'm trying to use SqlDataReader to read in the rows of my database and create a human object for each row. Is there a smarter way to insert each column into one of my attributes than this? Else, I would need many 'if' statements to check the name of each row and correlate it to an attribute.
XML Class
public partial class human
{
private string name;
private int id;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public int id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
** This is after a connect has been established to my database using SqlConnection
SqlCommand cmd = new SqlCommand("SELECT * FROM tablename");
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
//Iterating through the columns of a specific row
for (int i = 0; i < reader.FieldCount; i++)
{
human human1 = new human();
string currentColValue = reader[i].ToString();
string currentColName = reader.GetName(i);
if (currentColName == "name" ) { human.name = currentColValue; }
else { human.id = (int) currentColValue; } //Now you know it's the other attribute
//Now you insert the human into a larger class or array
}
}
As you can see, with a two column table it's relatively easy. But if I had a table with multiple columns, then it becomes way harder. Is there an easier way to do this?
It seems like NHibernate is the way to go, but the learning curve for it appears to be quite steep - would anyone mind giving me some sample code?
Instead of using a full fledge ORM (Object Relational Mapping) like NHibernate and Entity Framework, I decided to use PetoPoco. In my opinion, it was much easier to learn over NHibernate as it was a simple class file.
Instead of the solution taking n number of if statements for n columns, it took only a single lines of code!
IEnumerable<human> humanList = (new PetaPoco.Database(connectionstring))
.Fetch<human>("SELECT * FROM dbTable");

Categories