Add Join in BQLCommand - acumatica

I would like to join the following statement with EPEmployee table on EPEmployee's BAccountID with FSAppointmentEmployee's EmployeeID column then put where condition on EPEmployee's UserID with currently logged in employee's user id in the current BQLCommand for PXAdapter, so that I can see the list of appointments that are assigned to current employee only.
public static PXAdapter PrepareCustomNavAdapter(PXAction action, PXAdapter adapter, bool prevNextAction = false)
{
var select = adapter.View.BqlSelect;
select = select
.WhereAnd<Where<EPEmployee.userID,Equal<AccessInfo.userID>>>()
.OrderByNew<OrderBy<
Desc<FSAppointment.createdDateTime,
Desc<FSAppointment.srvOrdType,
Desc<FSAppointment.refNbr>>>>>();
var newAdapter = new PXAdapter(new PXView(action.Graph, true, select))
{
MaximumRows = adapter.MaximumRows
};
object current = action.Graph.Views[action.Graph.PrimaryView].Cache.Current;
if (prevNextAction)
{
var sortColumns = new string[adapter.SortColumns.Count() + 1];
adapter.SortColumns.CopyTo(sortColumns, 1);
sortColumns[0] = "CreatedDateTime";
newAdapter.SortColumns = sortColumns;
var descendings = new bool[adapter.Descendings.Count() + 1];
adapter.Descendings.CopyTo(descendings, 1);
descendings[0] = true;
newAdapter.Descendings = descendings;
var searches = new object[adapter.Searches.Count() + 1];
adapter.Searches.CopyTo(searches, 1);
if (current != null && current is FSAppointment)
searches[0] = ((FSAppointment)current).CreatedDateTime;
newAdapter.Searches = searches;
}
else if (current != null)
{
adapter.Currents = new object[] { current };
}
return newAdapter;
}
So that, only these two employees would be able to see that appointment.
Thank you.

AccessInfo is a Singleton, you should decorate it with Current class:
Where<EPEmployee.userID, Equal<Current<AccessInfo.userID>>>

Related

Creating Customer Location from Sales Order Screen

I want to create Customer Location from Sales Order Ship to Contact and Address. The following code works fine when order is saved and a new location is created.
The problem is: After I created a new order with order type “SO” and save the location, I go back to the Order Entry point screen, then click add new order. In new order screen, I select order type “QT” and select the same customer, the ADDRESS tab info are all blank. If I click save button, I got the error message “Error: ‘CustomerID’cannot be empty”. It seems the Customer is not selected. I have to select Customer again.
This only happens when location is saved, and create a new order with different order type and same customer. If I don't go back to the Order Entry point screen, and click + from Sales Order screen directly, it works fine.
namespace PX.Objects.SO
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class SOOrderEntry_CBIZExtension : PXGraphExtension<PX.Objects.SO.SOOrderEntry>
{
#region Event Handlers
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
SOShippingContact shippingContact = Base.Shipping_Contact.Select();
SOShippingAddress shippingAddress = Base.Shipping_Address.Select();
SOOrder order = Base.CurrentDocument.Select();
SOOrderExt orderExt = order.GetExtension<SOOrderExt>();
if (orderExt != null && orderExt.UsrSaveLocation == true
&& !(string.IsNullOrEmpty(shippingContact.FullName)))
{
// get LOCATION segment length
Segment seg = SelectFrom<Segment>.Where<Segment.dimensionID.IsEqual<#P.AsString>>.View.
SelectSingleBound(Base, null, "LOCATION");
int locationLength = 10;
if (seg != null)
locationLength = seg.Length.Value;
// Get Location Name
string[] nameList = shippingContact.FullName.Split(' ');
string locationFirstName = nameList[0];
if (locationFirstName.Length > locationLength)
locationFirstName = locationFirstName.Substring(0, locationLength);
// Check if Customer location already exist
Location location = SelectFrom<Location>.Where<Location.bAccountID.IsEqual<#P.AsInt>.
And<Location.locationCD.IsEqual<#P.AsString>>>.View.
SelectSingleBound(Base, null, Base.Document.Current.CustomerID, locationFirstName);
if (location == null)
{
// New Customer location
// Create Location Instance
LocationMaint locationMaint = PXGraph.CreateInstance<CustomerLocationMaint>();
var loc = (Location) locationMaint.Location.Cache.CreateInstance();
loc.BAccountID = Base.Document.Current.CustomerID;
locationMaint.Location.Cache.SetValueExt<Location.locationCD>(loc, locationFirstName);
loc.LocType = LocTypeList.CustomerLoc;
loc = locationMaint.Location.Insert(loc);
loc.Descr = shippingContact.FullName;
loc = locationMaint.Location.Update(loc);
if (shippingContact.OverrideContact == true)
{
loc.OverrideContact = true;
loc = locationMaint.Location.Update(loc);
string firstName = nameList[0];
string lastName = "";
if (nameList.Length > 1)
lastName = nameList[1];
Contact cont = locationMaint.Contact.Current;
cont.FullName = shippingContact.FullName;
cont.FirstName = firstName;
cont.LastName = lastName;
cont.Attention = shippingContact.Attention;
cont.Phone1 = shippingContact.Phone1;
cont.Phone1Type = shippingContact.Phone1Type;
cont.EMail = shippingContact.Email;
cont = locationMaint.Contact.Update(cont);
}
if (shippingAddress.OverrideAddress == true)
{
loc.OverrideAddress = true;
loc = locationMaint.Location.Update(loc);
var addr = locationMaint.Address.Current;
addr.AddressLine1 = shippingAddress.AddressLine1;
addr.AddressLine2 = shippingAddress.AddressLine2;
addr.City = shippingAddress.City;
addr.CountryID = shippingAddress.CountryID;
addr.State = shippingAddress.State;
addr.PostalCode = shippingAddress.PostalCode;
addr = locationMaint.Address.Update(addr);
}
// Save the customer location
locationMaint.Actions.PressSave();
}
}
// Call bas method
baseMethod();
Base.Shipping_Contact.Cache.Clear();
Base.Shipping_Address.Cache.Clear();
Base.Document.View.RequestRefresh();
Base.Document.Cache.IsDirty = false;
}
#endregion
}
}

