I am working on a project where I plan on using Redis as persistent data storage, however the task at hand, I am working on a generic Object cache. and as a huge fan of LINQ I have started designing a cache which does support this.
public ConcurrentBag<Object> Cache = new ConcurrentBag<object>();
public List<T> FindBy<T>(Func<T, bool> predicate) where T : class
{
var tmp = new List<T>();
foreach (var i in Cache)
{
try
{
T obj = i as T;
if (obj != null)
tmp.Add(obj);
}
catch { }
}
return tmp.Where(predicate).ToList();
}
I am afraid that when the object cache grows large it will become inefficient. (I estimate 500k-1m objects)
I was hoping that it would be possible to use something like this
public ConcurrentBag<Object> Cache = new ConcurrentBag<object>();
public List<T> FindBy<T>(Func<T, bool> predicate) where T : class
{
return Cache.Where<T>(predicate).ToList();
}
Hopefully I am not all off-track here? Any suggestions are welcome :)
Hash your genric type and save list of specific type..
Something like:
Dictionary<Type,List<T>>
Then get value by type key and query as you wanted
Since you estimate a lot of items in the cache and the operations on the cache will be type specific, you could use multiple bags wrapped into a dictionary. That would speed up finding the subset of the cache of type of interest and would be ideal if the cache contained many minimal subsets of different types.
readonly IDictionary<Type, ConcurrentBag<object>> _cache = new ConcurrentDictionary<Type, ConcurrentBag<object>>();
public List<T> FindBy<T>(Func<T, bool> predicate) where T : class
{
// Check if items of type {T} exist in the cache.
ConcurrentBag<object> bag;
if (_cache.TryGetValue(typeof (T), out bag))
{
// Cast, apply predicate and return.
return bag.Cast<T>().Where(predicate).ToList();
}
// Return an empty list.
return new List<T>();
}
Of course now you also need to handle adding items properly to the cache to ensure that different types will be put into their corresponding bags.
Big thanks to both Discosultan and user1190916 Whom both pointed me in the right direction in what I needed to get a CRUD cached-object repository with full LINQ support using Redis for persistent storage (Client ServiceStack.Redis), this is what I have managed to conjure up thus far.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Collections.Concurrent;
using ServiceStack.Redis;
namespace RedisTestRepo
{
class Program
{
//public static DataRepository Db;
static void Main(string[] args)
{
Repo r = new Repo();
// We do not touch sequence, by running example we can see that sequence will give Users new unique Id.
// Empty data store.
Console.WriteLine("Our User Data store should be empty.");
Console.WriteLine("Users In \"Database\" : {0}\n", r.All<User>().Count);
// Add imaginary users.
Console.WriteLine("Adding 100 imaginairy users.");
for (int i = 0; i < 99; i++)
r.Create<User>(new User { Id = r.Next<User>(), Name = "Joachim Nordvik" });
// We should have 100 users in data store.
Console.WriteLine("Users In \"Database\" : {0}\n", r.All<User>().Count);
// Lets print 10 users from data store.
Console.WriteLine("Order by Id, Take (10) and print users.");
foreach (var u in r.All<User>().OrderBy(z => z.Id).Take(10))
{
Console.WriteLine("ID:{0}, Name: {1}", u.Id, u.Name);
// Lets update an entity.
u.Name = "My new Name";
r.Update<User>(x=>x.Id == u.Id, u);
}
// Lets print 20 users from data store, we already edited 10 users.
Console.WriteLine("\nOrder by Id, Take (20) and print users, we previously edited the users that we printed lets see if it worked.");
foreach (var u in r.All<User>().OrderBy(z => z.Id).Take(20))
{
Console.WriteLine("ID:{0}, Name: {1}", u.Id, u.Name);
}
// Clean up data store.
Console.WriteLine("\nCleaning up Data Store.\n");
foreach (var u in r.All<User>())
r.Delete<User>(u);
// Confirm that we no longer have any users.
Console.WriteLine("Confirm that we no longer have User entities in Data Store.");
Console.WriteLine("Users In \"Database\" : {0}\n\n", r.All<User>().Count);
Console.WriteLine("Hit return to exit!");
Console.Read();
}
}
public class Repo
{
private static readonly PooledRedisClientManager m = new PooledRedisClientManager();
public Repo()
{
// Spool Redis Database into our object cache.
LoadIntoCache<User>();
}
readonly IDictionary<Type, List<object>> _cache = new ConcurrentDictionary<Type, List<object>>();
/// <summary>
/// Load {T} into object cache from Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
private void LoadIntoCache<T>() where T : class
{
_cache[typeof(T)] = GetAll<T>().Cast<object>().ToList();
}
/// <summary>
/// Add single {T} into cache and Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="entity">class object</param>
public void Create<T>(T entity) where T : class
{
List<object> list;
if (!_cache.TryGetValue(typeof(T), out list))
{
list = new List<object>();
}
list.Add(entity);
_cache[typeof(T)] = list;
Store<T>(entity);
}
/// <summary>
/// Delete single {T} from cache and Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="entity">class object</param>
public void Delete<T>(T entity) where T : class
{
List<object> list;
if (_cache.TryGetValue(typeof(T), out list))
{
list.Remove(entity);
_cache[typeof(T)] = list;
RedisDelete<T>(entity);
}
}
/// <summary>
/// Tries to update or Add entity to object cache and Data Store.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq expression</param>
/// <param name="entity">entity</param>
public void Update<T>(Func<T, bool> predicate, T entity) where T : class
{
List<object> list;
if (_cache.TryGetValue(typeof(T), out list))
{
// Look for old entity.
var e = list.Cast<T>().Where(predicate).FirstOrDefault();
if(e != null)
{
list.Remove(e);
}
// Regardless if object existed or not we add it to our Cache / Data Store.
list.Add(entity);
_cache[typeof(T)] = list;
Store<T>(entity);
}
}
/// <summary>
/// Find List<T>(predicate) in cache.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public List<T> FindBy<T>(Func<T, bool> predicate) where T : class
{
List<object> list;
if (_cache.TryGetValue(typeof(T), out list))
{
return list.Cast<T>().Where(predicate).ToList();
}
return new List<T>();
}
/// <summary>
/// Find All {T}
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>List<T></returns>
public List<T> All<T>() where T : class
{
return GetAll<T>().ToList();
}
/// <summary>
/// Find Single {T} in object cache.
/// </summary>
/// <typeparam name="T">class</typeparam>
/// <param name="predicate">linq statement</param>
/// <returns></returns>
public T Read<T>(Func<T, bool> predicate) where T : class
{
List<object> list;
if (_cache.TryGetValue(typeof(T), out list))
{
return list.Cast<T>().Where(predicate).FirstOrDefault();
}
return null;
}
public long Next<T>() where T : class
{
long id = 1;
using (var ctx = m.GetClient())
{
try
{
id = ctx.As<T>().GetNextSequence();
}
catch(Exception ex)
{
// Add exception handler.
}
}
return id;
}
private void RedisDelete<T>(T entity) where T : class
{
using (var ctx = m.GetClient())
ctx.As<T>().Delete(entity);
}
private T Find<T>(long id) where T : class
{
using (var ctx = m.GetClient())
return ctx.As<T>().GetById(id);
}
private IList<T> GetAll<T>() where T : class
{
using(var ctx = m.GetClient())
{
try
{
return ctx.As<T>().GetAll();
}
catch
{
return new List<T>();
}
}
}
private void Store<T>(T entity) where T : class
{
using (var ctx = m.GetClient())
ctx.Store<T>(entity);
}
}
public class User
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Related
I'm still really new to coding and trying to make my first WPF application. I have been trying to implement sqlite for the past like 3 days now.
I am using VS 2019. I have downloaded the SQLite Toolbox and am following the instructions here - https://github.com/ErikEJ/SqlCeToolbox/wiki/EF6-workflow-with-SQLite-DDEX-provider. I did a full installation. Was I supposed to install it in my project directory? Because now I just have a bunch of files and nothing seems to have changed in Studio. I tried using Install.exe, but it returned a "confirm option not enabled" error. Looking at a similar question, I tried putting the files in an external folder in my project, and then installed the System.Data.SQLite.EF6.dll to my GAC using the VS Dev Console. The toolbox doesn't see any changes, and does not recognize the dll and I'm having a hard time finding reliable information for my version. Thanks for any help getting pointed in the right direction!
I'm not sure why you mention toolbox as far as I'm aware you access the SQLite functionality programmatically.
The NuGet packages that I recently used for a Xamarin project (SQLite-net-pcl by Frank Krueger and supporting libraries) allowed me to use pretty simple object mapping without recourse to SQL like strings.
I use a lot of interfaces in my code, but here's my all access database class
:
public class AllAccessDataTableBaseSqLite<T> : IDataAccessRead<T>, IDataAccessWrite<T>,IDataAccessExpressionSearch<T>, IDataAccessDelete<T> where T: IDataRecord, new()
{
//Note that this static value will only apply to those classes based on the same generic type e.g. all DataTableBase<User> instances etc.
public static SQLiteAsyncConnection DBConnection;
/// <summary>
/// Lock object to prevent multi-thread interruption of code segment.
/// </summary>
public static readonly object CollisionLock = new object();
/// <summary>
/// Constructor
/// </summary>
public AllAccessDataTableBaseSqLite()
{
lock (CollisionLock)
{
if (DBConnection != null)
{
DBConnection.CreateTableAsync<T>().Wait();
return;
}
try
{
string directory;
if (DeviceInfo.Platform != DevicePlatform.Unknown)
{
directory = FileSystem.AppDataDirectory;
}
else
{
directory = "DataStore";
var directoryInfo = Directory.CreateDirectory(directory);
directory = directoryInfo.FullName;
}
var path = Path.Combine(directory, $"{typeof(T).Name}.db");
if (!File.Exists(path))
{
using var fileStream = File.Create(path);
fileStream.Close();
}
DBConnection = new SQLiteAsyncConnection(path);
DBConnection.CreateTableAsync<T>().Wait();
}
catch (Exception ex)
{
if (ex is UnauthorizedAccessException)
{
}
}
}
}
/// <summary>
/// Create the data table
/// </summary>
/// <returns></returns>
public async Task<CreateTableResult> CreateTableAsync()
{
if (DBConnection != null)
{
return await DBConnection.CreateTableAsync<T>();
}
return CreateTableResult.Migrated;
}
/// <summary>
/// Create a new record entry
/// </summary>
/// <param name="entity">Data entity to enter</param>
/// <param name="user">Current User information</param>
/// <returns>New entry record if successful</returns>
public async Task<T> CreateAsync(T entity, IUserRecord user)
{
if (entity == null)
{
return default(T);
}
if (DBConnection == null)
{
return default(T);
}
entity.CreatedDate = DateTime.UtcNow;
entity.CreatedByUserId = user.Id;
entity.Id = 0;
try
{
await DBConnection.InsertAsync(entity);
}
catch (SQLiteException e)
{
if (e.Message == "Constraint")
{
throw new InvalidConstraintException(e.Message, e.InnerException);
}
}
var result = entity;
return result;
}
/// <summary>
/// Update a collection of new entities of type T to the data table.
/// All entities should be present within the data table
/// </summary>
/// <param name="entityList">Entity collection</param>
/// <param name="user">user making the change</param>
/// <returns>ID of entities successfully updated or added</returns>
public async Task<int> UpdateAllAsync(IEnumerable<T> entityList, IUserRecord user)
{
var result = 0;
foreach (var t in entityList)
{
if (null != await UpdateAsync(t, user))
{
result++ ;
}
}
return result;
}
/// <summary>
/// Obtain the data record with the given Id
/// </summary>
/// <param name="id">Id value to select the record by</param>
/// <returns>A valid record if found otherwise null</returns>
public async Task<T> GetById(int id)
{
if (DBConnection == null)
{
return default(T);
}
return await DBConnection.Table<T>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
/// <summary>
/// This function returns all database entries that are not marked deleted or changed
/// Warning: The data set may be very large
/// </summary>
/// <returns>A list of entries</returns>
public async Task<List<T>> GetAll()
{
if (DBConnection != null)
{
return await DBConnection.Table<T>().Where(x=>x.ChangedDate==default && x.DeletedDate==default)
.ToListAsync();
}
return new List<T>();
}
/// <inheritdoc />
public async Task<List<T>> GetAllHistoric() => await DBConnection.Table<T>().ToListAsync();
/// <summary>
/// This function is used to update the supplied record entry within the database.
/// If the supplied record does not have a non-zero value Id field it is assumed to be a
/// new record to be inserted into the database.
/// </summary>
/// <param name="entity">Record to update</param>
/// <param name="user">User performing the action</param>
/// <returns></returns>
public async Task<T> UpdateAsync(T entity, IUserRecord user)
{
if (DBConnection == null)
{
return default(T);
}
if (entity == null)
{
return default(T);
}
var newRecord = (T) ((entity) as BaseRecord<T>)?.Clone();
if (null == newRecord)
{
return default(T);
}
//if Id is zero assume that the record is new and to be added
if (newRecord.Id == 0)
{
if (user != null)
{
newRecord.CreatedByUserId = user.Id;
}
newRecord.CreatedDate = DateTime.UtcNow;
newRecord.Id = await DBConnection.InsertAsync(newRecord);
return newRecord;
}
// Id is not zero and thus a new record should be created linked to the old record.
var oldRecord = await GetById(newRecord.Id);
oldRecord.ChangedDate = DateTime.UtcNow;
if (user != null)
{
oldRecord.ChangedByUserId = user.Id;
}
try
{
var result = await DBConnection.UpdateAsync(oldRecord);
}
catch (Exception e)
{
Debug.WriteLine($"UpdateAsync {e.Message}");
}
newRecord.PreviousRecordId = oldRecord.Id;
newRecord.Id = 0;
return await CreateAsync(newRecord, user);
}
/// <inheritdoc />
public async Task<int> DeleteAsync(T entity)
{
if (DBConnection == null)
{
return -1;
}
return await DBConnection.DeleteAsync(entity);
}
/// <inheritdoc />
public async Task DeleteAll()
{
await DBConnection.DropTableAsync<T>();
await CreateTableAsync();
}
/// <inheritdoc />
public async Task<PagedResult<T>> GetAllPagedResult(int recordId, uint maxResults = 100)
{
if (DBConnection == null)
{
return null;
}
List<T> list;
if (maxResults == 0)
{
list = await GetAll();
}
else
{
list = await DBConnection.Table<T>().Where(x => (x.Id >= recordId && x.ChangedDate == default && x.DeletedDate == default)).ToListAsync();
if (list.Count() > maxResults)
{
list = list.GetRange(0, (int) maxResults);
}
}
return new PagedResult<T>(list, list.Count());
}
/// <inheritdoc />
public async Task<IEnumerable<T>> FindAsyncOrdered<TValue>(Expression<Func<T, bool>> predicate = null,
Expression<Func<T, TValue>> orderBy = null)
{
var query = DBConnection.Table<T>();
if (predicate != null)
{
query = query.Where(predicate);
}
if (orderBy != null)
{
query = query.OrderBy<TValue>(orderBy);
}
return await query.ToListAsync();
}
/// <inheritdoc />
public async Task<T> FindFirst(Expression<Func<T, bool>> predicate) => await DBConnection.FindAsync(predicate);
}
I'm using data classes based upon:
public interface IDataRecord
{
/// <summary>
/// Identifier for record
/// </summary>
int Id { get; set; }
/// <summary>
/// Link to previous version of record
/// </summary>
int PreviousRecordId { get; set; }
/// <summary>
/// User Identity that made the change
/// </summary>
int ChangedByUserId { get; set; }
/// <summary>
/// Date when the data record was last changed
/// </summary>
DateTime ChangedDate { get; set; }
/// <summary>
/// Identity of User that deleted the record
/// </summary>
int DeletedByUserId { get; set; }
/// <summary>
/// Date when the data record was deleted
/// </summary>
DateTime DeletedDate { get; set; }
/// <summary>
/// Identity of User that created the record
/// </summary>
int CreatedByUserId { get; set; }
/// <summary>
/// Date when the data record was added
/// </summary>
DateTime CreatedDate { get; set; }
object Clone();
}
Obviously you don't have to use this, but put simply for my application implementation each type of data record is stored in its own data file (thus 1 table per file) and this is created at the beginning in the constructor.
The SQLite db connection is created using the data file path.
Table is created using the dbconnection
edit
I've had a closer look at your code.
Points to note are:
You don't appear to be creating a table.
Access to non-app folders is restricted if you chose to create a UWP rather than base WPF project - be aware of folder access permissions when running apps especially in release mode.
I have a dictionary in a singleton class. I am saving there the pair , every time I access to that dictionary from the method that is creating the token it shows all the credentials that I've stored there.
But when I access from another class in another project of the solutions it displays the dictionary empty. Can anybody tell me why this happens?
This is the class that manage the dictionary:
public class UserAccessToken
{
public Dictionary<string, string> UserDictionary { get; set; }
private static UserAccessToken _instance;
private UserAccessToken() { }
public static UserAccessToken Instance
{
get
{
if (_instance == null)
_instance = new UserAccessToken
{
UserDictionary = new Dictionary<string, string>()
};
return _instance;
}
}
}
This is the method that insert the key,value pair in the dictionary:
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
var accessToken = context.AccessToken;
if (context.Properties.Dictionary.ContainsKey("userName"))
{
var username = context.Properties.Dictionary["userName"];
// If I access here multiple times the singleton works
UserAccessToken.Instance.UserDictionary[username] = accessToken;
}
return Task.FromResult<object>(null);
}
This is the method where I access the dictionary, from here I can see that it's empty:
private bool IsTokenValid(HttpContextBase httpContext)
{
var username = httpContext.User.Identity.Name;
var userTokens = UserAccessToken.Instance.UserDictionary;
var tokenToAccess = httpContext.Request.Headers["Authorization"];
tokenToAccess = tokenToAccess.Replace("Bearer ", "");
if (userTokens.ContainsKey(username))
{
var token = userTokens[username];
if (token == tokenToAccess) return true;
}
return true;
}
I already solved my problem, but I'll let my solution here in case could be useful for somebody.
The problem is that if you are running two different projects, that will mean two different process, so, what I wanted to do is pretty impossible. I used Redis for this and it is working well.
This is an example of Redis use:
public class CallManagerCache : ICallManagerMethods{
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer
.Connect(CloudConfigurationManager.GetSetting("RedisConnectionString")));
public static ConnectionMultiplexer cacheConnection
{
get
{
return lazyConnection.Value;
}
}
/// <summary>
/// Here you save the value in cache, you get the connection, then StringSetAsync is in charge of saving the value
/// </summary>
/// <param name="name"></param>
/// <param name="template"></param>
public async Task UpdateCallInstance(int id, byte[] data, bool instanceForCallback = false, TimeSpan? timespan = null)
{
var cache = cacheConnection.GetDatabase();
await cache.StringSetAsync(instanceForCallback ? $"Call_{id}" : id.ToString(), data, timespan ?? new TimeSpan(0, 0, 15, 0));
}
/// <summary>
/// Here you get the value in cache, you get the connection, then StringGetAsync is in charge of getting the value
/// </summary>
/// <param name="name"></param>
/// <param name="template"></param>
public async Task<CallInstance> GetById(int id, bool isForCallback)
{
var cache = cacheConnection.GetDatabase();
var callInstance = new CallInstance
{
Id = id,
InstanceData = await cache.StringGetAsync(isForCallback ? $"Call_{id}" : id.ToString())
};
return callInstance;
}
}
I'm working on a client-server system at the moment, and I'm trying to get a collection to synchronise across a websocket. Everything is in C# + .Net 4.5, and I was wondering if there was a particular best practise for synchronising data over a websocket. It's a one way sync:
Server: BindingCollection< MyClass > ----- Websocket -----> Client: BindingCollection< MyClass >
The collection could be up to 1000 objects with 20 fields each so sending the whole lot each time seems a little wasteful.
I would use a observer pattern and only send the changed object to be synced.
So I finally took the time to write a small example.
I am using a in-memory generic repository that invokes events on changes. The changes is then sent to all clients so that you do not have to send the complete list/collection.
A simple model to monitor
using System;
namespace SynchronizingCollection.Common.Model
{
public class MyModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
A Generic Repository
Notice the event OnChange that is called when something is added/updated/removed. The event is "subscribed" to in a XSockets long running controller (a singleton) See the "RepoMonitor" class
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// A static generic thread-safe repository for in-memory storage
/// </summary>
/// <typeparam name="TK">Key Type</typeparam>
/// <typeparam name="T">Value Type</typeparam>
public static class Repository<TK, T>
{
/// <summary>
/// When something changes
/// </summary>
public static event EventHandler<OnChangedArgs<TK,T>> OnChange;
private static ConcurrentDictionary<TK, T> Container { get; set; }
static Repository()
{
Container = new ConcurrentDictionary<TK, T>();
}
/// <summary>
/// Adds or updates the entity T with key TK
/// </summary>
/// <param name="key"></param>
/// <param name="entity"></param>
/// <returns></returns>
public static T AddOrUpdate(TK key, T entity)
{
var obj = Container.AddOrUpdate(key, entity, (s, o) => entity);
if(OnChange != null)
OnChange.Invoke(null,new OnChangedArgs<TK, T>(){Key = key,Value = entity, Operation = Operation.AddUpdate});
return obj;
}
/// <summary>
/// Removes the entity T with key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Remove(TK key)
{
T entity;
var result = Container.TryRemove(key, out entity);
if (result)
{
if (OnChange != null)
OnChange.Invoke(null, new OnChangedArgs<TK, T>() { Key = key, Value = entity, Operation = Operation.Remove});
}
return result;
}
/// <summary>
/// Removes all entities matching the expression f
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static int Remove(Func<T, bool> f)
{
return FindWithKeys(f).Count(o => Remove(o.Key));
}
/// <summary>
/// Find all entities T matching the expression f
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static IEnumerable<T> Find(Func<T, bool> f)
{
return Container.Values.Where(f);
}
/// <summary>
/// Find all entities T matching the expression f and returns a Dictionary TK,T
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static IDictionary<TK, T> FindWithKeys(Func<T, bool> f)
{
var y = from x in Container
where f.Invoke(x.Value)
select x;
return y.ToDictionary(x => x.Key, x => x.Value);
}
/// <summary>
/// Returns all entities as a Dictionary TK,T
/// </summary>
/// <returns></returns>
public static IDictionary<TK, T> GetAllWithKeys()
{
return Container;
}
/// <summary>
/// Returns all entities T from the repository
/// </summary>
/// <returns></returns>
public static IEnumerable<T> GetAll()
{
return Container.Values;
}
/// <summary>
/// Get a single entity T with the key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static T GetById(TK key)
{
return Container.ContainsKey(key) ? Container[key] : default(T);
}
/// <summary>
/// Get a single entity T as a KeyValuePair TK,T with the key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static KeyValuePair<TK, T> GetByIdWithKey(TK key)
{
return Container.ContainsKey(key) ? new KeyValuePair<TK, T>(key, Container[key]) : new KeyValuePair<TK, T>(key, default(T));
}
/// <summary>
/// Checks if the repository has a key TK
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool ContainsKey(TK key)
{
return Container.ContainsKey(key);
}
}
}
Event argument and an enum to know what change just happend
using System;
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// To send changes in the repo
/// </summary>
/// <typeparam name="TK"></typeparam>
/// <typeparam name="T"></typeparam>
public class OnChangedArgs<TK,T> : EventArgs
{
public Operation Operation { get; set; }
public TK Key { get; set; }
public T Value { get; set; }
}
}
namespace SynchronizingCollection.Server.Repository
{
/// <summary>
/// What kind of change was performed
/// </summary>
public enum Operation
{
AddUpdate,
Remove
}
}
The Controller that send changes to the clients...
using System;
using SynchronizingCollection.Common.Model;
using SynchronizingCollection.Server.Repository;
using XSockets.Core.XSocket;
using XSockets.Core.XSocket.Helpers;
using XSockets.Plugin.Framework;
using XSockets.Plugin.Framework.Attributes;
namespace SynchronizingCollection.Server
{
/// <summary>
/// Long running controller that will send information to clients about the collection changes
/// </summary>
[XSocketMetadata(PluginRange = PluginRange.Internal, PluginAlias = "RepoMonitor")]
public class RepositoryMonitor : XSocketController
{
public RepositoryMonitor()
{
Repository<Guid, MyModel>.OnChange += RepositoryOnChanged;
}
private void RepositoryOnChanged(object sender, OnChangedArgs<Guid, MyModel> e)
{
switch (e.Operation)
{
case Operation.Remove:
this.InvokeTo<Demo>(p => p.SendUpdates, e.Value,"removed");
break;
case Operation.AddUpdate:
this.InvokeTo<Demo>(p => p.SendUpdates, e.Value, "addorupdated");
break;
}
}
}
}
The XSockets controller that clients call to add/remove/update the collection.
using System;
using SynchronizingCollection.Common.Model;
using SynchronizingCollection.Server.Repository;
using XSockets.Core.XSocket;
namespace SynchronizingCollection.Server
{
public class Demo : XSocketController
{
public bool SendUpdates { get; set; }
public Demo()
{
//By default all clients get updates
SendUpdates = true;
}
public void AddOrUpdateModel(MyModel model)
{
Repository<Guid, MyModel>.AddOrUpdate(model.Id, model);
}
public void RemoveModel(MyModel model)
{
Repository<Guid, MyModel>.Remove(model.Id);
}
}
}
And a demo client in C# that adds and removed 10 different objects... But it would be easy to use the JavaScript API as well. Especially with knockoutjs for manipulating the collection on the client.
using System;
using System.Threading;
using SynchronizingCollection.Common.Model;
using XSockets.Client40;
namespace SynchronizingCollection.Client
{
class Program
{
static void Main(string[] args)
{
var c = new XSocketClient("ws://127.0.0.1:4502","http://localhost","demo");
c.Controller("demo").OnOpen += (sender, connectArgs) => Console.WriteLine("Demo OPEN");
c.Controller("demo").On<MyModel>("addorupdated", model => Console.WriteLine("Updated " + model.Name));
c.Controller("demo").On<MyModel>("removed", model => Console.WriteLine("Removed " + model.Name));
c.Open();
for (var i = 0; i < 10; i++)
{
var m = new MyModel() {Id = Guid.NewGuid(), Name = "Person Nr" + i, Age = i};
c.Controller("demo").Invoke("AddOrUpdateModel", m);
Thread.Sleep(2000);
c.Controller("demo").Invoke("RemoveModel", m);
Thread.Sleep(2000);
}
Console.ReadLine();
}
}
}
You can download the project from my dropbox: https://www.dropbox.com/s/5ljbedovx6ufkww/SynchronizingCollection.zip?dl=0
Regards
Uffe
I have a set of huge taks to be performed in c#. Each calcuation will produce a resultant data which i want to write into a file (i am using SQLite). Currently i am doing this in a sequential way like this [Task1 -> FileSaving1], [Task2 -> FileSaving2],. and so on.
But my priority is to complete all the calculations first, so i want to run the calculation in parallel in one thread and the file saving to be done in another. FileSaving thread will be signalled everytime when the calculation is over and the data is ready to be written. FileSaving can be sequential or Parallel.
How to acheive this in C#? I am using .Net 4.0.
Please provide me with some example if possible.
You can use a BlockingCollection<T> to help with this.
The tricky thing is that you want several threads processing work items, but they can produce their output in a random order so you need to multiplex the output when writing it (assuming you want to write the data in the same order as it would have been written if you used the old single-threaded solution).
I wrote a class to do this a while back.
It assumes that you can encapsulate each "work item" in an instance of a class. Those instances are added to a work queue; then multiple threads (via Task) can remove work items from the work queue, process them, and then output them to a priority queue.
Finally, another thread can remove the completed work items from the completed queue, being careful to multiplex them so that it removes the items in the same order as they were originally added to the work queue.
This implementation creates and manages the threads for you. You need to tell it how many worker threads to use, and supply it delegates to provide new work items (Read()), process each work item (Process()) and output each work item (Write()).
Only the Process() delegate is called by multiple threads.
Note that if you don't care about the order, you can avoid all this stuff and pretty much use BlockingCollection directly.
Here's the code:
public sealed class ParallelWorkProcessor<T> where T: class // T is the work item type.
{
public delegate T Read(); // Called by only one thread.
public delegate T Process(T block); // Called simultaneously by multiple threads.
public delegate void Write(T block); // Called by only one thread.
public ParallelWorkProcessor(Read read, Process process, Write write, int numWorkers = 0)
{
_read = read;
_process = process;
_write = write;
numWorkers = (numWorkers > 0) ? numWorkers : Environment.ProcessorCount;
_workPool = new SemaphoreSlim(numWorkers*2);
_inputQueue = new BlockingCollection<WorkItem>(numWorkers);
_outputQueue = new ConcurrentPriorityQueue<int, T>();
_workers = new Task[numWorkers];
startWorkers();
Task.Factory.StartNew(enqueueWorkItems);
_multiplexor = Task.Factory.StartNew(multiplex);
}
private void startWorkers()
{
for (int i = 0; i < _workers.Length; ++i)
{
_workers[i] = Task.Factory.StartNew(processBlocks);
}
}
private void enqueueWorkItems()
{
int index = 0;
while (true)
{
T data = _read();
if (data == null) // Signals end of input.
{
_inputQueue.CompleteAdding();
_outputQueue.Enqueue(index, null); // Special sentinel WorkItem .
break;
}
_workPool.Wait();
_inputQueue.Add(new WorkItem(data, index++));
}
}
private void multiplex()
{
int index = 0; // Next required index.
int last = int.MaxValue;
while (index != last)
{
KeyValuePair<int, T> workItem;
_outputQueue.WaitForNewItem(); // There will always be at least one item - the sentinel item.
while ((index != last) && _outputQueue.TryPeek(out workItem))
{
if (workItem.Value == null) // The sentinel item has a null value to indicate that it's the sentinel.
{
last = workItem.Key; // The sentinel's key is the index of the last block + 1.
}
else if (workItem.Key == index) // Is this block the next one that we want?
{
// Even if new items are added to the queue while we're here, the new items will be lower priority.
// Therefore it is safe to assume that the item we will dequeue now is the same one we peeked at.
_outputQueue.TryDequeue(out workItem);
Contract.Assume(workItem.Key == index); // This *must* be the case.
_workPool.Release(); // Allow the enqueuer to queue another work item.
_write(workItem.Value);
++index;
}
else // If it's not the block we want, we know we'll get a new item at some point.
{
_outputQueue.WaitForNewItem();
}
}
}
}
private void processBlocks()
{
foreach (var block in _inputQueue.GetConsumingEnumerable())
{
var processedData = _process(block.Data);
_outputQueue.Enqueue(block.Index, processedData);
}
}
public bool WaitForFinished(int maxMillisecondsToWait) // Can be Timeout.Infinite.
{
return _multiplexor.Wait(maxMillisecondsToWait);
}
private sealed class WorkItem
{
public WorkItem(T data, int index)
{
Data = data;
Index = index;
}
public T Data { get; private set; }
public int Index { get; private set; }
}
private readonly Task[] _workers;
private readonly Task _multiplexor;
private readonly SemaphoreSlim _workPool;
private readonly BlockingCollection<WorkItem> _inputQueue;
private readonly ConcurrentPriorityQueue<int, T> _outputQueue;
private readonly Read _read;
private readonly Process _process;
private readonly Write _write;
}
Here's the test code for it:
namespace Demo
{
public static class Program
{
private static void Main(string[] args)
{
_rng = new Random(34324);
int threadCount = 8;
_maxBlocks = 200;
ThreadPool.SetMinThreads(threadCount + 2, 4); // Kludge to prevent slow thread startup.
_numBlocks = _maxBlocks;
var stopwatch = Stopwatch.StartNew();
var processor = new ParallelWorkProcessor<byte[]>(read, process, write, threadCount);
processor.WaitForFinished(Timeout.Infinite);
Console.WriteLine("\n\nFinished in " + stopwatch.Elapsed + "\n\n");
}
private static byte[] read()
{
if (_numBlocks-- == 0)
{
return null;
}
var result = new byte[128];
result[0] = (byte)(_maxBlocks-_numBlocks);
Console.WriteLine("Supplied input: " + result[0]);
return result;
}
private static byte[] process(byte[] data)
{
if (data[0] == 10) // Hack for test purposes. Make it REALLY slow for this item!
{
Console.WriteLine("Delaying a call to process() for 5s for ID 10");
Thread.Sleep(5000);
}
Thread.Sleep(10 + _rng.Next(50));
Console.WriteLine("Processed: " + data[0]);
return data;
}
private static void write(byte[] data)
{
Console.WriteLine("Received output: " + data[0]);
}
private static Random _rng;
private static int _numBlocks;
private static int _maxBlocks;
}
}
This also requires a ConcurrentPriorityQueue implementation from here.
I had to modify that slightly, so here's my modified version:
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace ConsoleApplication1
{
/// <summary>Provides a thread-safe priority queue data structure.</summary>
/// <typeparam name="TKey">Specifies the type of keys used to prioritize values.</typeparam>
/// <typeparam name="TValue">Specifies the type of elements in the queue.</typeparam>
[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
[DebuggerDisplay("Count={Count}")]
public sealed class ConcurrentPriorityQueue<TKey, TValue> :
IProducerConsumerCollection<KeyValuePair<TKey,TValue>>
where TKey : IComparable<TKey>
{
/// <summary>Initializes a new instance of the ConcurrentPriorityQueue class.</summary>
public ConcurrentPriorityQueue() {}
/// <summary>Initializes a new instance of the ConcurrentPriorityQueue class that contains elements copied from the specified collection.</summary>
/// <param name="collection">The collection whose elements are copied to the new ConcurrentPriorityQueue.</param>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
public ConcurrentPriorityQueue(IEnumerable<KeyValuePair<TKey, TValue>> collection)
{
if (collection == null) throw new ArgumentNullException("collection");
foreach (var item in collection) _minHeap.Insert(item);
}
/// <summary>Adds the key/value pair to the priority queue.</summary>
/// <param name="priority">The priority of the item to be added.</param>
/// <param name="value">The item to be added.</param>
public void Enqueue(TKey priority, TValue value)
{
Enqueue(new KeyValuePair<TKey, TValue>(priority, value));
}
/// <summary>Adds the key/value pair to the priority queue.</summary>
/// <param name="item">The key/value pair to be added to the queue.</param>
public void Enqueue(KeyValuePair<TKey, TValue> item)
{
lock (_syncLock)
{
_minHeap.Insert(item);
_newItem.Set();
}
}
/// <summary>Waits for a new item to appear.</summary>
public void WaitForNewItem()
{
_newItem.WaitOne();
}
/// <summary>Attempts to remove and return the next prioritized item in the queue.</summary>
/// <param name="result">
/// When this method returns, if the operation was successful, result contains the object removed. If
/// no object was available to be removed, the value is unspecified.
/// </param>
/// <returns>
/// true if an element was removed and returned from the queue succesfully; otherwise, false.
/// </returns>
public bool TryDequeue(out KeyValuePair<TKey, TValue> result)
{
result = default(KeyValuePair<TKey, TValue>);
lock (_syncLock)
{
if (_minHeap.Count > 0)
{
result = _minHeap.Remove();
return true;
}
}
return false;
}
/// <summary>Attempts to return the next prioritized item in the queue.</summary>
/// <param name="result">
/// When this method returns, if the operation was successful, result contains the object.
/// The queue was not modified by the operation.
/// </param>
/// <returns>
/// true if an element was returned from the queue succesfully; otherwise, false.
/// </returns>
public bool TryPeek(out KeyValuePair<TKey, TValue> result)
{
result = default(KeyValuePair<TKey, TValue>);
lock (_syncLock)
{
if (_minHeap.Count > 0)
{
result = _minHeap.Peek();
return true;
}
}
return false;
}
/// <summary>Empties the queue.</summary>
public void Clear() { lock(_syncLock) _minHeap.Clear(); }
/// <summary>Gets whether the queue is empty.</summary>
public bool IsEmpty { get { return Count == 0; } }
/// <summary>Gets the number of elements contained in the queue.</summary>
public int Count
{
get { lock (_syncLock) return _minHeap.Count; }
}
/// <summary>Copies the elements of the collection to an array, starting at a particular array index.</summary>
/// <param name="array">
/// The one-dimensional array that is the destination of the elements copied from the queue.
/// </param>
/// <param name="index">
/// The zero-based index in array at which copying begins.
/// </param>
/// <remarks>The elements will not be copied to the array in any guaranteed order.</remarks>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
lock (_syncLock) _minHeap.Items.CopyTo(array, index);
}
/// <summary>Copies the elements stored in the queue to a new array.</summary>
/// <returns>A new array containing a snapshot of elements copied from the queue.</returns>
public KeyValuePair<TKey, TValue>[] ToArray()
{
lock (_syncLock)
{
var clonedHeap = new MinBinaryHeap(_minHeap);
var result = new KeyValuePair<TKey, TValue>[_minHeap.Count];
for (int i = 0; i < result.Length; i++)
{
result[i] = clonedHeap.Remove();
}
return result;
}
}
/// <summary>Attempts to add an item in the queue.</summary>
/// <param name="item">The key/value pair to be added.</param>
/// <returns>
/// true if the pair was added; otherwise, false.
/// </returns>
bool IProducerConsumerCollection<KeyValuePair<TKey, TValue>>.TryAdd(KeyValuePair<TKey, TValue> item)
{
Enqueue(item);
return true;
}
/// <summary>Attempts to remove and return the next prioritized item in the queue.</summary>
/// <param name="item">
/// When this method returns, if the operation was successful, result contains the object removed. If
/// no object was available to be removed, the value is unspecified.
/// </param>
/// <returns>
/// true if an element was removed and returned from the queue succesfully; otherwise, false.
/// </returns>
bool IProducerConsumerCollection<KeyValuePair<TKey, TValue>>.TryTake(out KeyValuePair<TKey, TValue> item)
{
return TryDequeue(out item);
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator for the contents of the queue.</returns>
/// <remarks>
/// The enumeration represents a moment-in-time snapshot of the contents of the queue. It does not
/// reflect any updates to the collection after GetEnumerator was called. The enumerator is safe to
/// use concurrently with reads from and writes to the queue.
/// </remarks>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
var arr = ToArray();
return ((IEnumerable<KeyValuePair<TKey, TValue>>)arr).GetEnumerator();
}
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An IEnumerator that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
/// <summary>Copies the elements of the collection to an array, starting at a particular array index.</summary>
/// <param name="array">
/// The one-dimensional array that is the destination of the elements copied from the queue.
/// </param>
/// <param name="index">
/// The zero-based index in array at which copying begins.
/// </param>
void ICollection.CopyTo(Array array, int index)
{
lock (_syncLock) ((ICollection)_minHeap.Items).CopyTo(array, index);
}
/// <summary>
/// Gets a value indicating whether access to the ICollection is synchronized with the SyncRoot.
/// </summary>
bool ICollection.IsSynchronized { get { return true; } }
/// <summary>
/// Gets an object that can be used to synchronize access to the collection.
/// </summary>
object ICollection.SyncRoot { get { return _syncLock; } }
/// <summary>Implements a binary heap that prioritizes smaller values.</summary>
private sealed class MinBinaryHeap
{
private readonly List<KeyValuePair<TKey, TValue>> _items;
/// <summary>Initializes an empty heap.</summary>
public MinBinaryHeap()
{
_items = new List<KeyValuePair<TKey, TValue>>();
}
/// <summary>Initializes a heap as a copy of another heap instance.</summary>
/// <param name="heapToCopy">The heap to copy.</param>
/// <remarks>Key/Value values are not deep cloned.</remarks>
public MinBinaryHeap(MinBinaryHeap heapToCopy)
{
_items = new List<KeyValuePair<TKey, TValue>>(heapToCopy.Items);
}
/// <summary>Empties the heap.</summary>
public void Clear() { _items.Clear(); }
/// <summary>Adds an item to the heap.</summary>
public void Insert(KeyValuePair<TKey,TValue> entry)
{
// Add the item to the list, making sure to keep track of where it was added.
_items.Add(entry);
int pos = _items.Count - 1;
// If the new item is the only item, we're done.
if (pos == 0) return;
// Otherwise, perform log(n) operations, walking up the tree, swapping
// where necessary based on key values
while (pos > 0)
{
// Get the next position to check
int nextPos = (pos-1) / 2;
// Extract the entry at the next position
var toCheck = _items[nextPos];
// Compare that entry to our new one. If our entry has a smaller key, move it up.
// Otherwise, we're done.
if (entry.Key.CompareTo(toCheck.Key) < 0)
{
_items[pos] = toCheck;
pos = nextPos;
}
else break;
}
// Make sure we put this entry back in, just in case
_items[pos] = entry;
}
/// <summary>Returns the entry at the top of the heap.</summary>
public KeyValuePair<TKey, TValue> Peek()
{
// Returns the first item
if (_items.Count == 0) throw new InvalidOperationException("The heap is empty.");
return _items[0];
}
/// <summary>Removes the entry at the top of the heap.</summary>
public KeyValuePair<TKey, TValue> Remove()
{
// Get the first item and save it for later (this is what will be returned).
if (_items.Count == 0) throw new InvalidOperationException("The heap is empty.");
KeyValuePair<TKey, TValue> toReturn = _items[0];
// Remove the first item if there will only be 0 or 1 items left after doing so.
if (_items.Count <= 2) _items.RemoveAt(0);
// A reheapify will be required for the removal
else
{
// Remove the first item and move the last item to the front.
_items[0] = _items[_items.Count - 1];
_items.RemoveAt(_items.Count - 1);
// Start reheapify
int current = 0, possibleSwap = 0;
// Keep going until the tree is a heap
while (true)
{
// Get the positions of the node's children
int leftChildPos = 2 * current + 1;
int rightChildPos = leftChildPos + 1;
// Should we swap with the left child?
if (leftChildPos < _items.Count)
{
// Get the two entries to compare (node and its left child)
var entry1 = _items[current];
var entry2 = _items[leftChildPos];
// If the child has a lower key than the parent, set that as a possible swap
if (entry2.Key.CompareTo(entry1.Key) < 0) possibleSwap = leftChildPos;
}
else break; // if can't swap this, we're done
// Should we swap with the right child? Note that now we check with the possible swap
// position (which might be current and might be left child).
if (rightChildPos < _items.Count)
{
// Get the two entries to compare (node and its left child)
var entry1 = _items[possibleSwap];
var entry2 = _items[rightChildPos];
// If the child has a lower key than the parent, set that as a possible swap
if (entry2.Key.CompareTo(entry1.Key) < 0) possibleSwap = rightChildPos;
}
// Now swap current and possible swap if necessary
if (current != possibleSwap)
{
var temp = _items[current];
_items[current] = _items[possibleSwap];
_items[possibleSwap] = temp;
}
else break; // if nothing to swap, we're done
// Update current to the location of the swap
current = possibleSwap;
}
}
// Return the item from the heap
return toReturn;
}
/// <summary>Gets the number of objects stored in the heap.</summary>
public int Count { get { return _items.Count; } }
internal List<KeyValuePair<TKey, TValue>> Items { get { return _items; } }
}
private readonly AutoResetEvent _newItem = new AutoResetEvent(false);
private readonly object _syncLock = new object();
private readonly MinBinaryHeap _minHeap = new MinBinaryHeap();
}
}
I'm making a full repository in C#/ASP.NET with the Entity framework but at the moment I'm afraid I'm overlooking something like disposing my ObjectContexts. In the following lines of codes you'll see my full repository (atleast what is needed for you guys to understand my problem) and I hope someone is kind enough to look through it and tell me if I made some mistakes.
This project is very very important for me but I'm new to the repository/EF models.
Global.asax
public class Global : System.Web.HttpApplication
{
private WebObjectContextStorage _storage;
public override void Init()
{
base.Init();
_storage = new WebObjectContextStorage(this);
}
protected void Application_Start(object sender, EventArgs e)
{
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
ObjectContextInitializer.Instance().InitializeObjectContextOnce(() =>
{
ObjectContextManager.InitStorage(_storage);
});
}
protected void Application_EndRequest(object sender, EventArgs e)
{
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
ObjectContextManager
public static class ObjectContextManager
{
public static void InitStorage(IObjectContextStorage storage)
{
if (storage == null)
{
throw new ArgumentNullException("storage");
}
if ((Storage != null) && (Storage != storage))
{
throw new ApplicationException("A storage mechanism has already been configured for this application");
}
Storage = storage;
}
/// <summary>
/// The default connection string name used if only one database is being communicated with.
/// </summary>
public static readonly string DefaultConnectionStringName = "TraceConnection";
/// <summary>
/// Used to get the current object context session if you're communicating with a single database.
/// When communicating with multiple databases, invoke <see cref="CurrentFor()" /> instead.
/// </summary>
public static ObjectContext Current
{
get
{
return CurrentFor(DefaultConnectionStringName);
}
}
/// <summary>
/// Used to get the current ObjectContext associated with a key; i.e., the key
/// associated with an object context for a specific database.
///
/// If you're only communicating with one database, you should call <see cref="Current" /> instead,
/// although you're certainly welcome to call this if you have the key available.
/// </summary>
public static ObjectContext CurrentFor(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
if (Storage == null)
{
throw new ApplicationException("An IObjectContextStorage has not been initialized");
}
ObjectContext context = null;
lock (_syncLock)
{
context = Storage.GetObjectContextForKey(key);
if (context == null)
{
context = ObjectContextFactory.GetTraceContext(key);
Storage.SetObjectContextForKey(key, context);
}
}
return context;
}
/// <summary>
/// This method is used by application-specific object context storage implementations
/// and unit tests. Its job is to walk thru existing cached object context(s) and Close() each one.
/// </summary>
public static void CloseAllObjectContexts()
{
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
if (ctx.Connection.State == System.Data.ConnectionState.Open)
ctx.Connection.Close();
}
}
/// <summary>
/// An application-specific implementation of IObjectContextStorage must be setup either thru
/// <see cref="InitStorage" /> or one of the <see cref="Init" /> overloads.
/// </summary>
private static IObjectContextStorage Storage { get; set; }
private static object _syncLock = new object();
}
ObjectContextInitializer
public class ObjectContextInitializer
{
private static readonly object syncLock = new object();
private static ObjectContextInitializer instance;
protected ObjectContextInitializer() { }
private bool isInitialized = false;
public static ObjectContextInitializer Instance()
{
if (instance == null)
{
lock (syncLock)
{
if (instance == null)
{
instance = new ObjectContextInitializer();
}
}
}
return instance;
}
/// <summary>
/// This is the method which should be given the call to intialize the ObjectContext; e.g.,
/// ObjectContextInitializer.Instance().InitializeObjectContextOnce(() => InitializeObjectContext());
/// where InitializeObjectContext() is a method which calls ObjectContextManager.Init()
/// </summary>
/// <param name="initMethod"></param>
public void InitializeObjectContextOnce(Action initMethod)
{
lock (syncLock)
{
if (!isInitialized)
{
initMethod();
isInitialized = true;
}
}
}
}
ObjectContextFactory
public static class ObjectContextFactory
{
/// <summary>
/// Gets the TraceContext
/// </summary>
/// <param name="connectionString">Connection string to use for database queries</param>
/// <returns>The TraceContext</returns>
public static TraceContext GetTraceContext(string configName)
{
string connectionString = ConfigurationManager.ConnectionStrings[configName].ConnectionString;
return new TraceContext(connectionString);
}
}
WebObjectContextStorage
public class WebObjectContextStorage : IObjectContextStorage
{
public WebObjectContextStorage(HttpApplication app)
{
app.EndRequest += (sender, args) =>
{
ObjectContextManager.CloseAllObjectContexts();
HttpContext.Current.Items.Remove(HttpContextObjectContextStorageKey);
};
}
public ObjectContext GetObjectContextForKey(string key)
{
ObjectContextStorage storage = GetObjectContextStorage();
return storage.GetObjectContextForKey(key);
}
public void SetObjectContextForKey(string factoryKey, ObjectContext session)
{
ObjectContextStorage storage = GetObjectContextStorage();
storage.SetObjectContextForKey(factoryKey, session);
}
public IEnumerable<ObjectContext> GetAllObjectContexts()
{
ObjectContextStorage storage = GetObjectContextStorage();
return storage.GetAllObjectContexts();
}
private ObjectContextStorage GetObjectContextStorage()
{
HttpContext context = HttpContext.Current;
ObjectContextStorage storage = context.Items[HttpContextObjectContextStorageKey] as ObjectContextStorage;
if (storage == null)
{
storage = new ObjectContextStorage();
context.Items[HttpContextObjectContextStorageKey] = storage;
}
return storage;
}
private static readonly string HttpContextObjectContextStorageKey = "HttpContextObjectContextStorageKey";
}
ObjectContextStorage
public class ObjectContextStorage : IObjectContextStorage
{
private Dictionary<string, ObjectContext> storage = new Dictionary<string, ObjectContext>();
/// <summary>
/// Initializes a new instance of the <see cref="SimpleObjectContextStorage"/> class.
/// </summary>
public ObjectContextStorage() { }
/// <summary>
/// Returns the object context associated with the specified key or
/// null if the specified key is not found.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public ObjectContext GetObjectContextForKey(string key)
{
ObjectContext context;
if (!this.storage.TryGetValue(key, out context))
return null;
return context;
}
/// <summary>
/// Stores the object context into a dictionary using the specified key.
/// If an object context already exists by the specified key,
/// it gets overwritten by the new object context passed in.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="objectContext">The object context.</param>
public void SetObjectContextForKey(string key, ObjectContext objectContext)
{
this.storage.Add(key, objectContext);
}
/// <summary>
/// Returns all the values of the internal dictionary of object contexts.
/// </summary>
/// <returns></returns>
public IEnumerable<ObjectContext> GetAllObjectContexts()
{
return this.storage.Values;
}
}
GenericRepository
public class GenericRepository : IRepository
{
private readonly string _connectionStringName;
private ObjectContext _objectContext;
private readonly PluralizationService _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
private bool _usePlurazation;
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository<TEntity>"/> class.
/// </summary>
public GenericRepository()
: this(string.Empty, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository<TEntity>"/> class.
/// </summary>
/// <param name="connectionStringName">Name of the connection string.</param>
public GenericRepository(string connectionStringName, bool usePlurazation)
{
this._connectionStringName = connectionStringName;
this._usePlurazation = usePlurazation;
}
/// <summary>
/// Initializes a new instance of the <see cref="GenericRepository"/> class.
/// </summary>
/// <param name="objectContext">The object context.</param>
public GenericRepository(ObjectContext objectContext, bool usePlurazation)
{
if (objectContext == null)
throw new ArgumentNullException("objectContext");
this._objectContext = objectContext;
this._usePlurazation = usePlurazation;
}
public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
{
EntityKey key = GetEntityKey<TEntity>(keyValue);
object originalItem;
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
return (TEntity)originalItem;
}
return default(TEntity);
}
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return ObjectContext.CreateQuery<TEntity>(entityName).OfType<TEntity>();
}
public IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().Where(predicate);
}
public IQueryable<TEntity> GetQuery<TEntity>(ISpecification<TEntity> specification) where TEntity : class
{
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>());
}
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return GetQuery<TEntity>().OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return GetQuery<TEntity>().OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public IEnumerable<TEntity> Get<TEntity>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return GetQuery<TEntity>().Where(predicate).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return GetQuery<TEntity>().Where(predicate).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public IEnumerable<TEntity> Get<TEntity>(ISpecification<TEntity> specification, Expression<Func<TEntity, string>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
if (sortOrder == SortOrder.Ascending)
{
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderBy(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
return specification.SatisfyingEntitiesFrom(GetQuery<TEntity>()).OrderByDescending(orderBy).Skip(pageIndex).Take(pageSize).AsEnumerable();
}
public TEntity Single<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().SingleOrDefault<TEntity>(criteria);
}
public TEntity Single<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
}
public TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
return GetQuery<TEntity>().FirstOrDefault(predicate);
}
public TEntity First<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).FirstOrDefault();
}
public void Add<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.AddObject(GetEntityName<TEntity>(), entity);
}
public void Attach<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.AttachTo(GetEntityName<TEntity>(), entity);
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
ObjectContext.DeleteObject(entity);
}
public void Delete<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
IEnumerable<TEntity> records = Find<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public void Delete<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
IEnumerable<TEntity> records = Find<TEntity>(criteria);
foreach (TEntity record in records)
{
Delete<TEntity>(record);
}
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().AsEnumerable();
}
public void Update<TEntity>(TEntity entity) where TEntity : class
{
var fqen = GetEntityName<TEntity>();
object originalItem;
EntityKey key = ObjectContext.CreateEntityKey(fqen, entity);
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
}
}
public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Where(criteria);
}
public TEntity FindOne<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Where(criteria).FirstOrDefault();
}
public TEntity FindOne<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntityFrom(GetQuery<TEntity>());
}
public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>());
}
public int Count<TEntity>() where TEntity : class
{
return GetQuery<TEntity>().Count();
}
public int Count<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class
{
return GetQuery<TEntity>().Count(criteria);
}
public int Count<TEntity>(ISpecification<TEntity> criteria) where TEntity : class
{
return criteria.SatisfyingEntitiesFrom(GetQuery<TEntity>()).Count();
}
public IUnitOfWork UnitOfWork
{
get
{
if (unitOfWork == null)
{
unitOfWork = new UnitOfWork(this.ObjectContext);
}
return unitOfWork;
}
}
private ObjectContext ObjectContext
{
get
{
if (this._objectContext == null)
{
if (string.IsNullOrEmpty(this._connectionStringName))
{
this._objectContext = ObjectContextManager.Current;
}
else
{
this._objectContext = ObjectContextManager.CurrentFor(this._connectionStringName);
}
}
return this._objectContext;
}
}
private EntityKey GetEntityKey<TEntity>(object keyValue) where TEntity : class
{
var entitySetName = GetEntityName<TEntity>();
var objectSet = ObjectContext.CreateObjectSet<TEntity>();
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
return entityKey;
}
private string GetEntityName<TEntity>() where TEntity : class
{
// WARNING! : Exceptions for inheritance
if (_usePlurazation)
{
return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));
}
else
{
return string.Format("{0}.{1}", ObjectContext.DefaultContainerName, typeof(TEntity).Name);
}
}
private IUnitOfWork unitOfWork;
}
I know it will take some time to read through the code, but it would help me miles is somebody looks at it and gives tips on what to do better or where I do not dispose an object.
Also I got a little question: "I would like to put a business layer above this repository, that would keep things like global.asax the same I guess but would need static classes(right?) like a BookProvider which gives me all the data about my book-entities?
Thanks in advance!
The only concrete remark I can give is about disposing the context:
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
{
if (ctx.Connection.State == System.Data.ConnectionState.Open)
ctx.Connection.Close();
}
ObjectContext implements IDisposable, so the standard way would be in my opinion:
foreach (ObjectContext ctx in Storage.GetAllObjectContexts())
ctx.Dispose();
As far as I know ObjectContext.Dispose() just closes the connection, so it does the same what you are doing. But I'd consider this as an internal implementation detail which might potentially change between EF releases.
Your generic repository is one as there are many of this kind. A few points which came to my mind when looking at the methods:
Since you are exposing IQueryable in public IQueryable<TEntity> GetQuery<TEntity>(...) why do you need most of the other methods like Single, First, Count, etc.? (Why not Any, etc.?) You get all this from your IQueryable.
Your Update method only works for scalar properties. But this is a common problem with generic repositories. There is no easy solution or no solution at all for updating entities in a generic way.
What is your goal you want to reach by using the repository pattern at all? If you have unit testability with an in-memory data store in mind, you cannot expose IQueryable because LINQ to Entities and LINQ to Objects are not the same. To test if your IQueryables work you need integration tests and the real database your application is supposed to work with in production. But if you don't expose IQueryable your repository needs a lot of business specific methods which return the results as POCOs, POCO collections or DTOs of projected/selected properties and hide the internal query specifications so that you can mock these methods with in-memory data to test your business logic. But this is the point where a generic repository is not sufficient anymore. (How are you going to write, for instance, a LINQ Join where more than one Entity/ObjectSet is involved in a repository which has only one entity type as generic parameter?)
If you ask ten people how their repositories are architectured, you'll get ten different answers. And none is really wrong or the best because it depends on the application you are going to build with this repository. I believe that nobody can tell you what your repository is really worth. You will see it in practice when you start writing an application. For some applications it might be over-architectured (which I consider as most dangerous because managing and keeping a meaningless architecture under control is expensive and a waste of time you lose for writing real application content). And for other needs you will likely have to extend the repository. For example:
How do you handle explicit loading or queries on navigation properties of an entity (with CreateSourceQuery or DbEntityEntry.Collection/Reference in EF 4.1)? If your application never needs explicite loading: Fine. If it's necessary you need to extend your repo.
How do you control eager loading? Sometimes perhaps you just want a parent entity. Sometimes you want to Includ the children1 collection and sometimes the child2 reference.
How do you set the entity state of an entity manually? Perhaps you never must. But in the next application it might be very helpful.
How do you detach an entity from the context manually?
How do you control loading behaviour (with or without tracking of entities by the context)?
How do you control manually lazy loading behaviour and creation of change tracking proxies?
How do you create an entity proxy manually? You might need it in some situations when you work with lazy loading or change tracking proxies.
How do you load entities into the context without building a result collection? Yet another repository method, perhaps... or perhaps not. Who knows in advance what your application logic will require.
And so on and so forth...