I have an application which caches some data at startup. There are several things to put in a cache, but they are very similar. I created classes like this, the only difference in them is the type of the item to be added to the dictionary (in this example the Setting class), and the _sqlNotifyCommand.CommandText.
public class SettingsCache : ILoggerClass
{
private Dictionary<int, Dictionary<int, Setting>> _cachedItems;
private string _entityConnectionString;
private SQLNotifier _sqlNotifier;
private SqlCommand _sqlNotifyCommand = new SqlCommand();
private bool _dataLoaded = false;
private void AddItem(Setting item)
{
if (!_cachedItems.ContainsKey(item.PartnerId))
{
_cachedItems.Add(item.PartnerId, new Dictionary<int, Setting>());
}
if (_cachedItems[item.PartnerId].ContainsKey(item.Id))
{
_cachedItems[item.PartnerId].Remove(item.Id);
}
_cachedItems[item.PartnerId].Add(item.Id, item);
}
public Setting GetSetting(int partnerId, int id)
{
if (_cachedItems.ContainsKey(partnerId))
{
if (_cachedItems[partnerId].ContainsKey(id))
{
return _cachedItems[partnerId][id];
}
return null;
}
return null;
}
public SettingsCache(string connectionString)
{
_entityConnectionString = connectionString;
_cachedItems = new Dictionary<int, Dictionary<int, Setting>>();
LoadData();
try
{
using (var db = new partnerEntity(connectionString))
{
string adoSqlConnectionString = ((EntityConnection) db.Connection).StoreConnection.ConnectionString;
_sqlNotifier = new SQLNotifier(adoSqlConnectionString);
_sqlNotifier.NewMessage += _sqlNotifier_NewMessage;
_sqlNotifyCommand.CommandType = CommandType.Text;
_sqlNotifyCommand.CommandText = "SELECT setting_id, setting_value, partner_id FROM dbo.setting";
_sqlNotifyCommand.Notification = null;
_sqlNotifier.RegisterDependency(_sqlNotifyCommand);
}
}
catch (Exception exception)
{
this.Log(this, LogLevel.Error, 0, exception);
}
}
private void _sqlNotifier_NewMessage(object sender, SqlNotificationEventArgs e)
{
if (e.Info == SqlNotificationInfo.Insert || e.Info == SqlNotificationInfo.Update)
{
this.Log(this, LogLevel.Info, 0, string.Format("Database changed, reloading settings data..."));
LoadData();
}
_sqlNotifier.RegisterDependency(_sqlNotifyCommand);
}
private void LoadData()
{
_dataLoaded = false;
try
{
using (var db = new partnerEntity(_entityConnectionString))
{
var dbData = db.setting.ToList();
foreach (var cItem in dbData)
{
AddItem(new Setting
{
PartnerId = cItem.partner_id,
Id = cItem.setting_id,
Value = cItem.setting_value
});
}
}
_dataLoaded = true;
}
catch (Exception exception)
{
this.Log(this, LogLevel.Error, 0, exception);
}
if (!_dataLoaded)
{
Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(_ => { LoadData(); });
}
}
}
Is there a more generic way to do this? The last thing which was needed in the classes this part:
if (!_dataLoaded)
{
Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(_ => { LoadData(); });
}
And I had to modify every Caching class. I needed to declare the variable, add it to the try-catch block, and after the block insert the same line in 6 classes. This code seems very boilerplate to me, I can't believe there is no simpler solution. I tried to make an Interface with AddItem, LoadData, OnLoadDataFailed methods, but in the AddItem method I need to specify the item, I'm stuck.
Here is the basic structure of what (I think) you want to accomplish using Generics.
First, declare a common interface for cached items so that the AddItem method still works:
public interface ICacheItem
{
int Id { get; set; }
int PartnerId { get; set; }
}
Now you can create the SettingsCache<T> that holds the items:
public class SettingsCache<T> : ILogger where T : ICacheItem
{
private Dictionary<int, Dictionary<int, T>> _cachedItems;
//...
public SettingsCache(string connectionString, string commandText)
{
//...
}
private void AddItem(T item)
{
if (!_cachedItems.ContainsKey(item.PartnerId))
{
_cachedItems.Add(item.PartnerId, new Dictionary<int, T>());
}
if (_cachedItems[item.PartnerId].ContainsKey(item.Id))
{
_cachedItems[item.PartnerId].Remove(item.Id);
}
_cachedItems[item.PartnerId].Add(item.Id, item);
}
}
I left out most of the implementation to focus on your two biggest concerns, the command text and the AddItem method. The generic has a constraint so that it can only accept items that are of ICacheItem, so you can use any properties that ICacheItem defines.
To use a cache, simply create one with the specific type (assuming Setting implements ICacheItem):
var settingsCache = new SettingsCache<Setting>("connection string", "command string");
Related
I have strange behaviour that my code at specific place is not stepping in specific method. There is no error, no nothing. It is just reaching the line without stepping into it. I was debugging and stepping into each tep to found that issue. I have no idea what's going on, that's first time i face such an issue. Below find my code and at the end explained exactly where it happens.
static class Program
{
private static UnityContainer container;
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Bootstrap();
Application.Run(container.Resolve<FrmLogin>());
}
private static void Bootstrap()
{
container = new UnityContainer();
container.RegisterType<IRepositoryDal<User>, UserRepositoryDal>();
container.RegisterType<IRepositoryDal<Order>, OrderRepositoryDal>();
container.RegisterType<IDbManager, DbManager>(new InjectionConstructor("sqlserver"));
container.RegisterType<IGenericBal<User>, UserBal>();
container.RegisterType<IGenericBal<Order>, OrderBal>();
}
}
public partial class FrmLogin : Form
{
private readonly IGenericBal<User> _userBal;
public FrmLogin(IGenericBal<User> userBal)
{
InitializeComponent();
_userBal = userBal;
}
private void btnSearch_Click(object sender, EventArgs e)
{
try
{
var a = _userBal.SearchByName("John");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
public class UserBal : IGenericBal<User>
{
private readonly IRepositoryDal<User> _userRepositoryDal;
public UserBal(IRepositoryDal<User> userRepositoryDal)
{
_userRepositoryDal = userRepositoryDal ?? throw new ArgumentNullException(nameof(userRepositoryDal));
}
public IEnumerable<User> SearchByName(string name)
{
return _userRepositoryDal.SearchByName(name);
}
}
public interface IGenericBal<out T> where T : IEntity
{
IEnumerable<T> SearchByName(string name);
}
public class UserRepositoryDal: IRepositoryDal<User>
{
private readonly IDbManager _dbManager;
public UserRepositoryDal(IDbManager dbManager)
{
_dbManager = dbManager;
}
public IEnumerable<User> SearchByName(string username)
{
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#Name", 50, username, DbType.String),
};
username = "JUSTyou";
var userDataTable = _dbManager.GetDataTable("SELECT * FROM T_Marke WHERE Name=#Name", CommandType.Text, parameters.ToArray());
foreach (DataRow dr in userDataTable.Rows)
{
var user = new User
{
Id = int.Parse(dr["Id"].ToString()),
Firstname = dr["Name"].ToString(),
};
yield return user;
}
}
}
public interface IRepositoryDal<T> where T : IEntity
{
IEnumerable<T> SearchByName(string username);
T SearchById(string id);
void Update(T entity);
void Remove(T entity);
void Add(T entity);
}
What happens here is:
When i start to debug using breakpoints i start to click button which raises btnSearch_Click handler you can find in my code. When it happens it goes to: var a = _userBal.SearchByName("John"); then to UserBal's code SearchByName method. When it reaches: return _userRepositoryDal.SearchByName(name); it's not going into in this case UserRepositoryDal's SerachByName method. It's just highlight this line of code and going next but not inside. No error, no nothing... Why it happens?
This is called "Lazy evaluation": https://blogs.msdn.microsoft.com/pedram/2007/06/02/lazy-evaluation-in-c/
In brief, you use yield return to return the method's results, which means that the code does not get evaluated immediately, but the actual method execution gets postponed up to until you actually use some results of the evaluation.
Update:
If you want to evaluate your code immediately, you need to use it somehow. The simplest way would be to return the whole result set to create a new array or list from it. You can do it, for instance, by replacing:
return _userRepositoryDal.SearchByName(name);
with:
return _userRepositoryDal.SearchByName(name).ToList();
While this might be good for debugging, it will also remove the performance gains you obtain by using lazy evaluation.
This bit of code is an Lazy Enumeration:
public IEnumerable<User> SearchByName(string username)
{
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#Name", 50, username, DbType.String),
};
username = "JUSTyou";
var userDataTable = _dbManager.GetDataTable("SELECT * FROM T_Marke WHERE Name=#Name", CommandType.Text, parameters.ToArray());
foreach (DataRow dr in userDataTable.Rows)
{
var user = new User
{
Id = int.Parse(dr["Id"].ToString()),
Firstname = dr["Name"].ToString(),
};
yield return user;
}
}
By using yield return user; your telling .Net to only run this code when it's enumerated. At no point are you accessing the result of SearchByName. So it won't step into it:
public IEnumerable<User> SearchByName(string name)
{
//this doesn't access the result
return _userRepositoryDal.SearchByName(name);
//this would
//return _userRepositoryDal.SearchByName(name).ToList();
}
The easiest way to "fix" this is to remove the enumeration as I don't think this is what you want:
public IEnumerable<User> SearchByName(string username)
{
List<User> response = new List<User>();
var parameters = new List<IDbDataParameter>
{
_dbManager.CreateParameter("#Name", 50, username, DbType.String),
};
username = "JUSTyou";
var userDataTable = _dbManager.GetDataTable("SELECT * FROM T_Marke WHERE Name=#Name", CommandType.Text, parameters.ToArray());
foreach (DataRow dr in userDataTable.Rows)
{
var user = new User
{
Id = int.Parse(dr["Id"].ToString()),
Firstname = dr["Name"].ToString(),
};
//Add to a collection
response.Add(user);
}
//return result
return response;
}
I have a WinForms application that gets Car data from a Sqlite database file which is generated by a CSV file from a WebService, and the related Parts for each Car. When instanced, the Car class populates all properties from the database and gets a lot of data from a WebService that takes about 30 seconds to finish so I just call an async Task after populating the database properties and let it run asyncronously. After the web service returns all data and all work is done, the instance is saved in a List that works like an internal cache.
All works well until the user makes an operation on a Part which requires the property ParentCar to be returned while the WebService method is still running, causing a new Car to be instantiated and re polling the web service as many times the property is requested while the Car doesn't exist on the cache list. This stops as soon as the first instance finishes processing but all changes will be overwritten every time the next instances finish.
I'm struggling to find a way to ensure that a Car is only instanced one time without blocking the UI during the application life time, any ideas?
This is the code that I currently have:
public class Car
{
private List<Part> _parts = new List<Part>();
public string Id { get; private set; }
public int DbIndex
{
get { return DbClass.GetCarIndexById(Id); }
}
public ReadOnlyCollection<Part> Parts { get => _parts.AsReadOnly(); }
public Car(System.Data.SQLite.SQLiteDataReader sQLiteDataReader)
{
// Code to Load all properties from sQLiteDataReader
Task.Run(() => LongRuningWebServiceTask());
}
private async Task LongRuningWebServiceTask
{
// Long running code that will populate even more properties
SaveToInternalDb();
}
private void SaveToInternalDb()
{
if (DbIndex > -1)
DbClass.UpdateCarData(this);
else
DbClass.AddCar(this);
}
public void RelateParts(IEnumerable<Part> parts)
{
_parts.AddRange(parts);
}
~Car()
{
SaveToInternalDb();
}
}
public class Part
{
public string ParentCarId { get; private set; }
public Car ParentCar
{
get
{
Task getCar = DbClass.GetCarById(ParentCarId);
getCar.Wait();
return getCar.Result;
}
}
}
public static class DbClass
{
private static SQLiteConnection sqlConn;
private readonly string sqlFile = "pathToDbFile";
private static List<Car> CarCache = new List<Car>();
public static async Task<Car> GetCarById(string> carId, bool ignoreCache = false)
{
Car foundCar = null;
if (!ignoreCache)
foundCar = CarCache.Find(s => s.Id == carId);
if (foundCar == null)
{
try
{
string sql = string.Format("SELECT * FROM all_Cars WHERE Car = '{0}';", carId);
using (SQLiteCommand command = new SQLiteCommand(sql, sqlConn))
{
using (SQLiteDataReader reader = (SQLiteDataReader)await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
foundCar = new Car(reader));
}
}
}
catch (Exception e)
{
string m = e.Message;
}
if (foundCar != null)
{
var partsList = await GetPartsByCarId(carId);
if (partsList.Count > 0)
Car.RelateParts(partsList);
if (!ignoreCache)
{
if (foundCar.DbIndex == -1)
CarCache.Add(foundCar);
}
}
}
return foundCar;
}
public static async Task<List<Part>> GetPartsByCarId(string carId)
{
List<Part> foundParts = new List<Part>();
int index = GeCarIndexById(carId);
Car foundCar = index > -1 ? CarCache[index] : null;
if (foundCar != null)
foundParts = await GetPartsByCarId(carId);
return foundParts;
}
public static void InitiateSqlConnection()
{
if (sqlConn == null)
{
sqlConn = new SQLiteConnection("Data Source=" + sqlFile.FullName + ";Version=3;");
try
{
sqlConn.Open();
}
catch (Exception e)
{
var m = e.Message;
}
}
else
{
if(sqlConn.State == System.Data.ConnectionState.Broken || sqlConn.State == System.Data.ConnectionState.Closed)
sqlConn.Open();
}
}
public static int GetCarIndexById(string carId)
{
int index = -1;
for(int c = 0; c < CarCache.Count;c++)
{
if(CarCache[c].Id == carId)
{
index = c;
break;
}
}
return index;
}
public static void AddCar(Car car)
{
CarCache.Add(car);
}
public static void UpdateCarData(Car car)
{
if(car != null)
{
int index = car.DbIndex;
if(index > -1)
CarCache[index] = car;
}
}
}
I Have a class Variable and another class Variables.
Variables class is used to maintain and generate the list of objects of the type Variable.
Everything is working ok and i am happy about it. But there is one thing i dont know how to get it done.
i want to use the Variables in foreach loop like this..
Variables oVariables;
foreach(Variable element in oVariables)
But i am not able to do this by just using the object of the class Variables in this example that is oVariable. i have to write some function that will return the Collection.
So is there any way i can avoid writing an extra function and get Collection from that.
Any Help is appreciated. Thanks
Here is the code of Variables Class.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualBasic;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
namespace APVariable
{
public class Variables
{
private Collection mCol;
public Variable Add(string Name, object Value, string skey = "")
{
Variable objNewMember = new Variable();
objNewMember.Name = Name;
objNewMember.Value = Value;
if (skey.Length == 0)
{
mCol.Add(objNewMember);
}
else
{
try
{
Information.Err().Clear();
mCol.Add(objNewMember, skey);
if (Information.Err().Number != 0)
{
Information.Err().Clear();
mCol.Add(objNewMember);
}
}
catch { Information.Err(); }
{
}
}
return objNewMember;
objNewMember = null;
}
public int count
{
get
{
return mCol.Count;
}
}
//public void Remove(int vntIndexKey)
//{
// //this can be the int or the string.
// //passes the index or the key of the collection to be removed.
// mCol.Remove(vntIndexKey);
//}
public void Remove(dynamic vntIndexKey)
{
//this can be the int or the string.
//passes the index or the key of the collection to be removed.
mCol.Remove(vntIndexKey);
}
public dynamic newEnum
{
get
{
return mCol.GetEnumerator();
}
}
public Variable this[object vIndex]
{
get
{
Variable result = null;
try
{
result = (Variable)mCol[vIndex];
}
catch
{
}
return result;
}
}
//public IEnumerator GetEnumerator()
//{
// //this property allows you to enumerate
// //this collection with the For...Each syntax
// return mCol.GetEnumerator();
//}
public Variables()
{
mCol = new Collection();
}
~Variables()
{
mCol = null;
}
}
}
You'll need to implement the IEnumerable interface. This will allow to be able to use the foreach on your class. You should also use Colection.
It looks like you've started, you're almost there.
namespace APVariable
{
public class Variables : IEnumerable, IEnumerator
{
private Collection<Variable> mCol;
public Variable Add(string Name, object Value, string skey = "")
{
Variable objNewMember = new Variable();
objNewMember.Name = Name;
objNewMember.Value = Value;
if (skey.Length == 0)
{
mCol.Add(objNewMember);
}
else
{
try
{
Information.Err().Clear();
mCol.Add(objNewMember, skey);
if (Information.Err().Number != 0)
{
Information.Err().Clear();
mCol.Add(objNewMember);
}
}
catch { Information.Err(); }
{
}
}
return objNewMember;
objNewMember = null;
}
public int count
{
get
{
return mCol.Count;
}
}
//public void Remove(int vntIndexKey)
//{
// //this can be the int or the string.
// //passes the index or the key of the collection to be removed.
// mCol.Remove(vntIndexKey);
//}
public void Remove(dynamic vntIndexKey)
{
//this can be the int or the string.
//passes the index or the key of the collection to be removed.
mCol.Remove(vntIndexKey);
}
public dynamic newEnum
{
get
{
return mCol.GetEnumerator();
}
}
public Variable this[object vIndex]
{
get
{
Variable result = null;
try
{
result = (Variable)mCol[vIndex];
}
catch
{
}
return result;
}
}
public IEnumerator GetEnumerator()
{
//this property allows you to enumerate
//this collection with the For...Each syntax
return mCol.GetEnumerator();
}
public Variables()
{
mCol = new Collection<Variable>();
}
~Variables()
{
mCol = null;
}
}
}
</code>
</pre>
I'd recommend subclassing Collection or List or implementing IList or ICollection
Based on this page we've created a Wizard that has three steps. Everything works great, but we have one problem with the code given in the link, which is how it creates the next step instance (copy pasted from the link):
protected override IScreen DetermineNextItemToActivate(IList<IScreen> list, int lastIndex)
{
var theScreenThatJustClosed = list[lastIndex] as BaseViewModel;
var state = theScreenThatJustClosed.WorkflowState;
var nextScreenType = TransitionMap.GetNextScreenType(theScreenThatJustClosed);
var nextScreen = Activator.CreateInstance(nextScreenType, state);
return nextScreen as IScreen;
}
Currently, it looks like this in our project:
protected override IWizardScreen DetermineNextItemToActivate(IList<IWizardScreen> list, int lastIndex)
{
var theScreenThatJustClosed = list[lastIndex];
if (theScreenThatJustClosed == null) throw new Exception("Expecting a screen here!");
if (theScreenThatJustClosed.NextTransition == WizardTransition.Done)
{
TryClose(); // Close the entire Wizard
}
var state = theScreenThatJustClosed.WizardAggregateState;
var nextScreenType = _map.GetNextScreenType(theScreenThatJustClosed);
if (nextScreenType == null) return null;
// TODO: CreateInstance requires all constructors for each WizardStep, even if they aren't needed. This should be different!
var nextScreen = Activator.CreateInstance(nextScreenType, state, _applicationService, _wfdRegisterInstellingLookUp,
_adresService, _userService, _documentStore, _windowManager, _fileStore, _fileUploadService, _dialogService,
_eventAggregator, _aanstellingViewModelFactory);
return nextScreen as IWizardScreen;
}
As you can see, we have quite a few parameters we need in some steps. In step 1 we only need like two, but because of the Activator.CreateInstance(nextScreenType, state, ...); we still need to pass all of them.
What I'd like instead is to use a delegate Factory. We use them at more places in our project, and let AutoFac take care of the rest of the parameters. For each of the three steps we only need a delegate Factory that uses the state.
Because all three uses the same delegate Factory with just state, I've placed this Factory in their Base class:
public delegate WizardBaseViewModel<TViewModel> Factory(AggregateState state);
How I'd like to change the DetermineNextItemToActivate method:
protected override IWizardScreen DetermineNextItemToActivate(IList<IWizardScreen> list, int lastIndex)
{
var theScreenThatJustClosed = list[lastIndex];
if (theScreenThatJustClosed == null) throw new Exception("Expecting a screen here!");
if (theScreenThatJustClosed.NextTransition == WizardTransition.Done)
{
TryClose(); // Close the entire Wizard
}
return _map.GetNextScreenFactoryInstance(state);
}
But now I'm stuck at making the GetNextScreenFactoryInstance method:
public IWizardScreen GetNextScreenFactoryInstance(IWizardScreen screenThatClosed)
{
var state = screenThatClosed.WizardAggregateState;
// This is where I'm stuck. How do I get the instance using the Factory, when I only know the previous ViewModel
// ** Half-Pseudocode
var nextType = GetNextScreenType(screenThatClosed);
var viewModelFactory = get delegate factory based on type?;
var invokedInstance = viewModelFactory.Invoke(state);
// **
return invokedInstance as IWizardScreen;
}
Feel free to change the GetNextScreenFactoryInstance any way you'd like. As long as I can get the next Step-ViewModel based on the previous one in the map.
NOTE: Other relevant code, can be found in the link, but I'll post it here as well to keep it all together:
The WizardTransitionMap (only change is it not being a Singleton anymore, so we can instantiate a map outselves):
public class WizardTransitionMap : Dictionary<Type, Dictionary<WizardTransition, Type>>
{
public void Add<TIdentity, TResponse>(WizardTransition transition)
where TIdentity : IScreen
where TResponse : IScreen
{
if (!ContainsKey(typeof(TIdentity)))
{
Add(typeof(TIdentity), new Dictionary<WizardTransition, Type> { { transition, typeof(TResponse) } });
}
else
{
this[typeof(TIdentity)].Add(transition, typeof(TResponse));
}
}
public Type GetNextScreenType(IWizardScreen screenThatClosed)
{
var identity = screenThatClosed.GetType();
var transition = screenThatClosed.NextTransition;
if (!transition.HasValue) return null;
if (!ContainsKey(identity))
{
throw new InvalidOperationException(String.Format("There are no states transitions defined for state {0}", identity));
}
if (!this[identity].ContainsKey(transition.Value))
{
throw new InvalidOperationException(String.Format("There is no response setup for transition {0} from screen {1}", transition, identity));
}
return this[identity][transition.Value];
}
}
Our InitializeMap-method:
protected override void InitializeMap()
{
_map = new WizardTransitionMap();
_map.Add<ScreenOneViewModel, ScreenTwoViewModel>(WizardTransition.Next);
_map.Add<ScreenTwoViewModel, ScreenOneViewModel>(WizardTransition.Previous);
_map.Add<ScreenTwoViewModel, ScreenThreeViewModel>(WizardTransition.Next);
_map.Add<ScreenThreeViewModel, ScreenTwoViewModel>(WizardTransition.Previous);
_map.Add<ScreenThreeViewModel, ScreenThreeViewModel>(WizardTransition.Done);
}
We've changed the code:
The WizardTransitionMap now accepts Delegates. Also, instead of retrieving the type by the WizardTransition-enum value (Next, Previous, etc.), we now retrieve the Factory-invoke based on the next Type (so the inner Dictionary is reversed). So, this is our current WizardTransitionMap:
using System;
using System.Collections.Generic;
namespace NatWa.MidOffice.CustomControls.Wizard
{
public class WizardTransitionMap : Dictionary<Type, Dictionary<Type, Delegate>>
{
public void Add<TCurrentScreenType, TNextScreenType>(Delegate delegateFactory)
{
if (!ContainsKey(typeof(TCurrentScreenType)))
{
Add(typeof(TCurrentScreenType), new Dictionary<Type, Delegate> { { typeof(TNextScreenType), delegateFactory } });
}
else
{
this[typeof(TCurrentScreenType)].Add(typeof(TNextScreenType), delegateFactory);
}
}
public IWizardScreen GetNextScreen(IWizardScreen screenThatClosed)
{
var identity = screenThatClosed.GetType();
var state = screenThatClosed.State;
var transition = screenThatClosed.NextScreenType;
if (!ContainsKey(identity))
{
throw new InvalidOperationException(String.Format("There are no states transitions defined for state {0}", identity));
}
if (!this[identity].ContainsKey(transition))
{
throw new InvalidOperationException(String.Format("There is no response setup for transition {0} from screen {1}", transition, identity));
}
if (this[identity][transition] == null)
return null;
return (IWizardScreen)this[identity][transition].DynamicInvoke(state);
}
}
}
Our InitializeMap is now changed to this:
protected override void InitializeMap()
{
_map = new WizardTransitionMap();
_map.Add<ScreenOneViewModel, ScreenTwoViewModel>(_screenTwoFactory);
_map.Add<ScreenTwoViewModel, ScreenOneViewModel>(_screenOneFactory);
_map.Add<ScreenTwoViewModel, ScreenThreeViewModel>(_screenThreeFactory);
_map.Add<ScreenThreeViewModel, ScreenTwoViewModel>(_screenTwoFactory);
_map.Add<ScreenThreeViewModel, ScreenThreeViewModel>(null);
}
And our DetemineNexttemToActivate method to this:
protected override IWizardScreen DetermineNextItemToActivate(IList<IWizardScreen> list, int previousIndex)
{
var theScreenThatJustClosed = list[previousIndex];
if (theScreenThatJustClosed == null) throw new Exception("Expecting a screen here!");
var nextScreen = _map.GetNextScreen(theScreenThatJustClosed);
if (nextScreen == null)
{
TryClose();
return ActiveItem; // Can't return null here, because Caliburn's Conductor will automatically get into this method again with a retry
}
return nextScreen;
}
We also removed our entire WizardBaseViewModel and just let every Step-ViewModel implement the IWizardScreen:
public interface IWizardScreen : IScreen
{
AggregateState State { get; }
Type NextScreenType { get; }
void Next();
void Previous();
}
With the following implementation in our ScreenOneViewModel:
public AggregateState State { get { return _state; } }
public Type NextScreenType { get; private set; }
public void Next()
{
if (!IsValid()) return;
NextScreenType = typeof(ScreenTwoViewModel);
TryClose();
}
public void Previous()
{
throw new NotImplementedException(); // Isn't needed in first screen, because we have no previous
}
And the following implementation in our ScreenThreeViewModel:
public AggregateState State { get { return _state; } }
public Type NextScreenType { get; private set; }
public void Next()
{
NextScreenType = typeof(ScreenThreeViewModel); // Own type, because we have no next
TryClose();
}
public void Previous()
{
NextScreenType = typeof(ScreenTwoViewModel);
TryClose();
}
And each Step-ViewModel has its own delegate Factory, like this one for ScreenTwoViewModel:
public delegate ScreenTwoViewModel Factory(AggregateState state);
This is a basically a class library project which is somehow exposed as a WCF service. The code below is a part of the Data Access Layer. 'db' is an object of a DataContext class. To save a file, we do the following-
public static Guid SaveFile(FileDetails fileDetails)
{
System.Nullable<Guid> id = null;
SystemDataContext.UsingWrite(db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
});
return id ?? Guid.Empty;
}
Then, the below would execute-
public static void UsingWrite(Action<SoftCashCreditDBDataContext> action)
{
using (var context = new SystemDataContext())
{
try
{
action(context.Write);
}
catch (Exception ex)
{
DataAccessExceptionHandler.HandleExcetion(ex, Config.DataLayerPolicy);
}
}
}
public SystemDataContext()
{
if (_stack == null)
{
_stack = new Stack<SystemDataContext>();
this.Depth = 1;
this.Read = new SoftCashCreditDBDataContext(Config.ReadDatabaseConnection);
this.Write = new SoftCashCreditDBDataContext(Config.WriteDatabaseConnection);
}
else
{
var parent = _stack.Peek();
/// Increment level of node.
this.Depth = parent.Depth + 1;
/// Copy data context from the parent
this.Read = parent.Read;
this.Write = parent.Write;
}
_stack.Push(this);
}
public int Depth { get; private set; }
public bool IsRoot { get { return this.Depth == 1; } }
[ThreadStatic]
private static Stack<SystemDataContext> _stack = null;
public SoftCashCreditDBDataContext Read { get; private set; }
public SoftCashCreditDBDataContext Write { get; private set; }
#region IDisposable Members
public void Dispose()
{
var context = _stack.Pop();
if (context.IsRoot == true)
{
context.Read.Dispose();
context.Write.Dispose();
_stack = null;
}
}
#endregion
}
They have implemented LINQ to SQL here, and created a DBContext class. The 'SaveFileData()' method is actually part of that class, where it just calls an SP inside to save the file.
What I did not follow-
What exactly does the call to UsingWrite() do here? What is passed to the 'Action action' parameter, and what is it doing?
I understand your confusion. They use 2 delegates.
This is passed to the action parameter:
db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
}
So when UsingWrite is called, the SoftCashCreditDBDataContext delegate which was set in the Write delegate will call SaveFileData.
A simplified example to help you understand Action:
public void Main()
{
Test(x => Debug.Write(x));
}
private void Test(Action<string> testAction)
{
testAction("Bla");
}
This function will call Debug.Write with the argument x, which is a string that is passed to the test action function.