Create sales order via code 2017 R2

I am trying to create sales orders with data retrieved from my edi system. I have the code working to retrieve the orders, but I am having trouble creating the SO. I can create them with an external program using the SOAP interface, but I am trying to use the graph and insert them directly. I get an exception stating "{"Error: An error occurred during processing of the field CustomerLocationID : Object reference not set to an instance of an object.."}" but I successfully looked up the customer id and location id. Here is the code for my routine. I use similar code to create records for a new master/detail set of tables that I created.
Please advise if anyone has insight on creating sales orders in this manner. The error is thrown at the line:
soOrder.CurrentDocument.Insert(order);
I also am getting an error trying to access the extended fields for SOOrder and SOLine. I currently commented those fields out to see if I could create an order, but I will need to load data in them as well.
foreach (LingoOrderSearch ediOrder in doc850)
{
res850 = lingo.Retrieve850(ediOrder.documentId, "850");
SOOrderEntry soOrder = PXGraph.CreateInstance<SOOrderEntry>();
soOrder.Clear();
var order = new SOOrder();
//SOOrderExt orderExt = order.GetExtension<SOOrderExt>();
order.OrderType = "SO";
order.Status = "Open";
if (res850.Data850.partner == "BEDBATH" || res850.Data850.partner == "BEDBATH_CAN")
customerLookup = "BBB";
else
customerLookup = res850.Data850.partner;
CustomerMaint customerGraph = PXGraph.CreateInstance<CustomerMaint>();
Customer arCustomer = PXSelect<Customer, Where<Customer.status, Equal<Required<Customer.status>>,
And<Customer.acctCD, Equal<Required<Customer.acctCD>>>>>.Select(this, "Active", customerLookup);
if (arCustomer == null)
throw new PXException("Unable to find customer " + customerLookup + " (partner:" + res850.Data850.partner + ")");
order.CustomerID = arCustomer.BAccountID;
CustomerLocationMaint customerLocationGraph = PXGraph.CreateInstance<CustomerLocationMaint>();
Location arCustomerLocation = PXSelect<Location, Where<Location.isActive, Equal<Required<Location.isActive>>,
And<Location.bAccountID, Equal<Required<Location.bAccountID>>,
And<Location.locationCD, Equal<Required<Location.locationCD>>>>>>
.Select(this, true, arCustomer.BAccountID, res850.Data850.location);
if (arCustomerLocation == null)
throw new PXException("Unable to find customer location" + customerLookup + " / " +
res850.Data850.location + " (partner:" + res850.Data850.partner + ")");
order.CustomerLocationID = arCustomerLocation.LocationID;
order.CustomerOrderNbr = res850.Data850.poNumber;
order.ExtRefNbr = res850.Data850.documentId.ToString();
//orderExt.UsrEDICustomerVendorId = res850.Data850.vendor;
//orderExt.UsrEDICustomerId = res850.Data850.partner;
if (!DateTime.TryParse(res850.Data850.poDate, out tempDate))
{
tempDate = DateTime.Today;
}
order.DocDate = tempDate;
DateTime.TryParse(res850.Data850.requestedDeliveryDate, out tempDate);
if (!DateTime.TryParse(res850.Data850.requestedDeliveryDate, out tempDate))
{
tempDate = DateTime.Today;
}
order.RequestDate = tempDate;
soOrder.CurrentDocument.Insert(order);
soOrder.Persist();
newOrderId = soOrder.CurrentDocument.Current.OrderNbr;
itemList = res850.Data850.items;
foreach (EdiDoc850Lingoitems item in itemList)
{
InventoryItemMaint invItemGraph = PXGraph.CreateInstance<InventoryItemMaint>();
InventoryItem invItem = PXSelect<InventoryItem,
Where<InventoryItem.itemStatus, Equal<Required<InventoryItem.itemStatus>>,
And<InventoryItem.inventoryCD, Equal<Required<InventoryItem.inventoryCD>>>>>
.Select(this, "Active", item.vendorItem);
if (invItem == null)
throw new PXException("Unable to locate item " + item.vendorItem);
var line = new SOLine();
//SOLineExt lineExt = line.GetExtension<SOLineExt>();
line.OrderNbr = newOrderId;
line.InventoryID = invItem.InventoryID;
line.Qty = item.qtyOrder;
line.ShipComplete = "Ship Complete";
Int32 tempLine = 0;
if (Int32.TryParse(item.lineNo, out tempLine)) { };
//lineExt.UsrEDILineNbr = tempLine;
soOrder.Transactions.Insert(line);
}
}
}
Hopefully, the sample below will help move forward with your task:
SOOrderEntry soOrder = PXGraph.CreateInstance<SOOrderEntry>();
var order = new SOOrder();
order.OrderType = SOOrderTypeConstants.SalesOrder;
order = soOrder.CurrentDocument.Insert(order);
Customer arCustomer = PXSelect<Customer, Where<Customer.status, Equal<Required<Customer.status>>,
And<Customer.acctCD, Equal<Required<Customer.acctCD>>>>>.Select(this, "Active", "ABARTENDE");
if (arCustomer == null)
throw new PXException("Unable to find customer");
order.CustomerID = arCustomer.BAccountID;
order = soOrder.CurrentDocument.Update(order);
Location arCustomerLocation = PXSelect<Location, Where<Location.isActive, Equal<Required<Location.isActive>>,
And<Location.bAccountID, Equal<Required<Location.bAccountID>>,
And<Location.locationCD, Equal<Required<Location.locationCD>>>>>>
.Select(this, true, arCustomer.BAccountID, "CHICAGO");
if (arCustomerLocation == null)
throw new PXException("Unable to find customer location");
order.CustomerLocationID = arCustomerLocation.LocationID;
order.CustomerOrderNbr = "res850.Data850.poNumber";
order.ExtRefNbr = "res850.Data850.documentId";
order.RequestDate = DateTime.Today;
soOrder.CurrentDocument.Update(order);
for(int i = 0; i < 3; i++)
{
InventoryItem invItem = PXSelect<InventoryItem,
Where<InventoryItem.itemStatus, Equal<Required<InventoryItem.itemStatus>>,
And<InventoryItem.inventoryCD, Equal<Required<InventoryItem.inventoryCD>>>>>
.Select(this, "Active", "AALEGO500");
if (invItem == null)
throw new PXException("Unable to locate item");
var line = soOrder.Transactions.Insert();
line.InventoryID = invItem.InventoryID;
line.Qty = 1;
line.ShipComplete = SOShipComplete.ShipComplete;
soOrder.Transactions.Update(line);
}
soOrder.Actions.PressSave();

