This article is contributed. See the original author and article here.
A common scenario for Field Service organizations is to augment their staff with external vendor resources. Leveraging Azure Active Directory B2B Guest Access, vendors can be added to the organizational directory without being created as full first party users within the organization. This allows a clean delineation of users to manage security and data access.
Dynamics 365 has made this vendor onboarding process even easier with Wave 1 2023 by introducing Tenant Switcher for Field Service Mobile. Tenant Switcher provides a user interface where guest users can now easily switch between their Home and Guest Tenants.
Other considerations to note:
Guest Users require a Field Service license and appropriate Security role for access to Field Service Mobile.
Model Driven Application Authentication supports work or school accounts. AAD B2B Guest users configured with a personal account would not be able to authenticate and access the Field Service Mobile application directly.
This article is contributed. See the original author and article here.
The Breakthru app in Teams is available to more than 300 million potential monthly active users in 500,000 organizations. Finding the right audience is critical for independent software vendors (ISVs), and just three years after launching on Teams, Breakthru reaches more than 45,000 organizations worldwide, with a growing customer base.
This article is contributed. See the original author and article here.
This week I had a service request where our customer didn’t have a connection retry logic implemented in their application code in the event of a connection failure to Azure SQL. I would like to share an example about how to implement it.
First the C# code using ODBC API:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace DotNetExample
{
class ClsODBCAPI
{
// Import the ODBC API functions using P/Invoke
[DllImport("odbc32.dll")]
private static extern short SQLAllocHandle(short handleType, IntPtr inputHandle, out IntPtr outputHandle);
[DllImport("odbc32.dll")]
private static extern short SQLSetEnvAttr(IntPtr environmentHandle, int attribute, IntPtr valuePtr, int stringLength);
[DllImport("odbc32.dll")]
private static extern short SQLConnect(IntPtr connectionHandle, string serverName, short nameLength1, string userName, short nameLength2, string password, short nameLength3);
[DllImport("odbc32.dll")]
private static extern short SQLExecDirect(IntPtr statementHandle, string query, int textLength);
[DllImport("odbc32.dll")]
private static extern short SQLFetch(IntPtr statementHandle);
[DllImport("odbc32.dll")]
private static extern short SQLGetData(IntPtr statementHandle, short columnIndex, short targetType, IntPtr targetValue, int bufferLength, out int indicatorValue);
[DllImport("odbc32.dll")]
private static extern short SQLDisconnect(IntPtr connectionHandle);
[DllImport("odbc32.dll")]
private static extern short SQLFreeHandle(short handleType, IntPtr handle);
[DllImport("odbc32.dll")]
private static extern short SQLGetDiagRec(
short handleType,
IntPtr handle,
short recordNumber,
IntPtr sqlState,
out int nativeError,
IntPtr messageText,
short bufferLength,
out short textLength
);
public void Main()
{
// Initialize ODBC environment handle
IntPtr environmentHandle = IntPtr.Zero;
SQLAllocHandle(1, IntPtr.Zero, out environmentHandle);
SQLSetEnvAttr(environmentHandle, 200, (IntPtr)3, 0);
// Initialize ODBC connection and statement handles
IntPtr connectionHandle = IntPtr.Zero;
IntPtr statementHandle = IntPtr.Zero;
short retcode;
retcode = SQLAllocHandle(2, environmentHandle, out connectionHandle);
try
{
// Connect to the database
retcode = RetryLogicUsingODBCAPI(connectionHandle);
if( retcode != 1 )
{
return;
}
retcode = SQLAllocHandle(3, connectionHandle, out statementHandle);
// Prepare and execute a query
SQLExecDirect(statementHandle, "SELECT top 200 TextToSearch FROM PerformanceVarcharNVarchar", 60);
// Fetch and display the result set
int id = 0;
while (SQLFetch(statementHandle) == 0)
{
// Retrieve data for each column
id = id + 1;
int nameLength = 200;
IntPtr namePtr = Marshal.AllocHGlobal(nameLength);
SQLGetData(statementHandle, 1, 1, namePtr, nameLength, out nameLength);
string name = Marshal.PtrToStringAnsi(namePtr);
Console.WriteLine("ID: " + id);
Console.WriteLine("Name: " + name);
Marshal.FreeHGlobal(namePtr);
}
}
catch (Exception ex)
{
// Handle any errors that occur
Console.WriteLine("Error: " + ex.Message);
}
finally
{
// Disconnect and free resources
SQLDisconnect(connectionHandle);
SQLFreeHandle(3, statementHandle);
SQLFreeHandle(2, connectionHandle);
SQLFreeHandle(1, environmentHandle);
}
}
private short RetryLogicUsingODBCAPI(IntPtr connectionHandle)
{
int maxRetryAttempts = 5;
int retryIntervalSeconds = 10;
int retryCount = 0;
short retcode = 0;
TimeSpan ts;
string elapsedTime;
Stopwatch oConnTime = new Stopwatch();
while (retryCount < maxRetryAttempts)
{
try
{
retryCount++;
retcode = SQLConnect(connectionHandle, "DSNName", 7, "username", 8, "Password", 8);
if (retcode == 1)
{
ts = oConnTime.Elapsed;
elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
Console.WriteLine("Connected to the database. Time Spent:" + elapsedTime);
return retcode;
}
else
{
Console.WriteLine("SQLConnect failed with retcode: " + retcode);
GetODBCErrorDetails(connectionHandle);
Console.WriteLine("Retrying connection...in...{0} ms", (1000 * retryIntervalSeconds));
System.Threading.Thread.Sleep(1000 * retryIntervalSeconds);
retryIntervalSeconds = Convert.ToInt32(retryIntervalSeconds * 1.5);
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
return -1;
}
static void GetODBCErrorDetails(IntPtr handle)
{
const int SQL_HANDLE_ENV = 1;
const int SQL_HANDLE_DBC = 2;
IntPtr sqlStatePtr = Marshal.AllocHGlobal(6);
IntPtr messageTextPtr = Marshal.AllocHGlobal(1024);
int nativeError;
short textLength;
short retcode = SQLGetDiagRec(
SQL_HANDLE_DBC,
handle,
1,
sqlStatePtr,
out nativeError,
messageTextPtr,
1024,
out textLength
);
if (retcode == 0)
{
string sqlState = Marshal.PtrToStringAnsi(sqlStatePtr);
string messageText = Marshal.PtrToStringAnsi(messageTextPtr, textLength);
Console.WriteLine("ODBC Error Details:");
Console.WriteLine("SQLState: " + sqlState);
Console.WriteLine("Native Error: " + nativeError);
Console.WriteLine("Message: " + messageText);
}
else
{
Console.WriteLine("Failed to retrieve ODBC error details.");
}
Marshal.FreeHGlobal(sqlStatePtr);
Marshal.FreeHGlobal(messageTextPtr);
}
}
}
This first of the code declares and imports the required functions from the odbc32.dll library using P/Invoke. These functions are used to interact with the ODBC API.
In the Main method, the ODBC environment handle is initialized using SQLAllocHandle function. The SQLSetEnvAttr function is used to set the environment attribute. Then, the ODBC connection and statement handles are initialized using SQLAllocHandle.
Inside the try block, the RetryLogicUsingODBCAPI method is called to establish a connection to the database. If the connection is successful (retcode = 1), a query is executed using SQLExecDirect. The result set is fetched using SQLFetch, and the data is displayed.
In case of any errors, the catch block handles and displays the exception message. The finally block is used to disconnect from the database and free the allocated resources.
The RetryLogicUsingODBCAPI method is responsible for implementing the connection retry logic. It attempts to connect to the database using SQLConnect within a while loop. If the connection is successful (retcode = 1), it returns the retcode. Otherwise, it displays the failure details, waits for a specified interval, and increases the interval for subsequent retries.
The GetODBCErrorDetails method retrieves ODBC error details using SQLGetDiagRec function. It takes the handle as input and retrieves the SQLState, native error code, and message text associated with the error.
This article is contributed. See the original author and article here.
Hi!
Ready to meet your new best friend? Say hello to GitHub Copilot, the AI pair programmer that’s about to change the way you code. It’s like having a super-smart friend who’s always ready to help. No matter the scenario, writing code, fixing bugs, or just trying to remember that one command you always forget.
We’ve got a brand-new GitHub Copilot Fundamentals Learning Path all about GitHub Copilot. What’s a Learning Path, you may ask? Well, it’s a sequence of courses that guides you step-by-step to learn new skills and discover the power of Microsoft products. You can find all sorts of Learning Paths on Microsoft Learn.
Our new Learning Path is split into two parts: “Introduction to GitHub Copilot” and “Introduction to GitHub Copilot for Business“.
In the first part, you’ll get to know GitHub Copilot and all its cool features. It’s like having a ChatGPT friend right in your editor, helping you out with code, error messages, and even generating unit tests. Plus, it’s got your back when you’re working on pull requests or need help with documentation. And let’s not forget about the command line – GitHub Copilot CLI is like the ultimate cheat sheet!
The second part is all about GitHub Copilot for Business. (Spoiler: this is where things get serious). We’re going to review business scenarios like: AI-based security vulnerability filtering, VPN proxy support, and a super simple sign-up process. Imagine having a complete squad of Coding experts ready to help your business code faster and smarter.
Recent Comments