Pls see this first:
Good Coding Practices
So, this is my design.
Website
2.Business Logic Layer
3.DALFacade (we use dalfacade to hide the data access, because we use 2 different stores, sql and db2)
4.DAL
In the DAL we use unit of work pattern and repository pattern.
1. The big question here is: If the code below will run ok for transactions that are created from the business logic.?
Page:
public partial class NewBonusRequest : System.Web.UI.Page
{
#region Constructor and Instantiation of Business Logic
/// <summary>
/// Property that holds the Business Logic type to call methods
/// </summary>
public IRequestBL RequestBL { get; private set; }
/// <summary>
/// The default constructor will use the default implementation of the business logic interface
/// </summary>
public Request()
: this(new RequestBL())
{
}
/// <summary>
/// The constructor accepts a IEcoBonusRequestFacade type
/// </summary>
/// <param name="ecoBonusRequestBL">IEcoBonusRequestFacade type</param>
public NewRequest(IRequestBL RequestBL)
{
RequestBL = RequestBL;
}
#endregion
protected void PageLoad(object sender, EventArgs e)
{
if(!Page.IsPostBack)
{
}
}
#region Control Events
protected void BtnSubmitRequestClick(object sender, EventArgs e)
{
var request= new Request
{
IsOnHold = true
//All other properties go here.
};
RequestBL.Save(request);
}
Business Logic Code.
public interface IRequestBL
{
void Save(Request request);
}
/// <summary>
/// Class in charge of the business logic for EcoBonusRequest
/// </summary>
public class RequestBL : IRequestBL
{
/// <summary>
/// <summary>
/// Saves a new ecobonus request into database and evaluate business rules here
/// </summary>
/// <param name="ecoBonusWorkflow">EcoBonusWorkflow entity</param>
public void Save(Request Request)
{
using (var scope = new TransactionScope())
{
Request.Save(request);
// Call to other DALCFacade methods that insert data in different tables
// OtherObject.Save(otherobject)
scope.Complete();
}
}
}
Yes in the same thread, EF will properly consider transaction scope if it exists. EF will not create new transaction if it's already in one.
However, you must be careful because if you query your database without transaction then you will get dirty reads. Because EF will not read anything in transaction if it does not exist but it creates new transaction if it does not exist while saving changes.
In your code you are only saving changes in transaction but you should be careful while reading and you should encapsulate your queries also in scope in smaller units.
Related
Background
I am writing a WPF application using the MVVM pattern. I am using a Messenger to communicate between ViewModels as I learned in various tutorials. I am using the implementation of a Messenger class found in the Code section of this post (thanks to #Dalstroem WPF MVVM communication between View Model and Gill Cleeren at Pluralsight).
Due to the large number of Views/VMs needed by my app, each ViewModel is instantiated at the time a View is required and disposed subsequently (view-first, VM specified as DataContext of View).
Issue
The constructor of each ViewModel loads resources (Commands, Services, etc.) as necessary, and registers for messages of interest. Messages that were sent from a previously existing ViewModels are not picked up by new ViewModels.
Thus, I cannot communicate between ViewModels using my Messenger class.
Thoughts
Some examples I've seen use a ViewModelLocator that instantiates all ViewModels upfront. The Views, when created, simply pull the existing ViewModel from the VML. This approach means that Messages will always be received and available in every ViewModel. My concern is that with 30+ ViewModels that all load a substantial amount of data with use, my app will become slow with extended use as each View is used (no resources ever disposed).
I've considered finding a way to store Messages and subsequently resend all messages to any registered recipients. If implemented, this would allow me to call a Resend method of sorts after registering for messages in each ViewModel. I have a few concerns with this approach, including the accumulation of messages over time.
I'm not sure what I'm doing wrong or if there are approachs I just don't know about.
Code
public class Messenger
{
private static readonly object CreationLock = new object();
private static readonly ConcurrentDictionary<MessengerKey, object> Dictionary = new ConcurrentDictionary<MessengerKey, object>();
#region Default property
private static Messenger _instance;
/// <summary>
/// Gets the single instance of the Messenger.
/// </summary>
public static Messenger Default
{
get
{
if (_instance == null)
{
lock (CreationLock)
{
if (_instance == null)
{
_instance = new Messenger();
}
}
}
return _instance;
}
}
#endregion
/// <summary>
/// Initializes a new instance of the Messenger class.
/// </summary>
private Messenger()
{
}
/// <summary>
/// Registers a recipient for a type of message T. The action parameter will be executed
/// when a corresponding message is sent.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="recipient"></param>
/// <param name="action"></param>
public void Register<T>(object recipient, Action<T> action)
{
Register(recipient, action, null);
}
/// <summary>
/// Registers a recipient for a type of message T and a matching context. The action parameter will be executed
/// when a corresponding message is sent.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="recipient"></param>
/// <param name="action"></param>
/// <param name="context"></param>
public void Register<T>(object recipient, Action<T> action, object context)
{
var key = new MessengerKey(recipient, context);
Dictionary.TryAdd(key, action);
}
/// <summary>
/// Unregisters a messenger recipient completely. After this method is executed, the recipient will
/// no longer receive any messages.
/// </summary>
/// <param name="recipient"></param>
public void Unregister(object recipient)
{
Unregister(recipient, null);
}
/// <summary>
/// Unregisters a messenger recipient with a matching context completely. After this method is executed, the recipient will
/// no longer receive any messages.
/// </summary>
/// <param name="recipient"></param>
/// <param name="context"></param>
public void Unregister(object recipient, object context)
{
object action;
var key = new MessengerKey(recipient, context);
Dictionary.TryRemove(key, out action);
}
/// <summary>
/// Sends a message to registered recipients. The message will reach all recipients that are
/// registered for this message type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
public void Send<T>(T message)
{
Send(message, null);
}
/// <summary>
/// Sends a message to registered recipients. The message will reach all recipients that are
/// registered for this message type and matching context.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
/// <param name="context"></param>
public void Send<T>(T message, object context)
{
IEnumerable<KeyValuePair<MessengerKey, object>> result;
if (context == null)
{
// Get all recipients where the context is null.
result = from r in Dictionary where r.Key.Context == null select r;
}
else
{
// Get all recipients where the context is matching.
result = from r in Dictionary where r.Key.Context != null && r.Key.Context.Equals(context) select r;
}
foreach (var action in result.Select(x => x.Value).OfType<Action<T>>())
{
// Send the message to all recipients.
action(message);
}
}
protected class MessengerKey
{
public object Recipient { get; private set; }
public object Context { get; private set; }
/// <summary>
/// Initializes a new instance of the MessengerKey class.
/// </summary>
/// <param name="recipient"></param>
/// <param name="context"></param>
public MessengerKey(object recipient, object context)
{
Recipient = recipient;
Context = context;
}
/// <summary>
/// Determines whether the specified MessengerKey is equal to the current MessengerKey.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
protected bool Equals(MessengerKey other)
{
return Equals(Recipient, other.Recipient) && Equals(Context, other.Context);
}
/// <summary>
/// Determines whether the specified MessengerKey is equal to the current MessengerKey.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((MessengerKey)obj);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
unchecked
{
return ((Recipient != null ? Recipient.GetHashCode() : 0) * 397) ^ (Context != null ? Context.GetHashCode() : 0);
}
}
}
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Update
The way my application is architectured, there is a ViewModel used with my MainWindow, which serves as a sort of basic shell. It provides a primary layout with a few controls for navigation and login/logout, etc.
All subsequent Views are displayed inside a ContentControl inside the MainWindow (taking up most of the window real estate). The ContentControl is bound to a "CurrentView" property of my "MainWindowViewModel." The MainWindowViewModel instantiates a custom Navigation service I created for the purpose of selecting and returning the appropriate View to update my "CurrentView" property.
This architecture may be unorthodox, but I wasn't sure how navigation is typically accompished without using out-the-box things like TabControl.
Idea
Building on ideas from #axlj, I could keep an ApplicationState object as a property of my "MainWindowViewModel." Using my Messenger class, I could pub an ApplicationState message whenever injecting a new View in my MainWindow. The ViewModels for each View would, of course, sub this message and gain state immediately upon creation. If any ViewModels make changes to their copy of ApplicationState, they would pub a message. The MainWindowViewModel would then be updated via its subscription.
I would recommend against "storing messages" -- even if you work out a good pattern for recovering messages, you'll still end up with logic that is difficult to test. This is really a sign that your view models need to know too much about the application state.
In the case of view model locator -- a well designed view model locator will likely lazy-load the view models, which would leave you in the same place you are right now.
Option 1
Instead, consider using UserControls and DependencyProperties where possible.
Option 2
If your views are in fact really views, then consider a singleton context class that maintains the necessary state and inject that into your view models. The benefit of this method is that your context class can implement INotifyPropertyChanged and any changes will automatically be propagated to your consuming views.
Option 3
If you're navigating between views, you may want to implement a Navigation service similar to something described here.
interface INavigationService(string location, object parameter) {}
In this case, your parameter is considered your state object. The new view model receives the model data from the view you're navigating away from.
This blog post is helpful in explaining best practices around when to use view models and user controls.
...and registers for messages of interest. Messages that were sent from a previously existing ViewModels are not picked up by new ViewModels. Thus, I cannot communicate between ViewModels using my Messenger class.
Why exactly do your VMs need to be aware of historical messages?
Generally messaging should be pub/sub; messages are published ("pub") and anyone who might be interested in specific messages subscribes ("sub") to receive those. The publisher shouldn't care what is done with the message - that is up to the subscriber.
If you have some obscure business case that requires knowledge of previous messages then you should create your own message queue mechanism (i.e. store them in a database and retrieve them based on datetime).
***Just for learning purpose***
Recently I just knew the word cache and cache mechanism and generally understand that the cache mechanism is a good thing on system responding performance and reduce many interacting with database.
And based on the talking with someone else, they told me the general idea that we can create an independent library and cache the data retrieving from database and once we need it in our business layer, then we can retrieve it from the cache layer.
And they also shared something but not very detailed that the database can update the cache layer automatically when the data in database refreshed, like updating, adding and deleting.
So my questions comes, how does database know and update cache layer proactively and automatically? Can anybody share something with me? or are there any existing frameworks, open source solutions?
I would much appreciate for your kindly help. I'm looking forward to hearing from you my friend.
Try this third party cache: CacheCrow, it is a simple LFU based cache.
Install using powershell command in visual studio: Install-Package CacheCrow
Code Snippet:
// initialization of singleton class
ICacheCrow<string, string> cache = CacheCrow<string, string>.Initialize(1000);
// adding value to cache
cache.Add("#12","Jack");
// searching value in cache
var flag = cache.LookUp("#12");
if(flag)
{
Console.WriteLine("Found");
}
// removing value
var value = cache.Remove("#12");
For more information you can visit: https://github.com/RishabKumar/CacheCrow
Jacob,
Let me give you an example...
In the data layer when we are going to retrieve a list of objects that should be cached from the database we could to something like this.
if (!CacheHelper.Get("AllRoles", out entities))
{
var items = _context.Set<Roles>().ToList();
entities = items;
var cachableEntities = entities.ToList();
CacheHelper.Add(cachableEntities, "AllRoles");
}
return entities;
You'll notice that I have Cache helper that will search the cache for the key "AllRoles" if it finds the cache it will return the entities from the cache. If it cant find it it will get the data from the database and Create the cache with the key.
Additionally, every time we add/delete/or change an item in this table we could simple destroy this cache.
CacheHelper.Clear(CacheKey);
So answering the question, in this sample the database doesn't know when to recreate the cache, the application logic does.
Here a sample of a Cache Helpers you may use....
using System;
using System.Collections.Generic;
using System.Web;
namespace Core.Helpers
{
public static class CacheHelper
{
public static List<string> GetCacheKeys()
{
List<string> keys = new List<string>();
// retrieve application Cache enumerator
var enumerator = System.Web.HttpRuntime.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
keys.Add(enumerator.Key.ToString());
}
return keys;
}
/// <summary>
/// Insert value into the cache using
/// appropriate name/value pairs
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="o">Item to be cached</param>
/// <param name="key">Name of item</param>
public static void Add<T>(T o, string key)
{
// NOTE: Apply expiration parameters as you see fit.
// I typically pull from configuration file.
// In this example, I want an absolute
// timeout so changes will always be reflected
// at that time. Hence, the NoSlidingExpiration.
if (HttpContext.Current != null)
HttpContext.Current.Cache.Insert(
key,
o,
null,
DateTime.Now.AddMinutes(1440),
System.Web.Caching.Cache.NoSlidingExpiration);
}
/// <summary>
/// Remove item from cache
/// </summary>
/// <param name="key">Name of cached item</param>
public static void Clear(string key)
{
if (HttpContext.Current != null)
HttpContext.Current.Cache.Remove(key);
}
/// <summary>
/// Check for item in cache
/// </summary>
/// <param name="key">Name of cached item</param>
/// <returns></returns>
public static bool Exists(string key)
{
var exists= HttpContext.Current != null && HttpContext.Current.Cache[key] != null;
return exists;
}
/// <summary>
/// Retrieve cached item
/// </summary>
/// <typeparam name="T">Type of cached item</typeparam>
/// <param name="key">Name of cached item</param>
/// <param name="value">Cached value. Default(T) if
/// item doesn't exist.</param>
/// <returns>Cached item as type</returns>
public static bool Get<T>(string key, out T value)
{
try
{
if (!Exists(key))
{
value = default(T);
return false;
}
value = (T)HttpContext.Current.Cache[key];
}
catch
{
value = default(T);
return false;
}
return true;
}
}
}
I am creating an SQLite Database in Visual Studio with Xamarin in C#.
I should note that this is for android only.
I am trying to make it so I am able to insert data into the SQLite database but I am unsure how.
I have been following this but I'm still unsure.
Here is the method I am trying to create.
/// <summary>
/// Insert a single ping group into the SQLite ping database.
/// </summary>
/// <param name="pingGroup"></param>
public void AddUnsynchronizedPing(PingGroup pingGroup)
{
// TODO: Add the passed ping group parameter into the SQLite database as new/unsynchronized.
if (pingGroup != null)
{
// Add ping group to the database.
// Add pings to the database.
// Maybe one step, maybe done separately.
// If done separately, must set Ping.PingGroupID to ID of original ping group.
}
}
For context, here is the entire class.
namespace BB.Mobile
{
/// <summary>
/// A class to provide a single interface for interacting with all SQLite data operations for stored tracking points.
/// </summary>
///
class DataManager
{
private SQLiteConnection db = null;
public DataManager()
{
if (this.db == null)
{
string dbPath = Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal),
"bb.db3");
db = new SQLiteConnection(dbPath);
db.CreateTable<Ping>();
db.CreateTable<PingGroup>();
}
}
/// <summary>
/// Will compile and return all matching unsynchronized ping data from the SQLite database.
/// </summary>
/// <returns></returns>
public List<PingGroup> GetUnsynchronizedPings()
{
List<PingGroup> unsynchronizedPings = new List<PingGroup>();
// TODO: Retrieve all unsynchronized pings from the SQLite database and return them to the caller.
//var pGroup = db.Get<PingGroup>();
//var pGroupList = db.List<PingGroup>();
var pGroups = db.Table<PingGroup>();
foreach (var pGroup in pGroups)
{
}
return unsynchronizedPings;
}
/// <summary>
/// Insert a single ping group into the SQLite ping database.
/// </summary>
/// <param name="pingGroup"></param>
public void AddUnsynchronizedPing(PingGroup pingGroup)
{
// TODO: Add the passed ping group parameter into the SQLite database as new/unsynchronized.
if (pingGroup != null)
{
// Add ping group to the database.
// Add pings to the database.
// Maybe one step, maybe done separately.
// If done separately, must set Ping.PingGroupID to ID of original ping group.
}
}
/// <summary>
/// Mark all open and unsynchronized pings in the database as synchronized.
/// </summary>
public void SetAllPingsSynchronized()
{
db.DeleteAll<PingGroup>();
db.DeleteAll<Ping>();
}
}
}
Thank you in advance.
To insert the object to sqlite database, you can just use something like:
void InsertPing(Ping p)
{
db.Insert(p);
}
void InsertGroupOfPings(IEnumerable<Ping> pings)
{
db.InsertAll(pings);
}
and to retrieve objects (for example):
List<Ping> GetPings()
{
// I assume here that Ping object has property named Synchronized
return db.Query<Ping>("select * from Ping where Synchronized = 0");
}
The SQLite library creates its tables according to your class definitions, so you can think about the properties of the class as of columns inside the table.
In my mvc application during certain times of the year we want to show one of two links. Basically I have to switch the link when I get a call from management. So, I thought instead of having to recompile the app I would add a custom app setting to the web.config file. Then I created a wrapper so that it is strongly typed. Now, my problem is I don't know where to execute the logic. Should add a property to my view model and set it in the controller based on the configuration setting value? Or should I read it directly in my View and toggle between the two links? I'm pretty sure this only belongs in the view or the controller, and not the service layer, since it is used specifically for UI stuff.
Details.cshtml //current code
#if(Search.App.ParcelDetailDisplayMode == Search.App.DisplayMode.Tax ){
<a id="tax-link" href="#taxlink" title="View Tax Bill on Tax Collectors Website">Tax Bill</a>
}
else if(Search.App.ParcelDetailDisplayMode == Search.App.DisplayMode.Trim ){
<a id="trim-link" href="#trimlink" title="View your TRIM notice online">Trim Notice</a>
}
web.config
<add key="ParcelDetailDisplayMode" value="Tax"/>
config wrapper
namespace Search
{
/// <summary>
/// The app.
/// </summary>
public static class App
{
/// <summary>
/// Gets the tax bill link.
/// </summary>
public static string TaxBillLink
{
get
{
return ConfigurationManager.AppSettings["TaxBillLink"];
}
}
/// <summary>
/// Gets the trim notice link.
/// </summary>
public static string TrimNoticeLink
{
get
{
return ConfigurationManager.AppSettings["TrimLink"];
}
}
/// <summary>
/// Gets the map link.
/// </summary>
public static string MapLink
{
get
{
return ConfigurationManager.AppSettings["MapLink"];
}
}
/// <summary>
/// Gets the update address link.
/// </summary>
public static string UpdateAddressLink
{
get
{
return ConfigurationManager.AppSettings["UpdateAddressLink"];
}
}
/// <summary>
/// Gets the release name.
/// </summary>
public static string ReleaseName
{
get
{
return ConfigurationManager.AppSettings["ReleaseName"];
}
}
/// <summary>
/// Gets the parcel detail display mode.
/// </summary>
public static DisplayMode ParcelDetailDisplayMode
{
get
{
var r = DisplayMode.Tax;
DisplayMode.TryParse(ConfigurationManager.AppSettings["ParcelDetailDisplayMode"], out r);
return r;
}
}
/// <summary>
/// The display mode.
/// </summary>
public enum DisplayMode
{
/// <summary>
/// The trim.
/// </summary>
Trim,
/// <summary>
/// The tax.
/// </summary>
Tax
}
}
}
I would say it does not really matter. Adding it as a property of your model feels to give a little bit more separation.
What does matter though is that your wrapper is static. This will make it really difficult to mock it for the purpose of unit testing (or any other purpose)
There should be no logic in the controller.
Read this for example: Where should I put my controller business logic in MVC3
or this one: https://softwareengineering.stackexchange.com/questions/165444/where-to-put-business-logic-in-mvc-design
I know it's tempting but the less logic you put there the best you will find yourself in the future.
the answer in my opinion is:
You should read your property in a business layer benhead the controller and pass it all the way up to the view in a model object.
I agree with Maurizio in general that all business logic should be in some service/business logic layer. However in this case since you're only fetching a value from web.config whether, in your controller action, you do:
var someValue = App.TaxBillLink;
or you do:
var someValue = _linkService.GetTodaysLink();
really doesn't matter much unless there is some sort of logic there that needs to be unit tested.
I created a simple Caching Data Access Layer that has caching using the Enterprise Library Caching Application Block and also makes use of SQL Query Notification - therefore not supporting any queries which are not valid for query notification.
Background: This was put in place after the application was developed in order to lighten the load on the database and speed up the application. The main use of this DAL is for pulling data that is not expected to change very often such as data in Look Up Tables (presented in drop downs on the UI, etc).
It is mainly used like the following example:
var cachingDal = new CachingDataAccessLayer();
var productTypes = cachingDal.LoadData<ProductType>();
Where ProductType is a Linq to SQL table. I am curious to see what people think of the implementation I came up with and if it is horrible or amazing.
Here's the code. Looking for any suggestions, criticisms, etc etc. Keep in mind I didn't choose the technology and am building on top of an existing system so switching data access stories is not really my call.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using MyDatabase;
public class CachingDataAccessLayer
{
#region Cache Keys
private const string CacheManagerName = "CachingDataAccessLayer";
#endregion
#region Database
/// <summary>
/// Instantiate new MyDataContext
/// </summary>
/// <returns></returns>
private MyDataContext DatabaseConnection()
{
// instantiate database connection
var database = new MyDataContext(Constants.DatabaseConnectionString);
// set transaction isolation level to read committed
database.ExecuteQuery(typeof(string), "SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
return database;
}
#endregion
#region Generic Data Access with Caching
/// <summary>
/// Calls .Exists on list using predicate and if it evaluates to false, adds records to list using predicate.
/// </summary>
/// <typeparam name="TEntity">Database table</typeparam>
/// <param name="list">List to add records to</param>
/// <param name="predicate">The delagate that defines the conditions of elements to search for.</param>
public void AddRecordsIfNeeded<TEntity>(ref List<TEntity> list, Predicate<TEntity> predicate) where TEntity : class
{
// check if items are in list based on predicate and if not, add them to the list
if (!list.Exists(predicate))
{
list.AddRange(LoadData<TEntity>(predicate.Invoke));
}
}
/// <summary>
/// Retrieve all records of type TEntity from the cache if available with filter Active = true (if Active property exists).<br/>
/// If data is not available in cache go directly to the database.<br/>
/// In addition, sets up query notification and refreshes cache on database change.
/// </summary>
/// <typeparam name="TEntity">Database table to retrieve.</typeparam>
/// <returns>returns List of TEntity</returns>
public List<TEntity> LoadData<TEntity>() where TEntity : class
{
// default filter is no filter
Func<TEntity, bool> predicate = delegate { return true; };
// check for active property
var activeProperty = typeof (TEntity).GetProperty("Active");
// if active property exists and is a boolean, set predicate to filter Active == true
if (activeProperty != null)
if (activeProperty.PropertyType.FullName == typeof (bool).FullName)
predicate = (x => (bool) activeProperty.GetValue(x, null));
// load data & return
return LoadData(predicate);
}
/// <summary>
/// Retrieve all records of type TEntity from the cache if available.<br/>
/// If data is not available in cache go directly to the database.<br/>
/// In addition, sets up query notification and refreshes cache on database change.
/// </summary>
/// <typeparam name="TEntity">Database table to retrieve.</typeparam>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <returns>returns List of TEntity</returns>
public List<TEntity> LoadData<TEntity>(Func<TEntity, bool> predicate) where TEntity : class
{
// default is to not refresh cache
return LoadData(predicate, false);
}
/// <summary>
/// Retrieve all records of type TEntity from the cache if available.<br/>
/// If data is not available in cache or refreshCache is set to true go directly to the database.<br/>
/// In addition, sets up query notification and refreshes cache on database change.
/// </summary>
/// <typeparam name="TEntity">Database table to retrieve.</typeparam>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="refreshCache">If true, ignore cache and go directly to the database and update cache.</param>
/// <returns></returns>
public List<TEntity> LoadData<TEntity>(Func<TEntity, bool> predicate, bool refreshCache) where TEntity : class
{
// instantiate database connection
using (var database = DatabaseConnection())
{
// instantiate the cache
var cache = CacheFactory.GetCacheManager(CacheManagerName);
// get cache key name
var cacheKey = typeof(TEntity).Name;
// if the value is in the cache, return it
if (cache.Contains(cacheKey) && !refreshCache)
// get data from cache, filter it and return results
return (cache.GetData(cacheKey) as List<TEntity>).Where(predicate).ToList();
// retrieve the data from the database
var data = from x in database.GetTable<TEntity>()
select x;
// if value is in cache, remove it
if (cache.Contains(cacheKey))
cache.Remove(cacheKey);
// add unfiltered results to cache
cache.Add(cacheKey, data.ToList());
Logger.Write(string.Format("Added {0} to cache {1} with key '{2}'", typeof(TEntity).Name, CacheManagerName, cacheKey));
// set up query notification
SetUpQueryNotification<TEntity>();
// return filtered results
return data.Where(predicate).ToList();
}
}
#endregion
#region Query Notification
public void SetUpQueryNotification<TEntity>() where TEntity : class
{
// get database connection
var database = DatabaseConnection();
// set up query notification
using (var sqlConnection = new SqlConnection(Constants.DatabaseConnectionString))
{
// linq query
var query = from t in database.GetTable<TEntity>()
select t;
var command = database.GetCommand(query);
// create sql command
using (var sqlCommand = new SqlCommand(command.CommandText, sqlConnection))
{
// get query parameters
var sqlCmdParameters = command.Parameters;
// add query parameters to dependency query
foreach (SqlParameter parameter in sqlCmdParameters)
{
sqlCommand.Parameters.Add(new SqlParameter(parameter.ParameterName, parameter.SqlValue));
}
// create sql dependency
var sqlDependency = new SqlDependency(sqlCommand);
// set up query notification
sqlDependency.OnChange += sqlDependency_OnChange<TEntity>;
// open connection to database
sqlConnection.Open();
// need to execute query to make query notification work
sqlCommand.ExecuteNonQuery();
}
}
Logger.Write(string.Format("Query notification set up for {0}", typeof(TEntity).Name));
}
/// <summary>
/// Calls LoadData of type TEntity with refreshCache param set to true.
/// </summary>
/// <typeparam name="TEntity">Database table to refresh.</typeparam>
void RefreshCache<TEntity>() where TEntity : class
{
// refresh cache
LoadData<TEntity>(delegate { return true; }, true);
}
/// <summary>
/// Refreshes data in cache for type TEntity if type is Delete, Insert or Update.<br/>
/// Also re-sets up query notification since query notification only fires once.
/// </summary>
/// <typeparam name="TEntity">Database table</typeparam>
void sqlDependency_OnChange<TEntity>(object sender, SqlNotificationEventArgs e) where TEntity : class
{
var sqlDependency = sender as SqlDependency;
// this should never happen
if (sqlDependency == null)
return;
// query notification only happens once, so remove it, it will be set up again in LoadData
sqlDependency.OnChange -= sqlDependency_OnChange<TEntity>;
// if the data is changed (delete, insert, update), refresh cache & set up query notification
// otherwise, just set up query notification
if (e.Info == SqlNotificationInfo.Delete || e.Info == SqlNotificationInfo.Insert || e.Info == SqlNotificationInfo.Update)
{
// refresh cache & set up query notification
Logger.Write(string.Format("sqlDependency_OnChange (Info: {0}, Source: {1}, Type: {2}). Refreshing cache for {3}", e.Info, e.Source, e.Type, typeof(TEntity).Name));
RefreshCache<TEntity>();
}
else
{
// set up query notification
SetUpQueryNotification<TEntity>();
}
}
#endregion
}
Personally, I'd suggest using the Repository pattern, where you have an IRepository.
Then, in real terms you could use an IoC container to provide your app with a CacheRepository for some static types that uses the caching system in the first instance and either automatically delegates on to a LinqToSqlRepository where data isn't found, or returns null and allows you to deal with populating the cache yourself.
If the data isn't expected to change very much and it's used for the UI such as for drop-downs etc., why not cache the data on client's machine? We did this for an application we built a while back. We cached almost all of our "look-up" type data in files on the client machine and then built a mechanism to invalidate the data when it was modified in the database. This was very fast and worked well for us.
BTW, are you aware that L2S does it's own caching?