NetSuite SuiteTalk API - Get Inventory Details

I'm using the SuiteTalk (API) service for NetSuite to retrieve a list of Assemblies. I need to load the InventoryDetails fields on the results to view the serial/lot numbers assigned to the items. This is the current code that I'm using, but the results still show those fields to come back as NULL, although I can see the other fields for the AssemblyBuild object. How do I get the inventory details (serials/lot#'s) to return on a transaction search?
public static List<AssemblyBuildResult> Get()
{
var listAssemblyBuilds = new List<AssemblyBuildResult>();
var service = Service.Context();
var ts = new TransactionSearch();
var tsb = new TransactionSearchBasic();
var sfType = new SearchEnumMultiSelectField
{
#operator = SearchEnumMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new string[] { "_assemblyBuild" }
};
tsb.type = sfType;
ts.basic = tsb;
ts.inventoryDetailJoin = new InventoryDetailSearchBasic();
// perform the search
var response = service.search(ts);
response.pageSizeSpecified = true;
// Process response
if (response.status.isSuccess)
{
// Process the records returned in the response
// Get more records with pagination
if (response.totalRecords > 0)
{
for (var x = 1; x <= response.totalPages; x++)
{
var records = response.recordList;
foreach (var t in records)
{
var ab = (AssemblyBuild) t;
listAssemblyBuilds.Add(GetAssemblyBuildsResult(ab));
}
if (response.pageIndex < response.totalPages)
{
response = service.searchMoreWithId(response.searchId, x + 1);
}
}
}
}
// Parse and return NetSuite WorkOrder into assembly WorkOrderResult list
return listAssemblyBuilds;
}
After much pain and suffering, I was able to solve this problem with the following code:
/// <summary>
/// Returns List of AssemblyBuilds from NetSuite
/// </summary>
/// <returns></returns>
public static List<AssemblyBuildResult> Get(string id = "", bool getDetails = false)
{
// Object to populate and return results
var listAssemblyBuilds = new List<AssemblyBuildResult>();
// Initiate Service and SavedSearch (TransactionSearchAdvanced)
var service = Service.Context();
var tsa = new TransactionSearchAdvanced
{
savedSearchScriptId = "customsearch_web_assemblysearchmainlist"
};
// Filter by ID if specified
if (id != "")
{
tsa.criteria = new TransactionSearch()
{
basic = new TransactionSearchBasic()
{
internalId = new SearchMultiSelectField
{
#operator = SearchMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new[] {
new RecordRef() {
type = RecordType.assemblyBuild,
typeSpecified = true,
internalId = id
}
}
}
}
};
}
// Construct custom columns to return
var tsr = new TransactionSearchRow();
var tsrb = new TransactionSearchRowBasic();
var orderIdCols = new SearchColumnSelectField[1];
var orderIdCol = new SearchColumnSelectField();
orderIdCols[0] = orderIdCol;
tsrb.internalId = orderIdCols;
var tranDateCols = new SearchColumnDateField[1];
var tranDateCol = new SearchColumnDateField();
tranDateCols[0] = tranDateCol;
tsrb.tranDate = tranDateCols;
var serialNumberCols = new SearchColumnStringField[1];
var serialNumberCol = new SearchColumnStringField();
serialNumberCols[0] = serialNumberCol;
tsrb.serialNumbers = serialNumberCols;
// Perform the Search
tsr.basic = tsrb;
tsa.columns = tsr;
var response = service.search(tsa);
// Process response
if (response.status.isSuccess)
{
var searchRows = response.searchRowList;
if (searchRows != null && searchRows.Length >= 1)
{
foreach (SearchRow t in searchRows)
{
var transactionRow = (TransactionSearchRow)t;
listAssemblyBuilds.Add(GetAssemblyBuildsResult(transactionRow, getDetails));
}
}
}
// Parse and return NetSuite WorkOrder into assembly WorkOrderResult list
return listAssemblyBuilds;
}
private static string GetAssemblyBuildLotNumbers(string id)
{
var service = Service.Context();
var serialNumbers = "";
var tsa = new TransactionSearchAdvanced
{
savedSearchScriptId = "customsearch_web_assemblysearchlineitems"
};
service.searchPreferences = new SearchPreferences { bodyFieldsOnly = false };
tsa.criteria = new TransactionSearch()
{
basic = new TransactionSearchBasic()
{
internalId = new SearchMultiSelectField
{
#operator = SearchMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new[] {
new RecordRef() {
type = RecordType.assemblyBuild,
typeSpecified = true,
internalId = id
}
}
}
}
};
// Construct custom columns to return
var tsr = new TransactionSearchRow();
var tsrb = new TransactionSearchRowBasic();
var orderIdCols = new SearchColumnSelectField[1];
var orderIdCol = new SearchColumnSelectField();
orderIdCols[0] = orderIdCol;
tsrb.internalId = orderIdCols;
var serialNumberCols = new SearchColumnStringField[1];
var serialNumberCol = new SearchColumnStringField();
serialNumberCols[0] = serialNumberCol;
tsrb.serialNumbers = serialNumberCols;
tsr.basic = tsrb;
tsa.columns = tsr;
var response = service.search(tsa);
if (response.status.isSuccess)
{
var searchRows = response.searchRowList;
if (searchRows != null && searchRows.Length >= 1)
{
foreach (SearchRow t in searchRows)
{
var transactionRow = (TransactionSearchRow)t;
if (transactionRow.basic.serialNumbers != null)
{
return transactionRow.basic.serialNumbers[0].searchValue;
}
}
}
}
return serialNumbers;
}
private static AssemblyBuildResult GetAssemblyBuildsResult(TransactionSearchRow tsr, bool getDetails)
{
if (tsr != null)
{
var assemblyInfo = new AssemblyBuildResult
{
NetSuiteId = tsr.basic.internalId[0].searchValue.internalId,
ManufacturedDate = tsr.basic.tranDate[0].searchValue,
SerialNumbers = tsr.basic.serialNumbers[0].searchValue
};
// If selected, this will do additional NetSuite queries to get detailed data (slower)
if (getDetails)
{
// Look up Lot Number
assemblyInfo.LotNumber = GetAssemblyBuildLotNumbers(tsr.basic.internalId[0].searchValue.internalId);
}
return assemblyInfo;
}
return null;
}
What I learned about pulling data from NetSuite:
Using SavedSearches is the best method to pull data that doesn't automatically come through in the API objects
It is barely supported
Don't specify an ID on the SavedSearch, specify a criteria in the TransactionSearch to get one record
You will need to specify which columns to actually pull down. NetSuite doesn't just send you the data from a SavedSearch automatically
You cannot view data in a SavedSearch that contains a Grouping
In the Saved Search, use the Criteria Main Line = true/false to read data from the main record (top of UI screen), and line items (bottom of screen)

