I am developing a C# application which connects to SQL Server. If the network connection breaks, the application should be able to go into a "read-only mode" (offline mode) and only read data from a local database. Right now, I am trying to figure out how to detect the disconnect:
public int executeNonQuery(string query, List<SqlParameter> parameters)
{
int result;
using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))
{
tryOpenSqlConnection(sqlConnection);
using (SqlCommand cmd = new SqlCommand(query, sqlConnection))
{
if (parameters != null)
{
cmd.Parameters.AddRange(parameters.ToArray());
}
result = cmd.ExecuteNonQuery();
}
sqlConnection.Close();
}
return result;
}
private void tryOpenSqlConnection(SqlConnection sqlConnection)
{
try
{
sqlConnection.Open();
}
catch (SqlException se)
{
if (se.Number == 26)
{
catchOfflineMode(se);
}
throw se;
}
}
//...
private void catchOfflineMode(SqlException se)
{
Console.WriteLine("SqlException: " + se.Message);
Console.WriteLine("Setting offline mode...");
//...
}
I thought about using the SQL error codes to detect the loss of connection. But the problem is that sometimes I get exceptions only after the SqlConnection already established, e.g. during execution of the command. The last exception I got was
Error Code 121 - The semaphore timeout period has expired
So, I would have to check every single error code that could have to do with losing network connection.
EDIT: I also thought about catching every SqlException and then checking the ethernet connection (e.g. pinging the server) to check whether the exception comes from a lost connection or not.
Are there better ways to do it?
I came up with my own solution by creating a simple helper class called
ExternalServiceHandler.cs
which is used as a proxy for external service calls to detect the online and offline status of the application after an operation failed.
using System;
using System.Threading;
using System.Threading.Tasks;
using Application.Utilities;
namespace Application.ExternalServices
{
class ExternalServiceHandler: IExternalServiceHandler
{
public event EventHandler OnlineModeDetected;
public event EventHandler OfflineModeDetected;
private static readonly int RUN_ONLINE_DETECTION_SEC = 10;
private static ExternalServiceHandler instance;
private Task checkOnlineStatusTask;
private CancellationTokenSource cancelSource;
private Exception errorNoConnection;
public static ExternalServiceHandler Instance
{
get
{
if (instance == null)
{
instance = new ExternalServiceHandler();
}
return instance;
}
}
private ExternalServiceHandler()
{
errorNoConnection = new Exception("Could not connect to the server.");
}
public virtual void Execute(Action func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
func();
}
catch
{
if(offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
public virtual T Execute<T>(Func<T> func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
return func();
}
catch
{
if (offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
public virtual async Task ExecuteAsync(Func<Task> func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
await func();
}
catch
{
if (offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
public virtual async Task<T> ExecuteAsync<T>(Func<Task<T>> func)
{
if (func == null) throw new ArgumentNullException("func");
try
{
return await func();
}
catch
{
if (offlineModeDetected())
{
throw errorNoConnection;
}
else
{
throw;
}
}
}
private bool offlineModeDetected()
{
bool isOffline = false;
if (!LocalMachine.isOnline())
{
isOffline = true;
Console.WriteLine("-- Offline mode detected (readonly). --");
// notify all modues that we're in offline mode
OnOfflineModeDetected(new EventArgs());
// start online detection task
cancelSource = new CancellationTokenSource();
checkOnlineStatusTask = Run(detectOnlineMode,
new TimeSpan(0,0, RUN_ONLINE_DETECTION_SEC),
cancelSource.Token);
}
return isOffline;
}
private void detectOnlineMode()
{
if(LocalMachine.isOnline())
{
Console.WriteLine("-- Online mode detected (read and write). --");
// notify all modules that we're online
OnOnlineModeDetected(new EventArgs());
// stop online detection task
cancelSource.Cancel();
}
}
public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(period, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
action();
}
}
}
protected virtual void OnOfflineModeDetected(EventArgs e)
{
OfflineModeDetected?.Invoke(this, e);
}
protected virtual void OnOnlineModeDetected(EventArgs e)
{
OnlineModeDetected?.Invoke(this, e);
}
}
}
The LocalMachine.isOnline() method looks like this:
namespace Application.Utilities
{
public class LocalMachine
{
// ... //
public static bool isOnline()
{
try
{
using (var client = new WebClient())
{
string serveraddress = AppSettings.GetServerHttpAddress();
using (var stream = client.OpenRead(serveraddress))
{
return true;
}
}
}
catch
{
return false;
}
}
// ... //
}
The helper class can be used every time an external service call is made. In the following example, a SQL non query is executed by the ExternalServiceHandler:
public async Task<int> executeNonQueryAsync(string query)
{
return await ExternalServiceHandler.Instance.ExecuteAsync(async () =>
{
return await DBManager.executeNonQueryAsync(query);
});
}
The solution works fine for me. If you have any better ideas, please let me know.
Related
I creating a UWP app that pulls in data from an easy table using IMobileServiceSyncTable, but when I try and use this to get information from the table to put into a list I get a SyncContext is not yet initialized error. I can add items to the database fine its just receiving things thats giving me trouble.
Heres my code for interacting with the table:
private MobileServiceCollection<DrillItem, DrillItem> drills;
private IMobileServiceSyncTable<DrillItem> drillTable = App.MobileService.GetSyncTable<DrillItem>();
public CombatDrillsTable()
{
}
public MobileServiceCollection<DrillItem, DrillItem> GetDrills()
{
return this.drills;
}
public async Task AddDrill(DrillItem drillItem, String n, int s, int t, string sty)
{
drillItem.Name = n;
drillItem.Sets = s;
drillItem.SetTime = t;
drillItem.Style = sty;
await App.MobileService.GetTable<DrillItem>().InsertAsync(drillItem);
drills.Add(drillItem);
}
public async void GetById(string n)
{
IMobileServiceTableQuery<DrillItem> query = drillTable.Where(drillItem => drillItem.Name == n)
.Select(drillItem => drillItem);
List<DrillItem> items = await query.ToListAsync();
Console.WriteLine(items);
}
public async Task GetDrillsAsync(String cat)
{
MobileServiceInvalidOperationException exception = null;
try {
drills = await drillTable.Where(drillItem => drillItem.Style == cat)
.ToCollectionAsync();
Console.WriteLine(drills);
}
catch (MobileServiceInvalidOperationException e)
{
exception = e;
}
if (exception != null)
{
await new MessageDialog(exception.Message, "Error loading items").ShowAsync();
}
else
{
// code here
}
}
heres the code that activates the get:
String parameters;
CombatTableView ctv = new CombatTableView();
private ObservableCollection<DrillItem> _items;
private ObservableCollection<DrillItem> _temp;
public DisplayDrills()
{
this.InitializeComponent();
_items = new ObservableCollection<DrillItem>();
AddItemsAsync();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
parameters = (String)e.Parameter;
testBox.Text = parameters;
RefreshListView.ItemsSource = _items;
}
private async Task updateDrillsAsync(){
await ctv.combatDrillsTable.GetDrillsAsync(parameters);
}
private async void AddItemsAsync()
{
await updateDrillsAsync();
_temp = ctv.combatDrillsTable.GetDrills();
foreach (var t in _temp)
{
_items.Insert(0, t);
}
}
As the exception described, we must initialize the local SQLite database before we can use the offline client. Details please reference Configuring the Local SQLite database. For example:
private async Task InitLocalStoreAsync()
{
if (!App.MobileService.SyncContext.IsInitialized)
{
var store = new MobileServiceSQLiteStore("localstore.db");
store.DefineTable<TodoItem>();
await App.MobileService.SyncContext.InitializeAsync(store);
}
await SyncAsync();
}
private async Task SyncAsync()
{
await App.MobileService.SyncContext.PushAsync();
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
}
And the best place to do this is in the GetTable<> method, the following code showed the example for initialize it when OnNavigatedTo, :
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
await InitLocalStoreAsync(); // offline sync
ButtonRefresh_Click(this, null);
}
I want to make a Wifimanager class for UWP (Raspberry pi 3)
I looked at some tutorials and figured i was good to go.
So I came up with this:
public class WifiManager
{
private WiFiAdapter adapter;
public List<WifiNetwork> Networks;
public WifiManager()
{
Initialize();
}
private async void Initialize()
{
var access = await WiFiAdapter.RequestAccessAsync();
if (access != WiFiAccessStatus.Allowed)
{
throw new WifiAdaperAccessDeniedException();
}
else
{
var result = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector());
if (result.Count >= 1)
{
adapter = await WiFiAdapter.FromIdAsync(result[0].Id);
}
else
{
throw new NoWifiAdapterFoundException();
}
}
}
public async Task GetAvailableNetWorksAsync()
{
try
{
if (adapter != null)
{
await adapter.ScanAsync();
}
}
catch (Exception err)
{
throw new WifiAdaperAccessDeniedException();
}
Networks = new List<WifiNetwork>();
foreach(var network in adapter.NetworkReport.AvailableNetworks)
{
Networks.Add(new WifiNetwork(network, adapter));
}
}
}
However when I try to get the Available Networks using the async function the adapter is null. When I remove the if statement around await adapter.ScanAsync(); I get an AccessViolation.
I dont have much experience with async tasks and such, but the tutorials i found did not gave me the explaination i needed to fix this.
The problem is that you are calling an async method from the constructor. Since you do no wait for the result and are probably calling GetAvailableNetWorksAsync directly after the constructor the adapter variable has not been set yet..
You need to take it out of the constructor like this:
public class WifiManager
{
private WiFiAdapter adapter;
public List<WifiNetwork> Networks;
public async Task InitializeAsync()
{
var access = await WiFiAdapter.RequestAccessAsync();
if (access != WiFiAccessStatus.Allowed)
{
throw new WifiAdaperAccessDeniedException();
}
else
{
var result = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector());
if (result.Count >= 1)
{
adapter = await WiFiAdapter.FromIdAsync(result[0].Id);
}
else
{
throw new NoWifiAdapterFoundException();
}
}
}
public async Task GetAvailableNetWorksAsync()
{
try
{
if (adapter != null)
{
await adapter.ScanAsync();
}
}
catch (Exception err)
{
throw new WifiAdaperAccessDeniedException();
}
Networks = new List<WifiNetwork>();
foreach(var network in adapter.NetworkReport.AvailableNetworks)
{
Networks.Add(new WifiNetwork(network, adapter));
}
}
}
Now your calling code probably looks like this:
var wifiManager = new WifiManager();
await wifiManager.GetAvailableNetWorksAsync();
change that to
var wifiManager = new WifiManager();
await wifiManager.InitializeAsync();
await wifiManager.GetAvailableNetWorksAsync();
Take away: do not call async methods in a constructor since you cannot await completion using the await keyword and using .Wait() or .Result might give other troubles . Applies for 99% of all situations.
Another approach could be something like:
public class WifiManager
{
private WiFiAdapter adapter;
public List<WifiNetwork> Networks;
pivate async Task InitializeAsync()
{
var access = await WiFiAdapter.RequestAccessAsync();
if (access != WiFiAccessStatus.Allowed)
{
throw new WifiAdaperAccessDeniedException();
}
else
{
var result = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector());
if (result.Count >= 1)
{
adapter = await WiFiAdapter.FromIdAsync(result[0].Id);
}
else
{
throw new NoWifiAdapterFoundException();
}
}
}
public async Task GetAvailableNetWorksAsync()
{
try
{
if (adapter == null)
{
await InitializeAsync();
}
if (adapter != null)
{
await adapter.ScanAsync();
}
}
catch (Exception err)
{
throw new WifiAdaperAccessDeniedException();
}
Networks = new List<WifiNetwork>();
foreach(var network in adapter.NetworkReport.AvailableNetworks)
{
Networks.Add(new WifiNetwork(network, adapter));
}
}
}
I have a windows service, which loads assembly in another AppDomain at runtime. Then it executes them and finally unloads the AppDomain. The problem is the execute method from the plugins are async tasks and I get the SerializationException because Task does not inherit from MarshalByRefObject.
I wrapped the plugin in a proxy which inherits from MarshalByRefObject, but I dont know how to get rid of the SerializationException?
public interface IPlugin : IDisposable
{
Guid GUID { get; }
string Name { get; }
string Description { get; }
Task Execute(PluginPanel panel, string user);
}
The proxy:
[Serializable()]
public class PluginProxy : MarshalByRefObject, IPlugin
{
private IPlugin m_Plugin;
public bool Init(string file)
{
Assembly ass = Assembly.Load(AssemblyName.GetAssemblyName(file));
if (ass == null || ass.GetTypes() == null || ass.GetTypes().Length == 0)
return false;
foreach (Type type in ass.GetTypes())
{
if (type.IsInterface || type.IsAbstract)
continue;
if (type.GetInterface(typeof(IPlugin).FullName) != null)
{
m_Plugin = (IPlugin)Activator.CreateInstance(type);
return true;
}
}
return false;
}
public Guid GUID { get { return m_Plugin.GUID; } }
public string Name { get { return m_Plugin.Name; } }
public string Description { get { return m_Plugin.Description; } }
// I debugged and found out the error happens AFTER m_Plugin.Execute
// so the method runs well, but the return back to the pProxy.Execute is throwing the SerializationException
public async Task Execute(PluginPanel panel, string user) { await m_Plugin.Execute(panel, user); }
}
And the Method which loads the Assembly and gets the SerializationException:
AppDomainSetup setup = new AppDomainSetup();
// some setup stuff
AppDomain dom = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, setup);
PluginProxy pProxy = (PluginProxy)dom.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().CodeBase, typeof(PluginProxy).FullName);
pProxy.Init(app.Apppath);
// I await the task later in code, because the user can cancel the execution
try { tExe = pProxy.Execute(panel, user.Username); }
catch (System.Runtime.Serialization.SerializationException e)
{
// runs always in this catch, even if no Exception from the plugin was thrown
}
catch (Exception e) { AddToErrorLog(panel.PanelName, e); }
finally
{
pProxy.Dispose();
AppDomain.Unload(dom);
}
Maybe my whole concept of loading Plugins is wrong?
Thanks to Hamlet Hakobyan and the post from Stephen Toub, I think I was able to solve the problem.
I replaced the line from the caller
try { tExe = pProxy.Execute(panel, user.Username); }
with
tExe = DoWorkInOtherDomain(pProxy, panel, user.Username);
and the method DoWorkInOtherDomain:
private Task DoWorkInOtherDomain(PluginProxy pProxy, PluginPanel panel, string user)
{
var ch = new MarshaledResultSetter<string>();
pProxy.Execute(panel, user, ch);
return ch.Task;
}
and finally the proxy class:
Task.Run(() =>
{
try
{
m_Plugin.Execute(panel, user).Wait();
}
catch (AggregateException e)
{
if (e.InnerExceptions != null)
foreach (Exception ein in e.InnerExceptions)
AddToErrorLog(panel.PanelName, ein);
}
catch (Exception e) { AddToErrorLog(panel.PanelName, e); }
finally { ch.SetResult(AppDomain.CurrentDomain.FriendlyName); }
});
I need to call Wait() in
m_Plugin.Execute(panel, user).Wait();
it catches the Exceptions from the plugin so everything is doing fine. The Wait() call should only blocking the Task.Run and not the other Tasks.
Can anyone tell me if this is a good solution or should I change something? I dont need a result so I just do:
ch.SetResult(AppDomain.CurrentDomain.FriendlyName);
because I dont know how I should do it without a result.
Given an implementation as follows:
public class SomeServiceWrapper
{
public string GetSomeString()
{
try
{
//Do Something
}
catch (IOException e)
{
throw new ServiceWrapperException("Some Context", e);
}
catch (WebException e)
{
throw new ServiceWrapperException("Some Context", e);
}
}
}
The intention of the above is to enable the consumer of GetSomeString to only need to catch ServiceWrapperException.
Consider the following approach to extending this with a similar async behaviour:
public Task<string> GetSomeStringAsync()
{
Task<string>.Factory doSomething = ...
return doSomething.ContinueWith(x =>
{
if (x.IsFaulted)
{
if (x.Exception.InnerExceptions.Count() > 1)
{
throw new AggregateException(x.Exception);
}
var firstException = x.Exception.InnerExceptions[0];
if (typeof(firstException) == typeof(IOException)
|| typeof(firstException) == typeof(WebException))
{
throw new ServiceWrapperException("Some Context", firstException);
}
}
return x.Result;
}
}
This synchronous approach to wrapping exceptions doesn't fit naturally with the asynchronous approach.
What could the author of SomeServiceWrapper do to simplify the exception handling code of any consumers so they only need to handle TradeLoaderException instead of both IOException and WebException?
I made an extension method that pretty much does that. Usage:
public static Task<string> GetSomeStringAsync()
{
var doSomething = Task.Factory.StartNew(() => "bar");
return doSomething.WrapExceptions(typeof(IOException), typeof(WebException));
}
You can just return the original task with the continuation.
I would suggest changing ServiceWrapperException to hold more than one exception like AggregateException and then change the first part.
The Method:
public static Task<TResult> WrapExceptions<TResult>(this Task<TResult> task, params Type[] exceptionTypes)
{
return task.ContinueWith(_ =>
{
if (_.Status == TaskStatus.RanToCompletion) return _.Result;
if (_.Exception.InnerExceptions.Count > 1)
{
throw new AggregateException(_.Exception);
}
var innerException = _.Exception.InnerExceptions[0];
if (exceptionTypes.Contains(innerException.GetType()))
{
throw new ServiceWrapperException("Some Context", innerException);
}
throw _.Exception;
});
}
I have the following code to test DB connection, it runs periodically to test for DB availability:
private bool CheckDbConn()
{
SqlConnection conn = null;
bool result = true;
try
{
conn = DBConnection.getNewCon();
ConnectionState conState = conn.State;
if (conState == ConnectionState.Closed || conState == ConnectionState.Broken)
{
logger.Warn(LogTopicEnum.Agent, "Connection failed in DB connection test on CheckDBConnection");
return false;
}
}
catch (Exception ex)
{
logger.Warn(LogTopicEnum.Agent, "Error in DB connection test on CheckDBConnection", ex);
return false; // any error is considered as db connection error for now
}
finally
{
try
{
if (conn != null)
{
conn.Close();
}
}
catch (Exception ex)
{
logger.Warn(LogTopicEnum.Agent, "Error closing connection on CheckDBConnection", ex);
result = false;
}
}
return result;
}
And:
static public SqlConnection getNewCon()
{
SqlConnection newCon = new SqlConnection();
newCon.ConnectionString = DBConnection.ConnectionString; // m_con.ConnectionString;
newCon.Open();
return newCon;
}
My question is: will this work as expected?
Specifically, I'm concerned about the test of the ConnectionState. Is it possible that the state will be: connecting (since Open() is synchronous)?
What should I do in that case?
You can try like this.
public bool IsServerConnected()
{
using (var l_oConnection = new SqlConnection(DBConnection.ConnectionString))
{
try
{
l_oConnection.Open();
return true;
}
catch (SqlException)
{
return false;
}
}
}
SqlConnection will throw a SqlException when it cannot connect to the server.
public static class SqlExtensions
{
public static bool IsAvailable(this SqlConnection connection)
{
try
{
connection.Open();
connection.Close();
}
catch(SqlException)
{
return false;
}
return true;
}
}
Usage:
using(SqlConnection connection = GetConnection())
{
if(connection.IsAvailable())
{
// Success
}
}
Your code seems fine, but you really need to use the IDisposable pattern, and some naming convention too:
private bool CheckDbConnection(string connectionString)
{
try
{
using(var connection = new SqlConnection(connectionString))
{
connection.Open();
return true;
}
}
catch (Exception ex)
{
logger.Warn(LogTopicEnum.Agent, "Error in DB connection test on CheckDBConnection", ex);
return false; // any error is considered as db connection error for now
}
}
And connection.Close() is not supposed to throw. Just use the using block and your are fine.
No need to test the Close state, since you have just opened it.
More about the Broken state:
Broken The connection to the data source is broken. This can occur
only after the connection has been opened. A connection in this state
may be closed and then re-opened. (This value is reserved for future
versions of the product.)
So really, no need to test that.
The Connecting state could be catch if you are in a multithread context and your instance of connection is shared. But it is not your case here.
This code does not block a UI if called.
public static class DatabaseExtensions
{
public static async Task<bool> IsConnectionViable(this string connectionStr)
{
await using var sqlConn = new SqlConnection(connectionStr);
return await sqlConn.IsConnectionViable();
}
public static async Task<bool> IsConnectionViable(this SqlConnection connection)
{
var isConnected = false;
try
{
await connection.OpenAsync();
isConnected = (connection.State == ConnectionState.Open);
}
catch (Exception)
{
// ignored
}
return isConnected;
}
}
actually, in visual studio, connection class has sonnectionstate property.
when connection state changes, connections statechange event is been trigerred.
you might want to check this article.
https://msdn.microsoft.com/en-us/library/aa326268(v=vs.71).aspx
I was using #Ramesh Durai's solution but found that on my setup at least (the app calling/testing periodically after the app had started; using .Net 3.5 with Sql Server 2012 database) that the first call to IsConnected() after taking the database offline was returning true. However, it was throwing the expected exception on the ExecuteScalar() line below:
public bool IsConnected() {
using (var conn = new SqlConnection(DBConnection.ConnectionString)) {
using (var cmd = New SqlCommand("SELECT 1", conn)) {
try {
conn.Open();
cmd.ExecuteScalar();
return true;
} catch (SqlException) {
return false;
}
}
}
}
This code is for Mysql.
public class Program
{
string connection = "SERVER=localhost; user id=root; password=; database=dbname";
private void Form1_Load(object sender, System.EventArgs e)
{
checkifconnected();
}
private void checkifconnected()
{
MySqlConnection connect = new MySqlConnection(connection);
try{
connect.Open();
MessageBox.Show("Database connected");
}
catch
{
MessageBox.Show("you are not connected to database");
}
}
public static void Main()
{
}
}