PXSelect returns null after record inserted

I am creating a customer in code. Straight after I create the customer I perform a PXSelect to retrieve the customer by the acctCd. But it returns null everytime. Even though I have checked to the database and verified it exists?
I'm guessing this is something to do with the cache how do I refresh this.
Here is my PXSelect
PXSelect<PX.Objects.AR.Customer, Where<PX.Objects.AR.Customer.acctCD, Equal<Required<PX.Objects.AR.Customer.acctCD>>>>.Select(this, id);
Here is my code that add customer
private PX.Objects.AR.Customer UpdateContact(ContactRead rexContact, PX.Objects.AR.Customer m, string customerClassID, bool insert = true)
{
PX.Objects.CR.Contact defContact = null;
PX.Objects.AR.CustomerMaintInherit graph = PXGraph.CreateInstance<PX.Objects.AR.CustomerMaintInherit>();
graph.Clear(PXClearOption.ClearAll);
//Add Customer and BAccount Records
graph.BAccount.Current = m;
m.AcctCD = "V" + rexContact._id;
m.AcctName = rexContact.system_search_key;
m.Type = "CU";
if (insert) {
m = graph.BAccount.Insert(m);
defContact = graph.DefContact.Current;
}
else {
defContact = PXSelect<PX.Objects.CR.Contact, Where<PX.Objects.CR.Contact.contactID, Equal<Required<PX.Objects.CR.Contact.contactID>>>>.Select(this, m.DefContactID);
graph.DefContact.Current = defContact;
}
//Update Default Contact Record
defContact.ContactType = "AP";
defContact.FullName = rexContact.system_search_key;
if (rexContact._related.contact_emails != null)
{
if (rexContact._related.contact_emails.Length > 0)
{
defContact.EMail = (from e in rexContact._related.contact_emails where e.email_primary == true select e.email_address).FirstOrDefault();
}
}
if (rexContact._related.contact_phones != null)
{
if (rexContact._related.contact_phones.Length > 0)
{
defContact.Phone1 = (from e in rexContact._related.contact_phones where e.phone_primary == true select e.phone_number).FirstOrDefault();
}
}
defContact = graph.DefContact.Update(defContact);
//Change customer class to vendor
m.CustomerClassID = customerClassID;
m = (PX.Objects.AR.Customer)graph.BAccount.Update(m);
graph.Actions.PressSave();
return m;
}
Consider usage PXSelectReadonly. It will try to retrieve value directly from db without usage of cache. Another option is to create instance of graph, with needed view, and through that graph ask db with PXSelect

With OrmLite, is there a way to automatically update table schema when my POCO is modified?

Can OrmLite recognize differences between my POCO and my schema and automatically add (or remove) columns as necessary to force the schema to remain in sync with my POCO?
If this ability doesn't exist, is there way for me to query the db for table schema so that I may manually perform the syncing? I found this, but I'm using the version of OrmLite that installs with ServiceStack and for the life of me, I cannot find a namespace that has the TableInfo classes.
I created an extension method to automatically add missing columns to my tables. Been working great so far. Caveat: the code for getting the column names is SQL Server specific.
namespace System.Data
{
public static class IDbConnectionExtensions
{
private static List<string> GetColumnNames(IDbConnection db, string tableName)
{
var columns = new List<string>();
using (var cmd = db.CreateCommand())
{
cmd.CommandText = "exec sp_columns " + tableName;
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var ordinal = reader.GetOrdinal("COLUMN_NAME");
columns.Add(reader.GetString(ordinal));
}
reader.Close();
}
return columns;
}
public static void AlterTable<T>(this IDbConnection db) where T : new()
{
var model = ModelDefinition<T>.Definition;
// just create the table if it doesn't already exist
if (db.TableExists(model.ModelName) == false)
{
db.CreateTable<T>(overwrite: false);
return;
}
// find each of the missing fields
var columns = GetColumnNames(db, model.ModelName);
var missing = ModelDefinition<T>.Definition.FieldDefinitions
.Where(field => columns.Contains(field.FieldName) == false)
.ToList();
// add a new column for each missing field
foreach (var field in missing)
{
var alterSql = string.Format("ALTER TABLE {0} ADD {1} {2}",
model.ModelName,
field.FieldName,
db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType)
);
Console.WriteLine(alterSql);
db.ExecuteSql(alterSql);
}
}
}
}
No there is no current support for Auto Migration of RDBMS Schema's vs POCOs in ServiceStack's OrmLite.
There are currently a few threads being discussed in OrmLite's issues that are exploring the different ways to add this.
Here is a slightly modified version of code from cornelha to work with PostgreSQL. Removed this fragment
//private static List<string> GetColumnNames(object poco)
//{
// var list = new List<string>();
// foreach (var prop in poco.GetType().GetProperties())
// {
// list.Add(prop.Name);
// }
// return list;
//}
and used IOrmLiteDialectProvider.NamingStrategy.GetTableName and IOrmLiteDialectProvider.NamingStrategy.GetColumnName methods to convert table and column names from PascalNotation to this_kind_of_notation used by OrmLite when creating tables in PostgreSQL.
public static class IDbConnectionExtensions
{
private static List<string> GetColumnNames(IDbConnection db, string tableName, IOrmLiteDialectProvider provider)
{
var columns = new List<string>();
using (var cmd = db.CreateCommand())
{
cmd.CommandText = getCommandText(tableName, provider);
var tbl = new DataTable();
tbl.Load(cmd.ExecuteReader());
for (int i = 0; i < tbl.Columns.Count; i++)
{
columns.Add(tbl.Columns[i].ColumnName);
}
}
return columns;
}
private static string getCommandText(string tableName, IOrmLiteDialectProvider provider)
{
if (provider == PostgreSqlDialect.Provider)
return string.Format("select * from {0} limit 1", tableName);
else return string.Format("select top 1 * from {0}", tableName);
}
public static void AlterTable<T>(this IDbConnection db, IOrmLiteDialectProvider provider) where T : new()
{
var model = ModelDefinition<T>.Definition;
var table = new T();
var namingStrategy = provider.NamingStrategy;
// just create the table if it doesn't already exist
var tableName = namingStrategy.GetTableName(model.ModelName);
if (db.TableExists(tableName) == false)
{
db.CreateTable<T>(overwrite: false);
return;
}
// find each of the missing fields
var columns = GetColumnNames(db, model.ModelName, provider);
var missing = ModelDefinition<T>.Definition.FieldDefinitions
.Where(field => columns.Contains(namingStrategy.GetColumnName(field.FieldName)) == false)
.ToList();
// add a new column for each missing field
foreach (var field in missing)
{
var columnName = namingStrategy.GetColumnName(field.FieldName);
var alterSql = string.Format("ALTER TABLE {0} ADD COLUMN {1} {2}",
tableName,
columnName,
db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType)
);
Console.WriteLine(alterSql);
db.ExecuteSql(alterSql);
}
}
}
I implemented an UpdateTable function. The basic idea is:
Rename current table on database.
Let OrmLite create the new schema.
Copy the relevant data from the old table to the new.
Drop the old table.
Github Repo: https://github.com/peheje/Extending-NServiceKit.OrmLite
Condensed code:
public interface ISqlProvider
{
string RenameTableSql(string currentName, string newName);
string GetColumnNamesSql(string tableName);
string InsertIntoSql(string intoTableName, string fromTableName, string commaSeparatedColumns);
string DropTableSql(string tableName);
}
public static void UpdateTable<T>(IDbConnection connection, ISqlProvider sqlProvider) where T : new()
{
connection.CreateTableIfNotExists<T>();
var model = ModelDefinition<T>.Definition;
string tableName = model.Name;
string tableNameTmp = tableName + "Tmp";
string renameTableSql = sqlProvider.RenameTableSql(tableName, tableNameTmp);
connection.ExecuteNonQuery(renameTableSql);
connection.CreateTable<T>();
string getModelColumnsSql = sqlProvider.GetColumnNamesSql(tableName);
var modelColumns = connection.SqlList<string>(getModelColumnsSql);
string getDbColumnsSql = sqlProvider.GetColumnNamesSql(tableNameTmp);
var dbColumns = connection.SqlList<string>(getDbColumnsSql);
List<string> activeFields = dbColumns.Where(dbColumn => modelColumns.Contains(dbColumn)).ToList();
string activeFieldsCommaSep = ListToCommaSeparatedString(activeFields);
string insertIntoSql = sqlProvider.InsertIntoSql(tableName, tableNameTmp, activeFieldsCommaSep);
connection.ExecuteSql(insertIntoSql);
string dropTableSql = sqlProvider.DropTableSql(tableNameTmp);
//connection.ExecuteSql(dropTableSql); //maybe you want to clean up yourself, else uncomment
}
private static String ListToCommaSeparatedString(List<String> source)
{
var sb = new StringBuilder();
for (int i = 0; i < source.Count; i++)
{
sb.Append(source[i]);
if (i < source.Count - 1)
{
sb.Append(", ");
}
}
return sb.ToString();
}
}
MySql implementation:
public class MySqlProvider : ISqlProvider
{
public string RenameTableSql(string currentName, string newName)
{
return "RENAME TABLE `" + currentName + "` TO `" + newName + "`;";
}
public string GetColumnNamesSql(string tableName)
{
return "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + tableName + "';";
}
public string InsertIntoSql(string intoTableName, string fromTableName, string commaSeparatedColumns)
{
return "INSERT INTO `" + intoTableName + "` (" + commaSeparatedColumns + ") SELECT " + commaSeparatedColumns + " FROM `" + fromTableName + "`;";
}
public string DropTableSql(string tableName)
{
return "DROP TABLE `" + tableName + "`;";
}
}
Usage:
using (var db = dbFactory.OpenDbConnection())
{
DbUpdate.UpdateTable<SimpleData>(db, new MySqlProvider());
}
Haven't tested with FKs. Can't handle renaming properties.
I needed to implement something similiar and found the post by Scott very helpful. I decided to make a small change which will make it much more agnostic. Since I only use Sqlite and MSSQL, I made the getCommand method very simple, but can be extended. I used a simple datatable to get the columns. This solution works perfectly for my requirements.
public static class IDbConnectionExtensions
{
private static List<string> GetColumnNames(IDbConnection db, string tableName,IOrmLiteDialectProvider provider)
{
var columns = new List<string>();
using (var cmd = db.CreateCommand())
{
cmd.CommandText = getCommandText(tableName, provider);
var tbl = new DataTable();
tbl.Load(cmd.ExecuteReader());
for (int i = 0; i < tbl.Columns.Count; i++)
{
columns.Add(tbl.Columns[i].ColumnName);
}
}
return columns;
}
private static string getCommandText(string tableName, IOrmLiteDialectProvider provider)
{
if(provider == SqliteDialect.Provider)
return string.Format("select * from {0} limit 1", tableName);
else return string.Format("select top 1 * from {0}", tableName);
}
private static List<string> GetColumnNames(object poco)
{
var list = new List<string>();
foreach (var prop in poco.GetType().GetProperties())
{
list.Add(prop.Name);
}
return list;
}
public static void AlterTable<T>(this IDbConnection db, IOrmLiteDialectProvider provider) where T : new()
{
var model = ModelDefinition<T>.Definition;
var table = new T();
// just create the table if it doesn't already exist
if (db.TableExists(model.ModelName) == false)
{
db.CreateTable<T>(overwrite: false);
return;
}
// find each of the missing fields
var columns = GetColumnNames(db, model.ModelName,provider);
var missing = ModelDefinition<T>.Definition.FieldDefinitions
.Where(field => columns.Contains(field.FieldName) == false)
.ToList();
// add a new column for each missing field
foreach (var field in missing)
{
var alterSql = string.Format("ALTER TABLE {0} ADD {1} {2}",
model.ModelName,
field.FieldName,
db.GetDialectProvider().GetColumnTypeDefinition(field.FieldType)
);
Console.WriteLine(alterSql);
db.ExecuteSql(alterSql);
}
}
}
So I took user44 answer, and modified the AlterTable method to make it a bit more efficient.
Instead of looping and running one SQL query per field/column, I merge it into one with some simple text parsing (MySQL commands!).
public static void AlterTable<T>(this IDbConnection db, IOrmLiteDialectProvider provider) where T : new()
{
var model = ModelDefinition<T>.Definition;
var table = new T();
var namingStrategy = provider.NamingStrategy;
// just create the table if it doesn't already exist
var tableName = namingStrategy.GetTableName(model.ModelName);
if (db.TableExists(tableName) == false)
{
db.CreateTable<T>(overwrite: false);
return;
}
// find each of the missing fields
var columns = GetColumnNames(db, model.ModelName, provider);
var missing = ModelDefinition<T>.Definition.FieldDefinitions
.Where(field => columns.Contains(namingStrategy.GetColumnName(field.FieldName)) == false)
.ToList();
string alterSql = "";
string addSql = "";
// add a new column for each missing field
foreach (var field in missing)
{
var alt = db.GetDialectProvider().ToAddColumnStatement(typeof(T), field); // Should be made more efficient, one query for all changes instead of many
int index = alt.IndexOf("ADD ");
alterSql = alt.Substring(0, index);
addSql += alt.Substring(alt.IndexOf("ADD COLUMN")).Replace(";", "") + ", ";
}
if (addSql.Length > 2)
addSql = addSql.Substring(0, addSql.Length - 2);
string fullSql = alterSql + addSql;
Console.WriteLine(fullSql);
db.ExecuteSql(fullSql);
}

Resources