Synchronising a collection over a websocket connection - c#

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

Related

Deserializing received Json-Rpc data via Websocket API

I'm trying to figure out a way to create a generic Websocket JsonRpc client.
After connecting I'm starting a loop to listen for data coming from the WebSocket API and sending that data to the event as a string. I'd like to return a generic JsonRpcResponse<T> object instead but not sure how or if it's possible.
Where JsonRpcResponse has an Id and Method fields as defined in the spec and T is a specific type depending on the data received.
From what I've seen there's no way to use generics here since I'd have to call another event with a non-generic type or object to pass that data along.
public event Func<string, Task>? OnDataReceived;
public async Task ConnectAsync()
{
Uri uri = new Uri(_url);
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000);
await _client.ConnectAsync(uri, cancellationTokenSource.Token);
// Start listening for data
await Task.Factory.StartNew(async () =>
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
while (_client.State == WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested)
{
var receivedData = await ReadStringAsync(cancellationTokenSource.Token);
if (OnDataReceived != null)
{
await OnDataReceived.Invoke(receivedData);
}
}
};
}
How about this:
namespace GenericJsonResponse {
/// <summary>
/// Desired returned type
/// </summary>
/// <typeparam name="T"></typeparam>
public class JsonResponse<T> {
public T? Data { get; set; }
public int Id { get; set; } = 0;
public string Method { get; set; } = string.Empty;
/// <summary>
/// Convert string to anew instance
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
/// <remarks>will have to add to each sub class</remarks>
public static T FromJson(string json) {
throw new NotImplementedException();
}
}
/// <summary>
/// Generic socket listener / sub class per type
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericSocketListener<T> {
/// <summary>
/// exposes event with generic response
/// </summary>
public event Func<string, Task<JsonResponse<T>>>? OnDataReceived;
/// <summary>
/// Listen to socket
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public virtual async Task Listen( CancellationToken token) {
string data = string.Empty;
while (! token.IsCancellationRequested) {
// listen... and get typed result, save string to data
JsonResponse<T> result = await OnDataReceived.Invoke(data);
}
}
}
/// <summary>
/// Dummy poco
/// </summary>
public class Person {
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
}
/// <summary>
/// Class that will listen to people
/// </summary>
public class PersonSocketListener : GenericSocketListener<Person> {
}
}

Silverlight talking to a WCF using RIA Services Silverlight Client

My silverlight project has references to the RIA Services Silverlight Client and my .web application has access to RIAServices.Server.
I can't seem to find a good tutorial to learn how to connect these. Once I understand how to get the data from my NumberGenerator method. I can pick it up from there. I have three questions that I need some help with.
First, am I setting up this project correctly? I never done a project with RIA before.
Secondly what reference do I need to use ServiceContract(), FaultContract, and OperationContract? Most examples show that they are in the System.ServiceModel library. In this case with using the external library RIA Services Silverlight Client that is not the case. It throws a error saying I am missing a reference. What other libraries would have those three in it?
My last question is what URI do you use in the SystemDomainContext? Where I found the code at used this MyFirstRIAApplication-Web-EmployeeDomainService.svc but in my case I don't have anything that has a .svc extension. Would I use the .xap or .aspx?
Here is my Silverlight code
Here is my NumberGenerator Silverlight UserControl Page Code
public partial class NumberGenerator : UserControl
{
public NumberGenerator()
{
InitializeComponent();
GenerateNumber();
}
private void GenerateButton_Click(object sender, RoutedEventArgs e)
{
GenerateNumber();
}
private void GenerateNumber()
{
int result = 0
SystemClientServices systemclientservices = SystemClientServices.Instance;
result = systemclientservices.GenerateNumber();
NumberLabel.Content = result;
}
}
Here is my System Client Services class
private readonly static SystemClientServices _instance = new SystemClientServices();
private SystemDomainContext _domainContext = new SystemDomainContext();
private SystemClientServices() { }
public static SystemClientServices Instance
{
get
{
return _instance;
}
}
public int GenerateNumber()
{
//Code goes here to get the information from the Domainservices
LoadOperation load = this._domainContext.Load(this._domainContext.GetNumberGeneratorQuery(), LoadBehavior.KeepCurrent, false);
return Convert.ToInt32(load.Entities);
}
Here is my NumberGenerator class on the silverlight local project
public sealed partial class NumberGenerator : Entity
{
private static readonly NumberGenerator _instance = new NumberGenerator();
public int NumberGenerated { get; set; }
public NumberGenerator()
{
NumberGenerated = 0;
}
public static NumberGenerator Instance
{
get
{
return _instance;
}
}
}
Here is System Domain Conext class on the silverlight local project
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel.DomainServices;
using System.ServiceModel.DomainServices.Client;
using System.ServiceModel.DomainServices.Client.ApplicationServices;
using System.ServiceModel.Web;
using System.ServiceModel;
public sealed partial class SystemDomainConext : DomainContext
{
#region Extensibility Method Definitions
/// <summary>
/// This method is invoked from the constructor once initialization is complete and
/// can be used for further object setup.
/// </summary>
partial void OnCreated();
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="EmployeeDomainContext"/> class.
/// </summary>
public SystemDomainConext() : this(new WebDomainClient<ISystemDomainServiceContract>(new Uri("MyFirstRIAApplication-Web-EmployeeDomainService.svc", UriKind.Relative)))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EmployeeDomainContext"/> class with the specified service URI.
/// </summary>
/// <param name="serviceUri">The EmployeeDomainService service URI.</param>
public SystemDomainConext(Uri serviceUri) : this(new WebDomainClient<ISystemDomainServiceContract>(serviceUri))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EmployeeDomainContext"/> class with the specified <paramref name="domainClient"/>.
/// </summary>
/// <param name="domainClient">The DomainClient instance to use for this DomainContext.</param>
public SystemDomainConext(DomainClient domainClient) : base(domainClient)
{
this.OnCreated();
}
/// <summary>
/// Gets the set of <see cref="Employee"/> entity instances that have been loaded into this <see cref="EmployeeDomainContext"/> instance.
/// </summary>
public EntitySet<NumberGenerator> GeneratedNumber
{
get
{
return base.EntityContainer.GetEntitySet<NumberGenerator>();
}
}
/// <summary>
/// Gets an EntityQuery instance that can be used to load <see cref="Employee"/> entity instances using the 'GetEmployee' query.
/// </summary>
/// <returns>An EntityQuery that can be loaded to retrieve <see cref="Employee"/> entity instances.</returns>
public EntityQuery<NumberGenerator> GetNumberGeneratorQuery()
{
this.ValidateMethod("GetGeneratedNumber", null);
return base.CreateQuery<NumberGenerator>("GetNumberGenerator", null, false, true);
}
/// <summary>
/// Creates a new EntityContainer for this DomainContext's EntitySets.
/// </summary>
/// <returns>A new container instance.</returns>
protected override EntityContainer CreateEntityContainer()
{
return new NumberGeneratorDomainContextEntityContainer();
}
/// <summary>
/// Service contract for the 'EmployeeDomainService' DomainService.
/// </summary>
[ServiceContract()]
public interface ISystemDomainServiceContract
{
/// <summary>
/// Asynchronously invokes the 'GetEmployee' operation.
/// </summary>
/// <param name="callback">Callback to invoke on completion.</param>
/// <param name="asyncState">Optional state object.</param>
/// <returns>An IAsyncResult that can be used to monitor the request.</returns>
[FaultContract(typeof(DomainServiceFault), Action="http://tempuri.org/EmployeeDomainService/GetEmployeeDomainServiceFault", Name="DomainServiceFault", Namespace="DomainServices")]
[OperationContract(AsyncPattern=true, Action="http://tempuri.org/EmployeeDomainService/GetEmployee", ReplyAction="http://tempuri.org/EmployeeDomainService/GetEmployeeResponse")]
[WebGet()]
IAsyncResult GetGeneratedNumber(AsyncCallback callback, object asyncState);
/// <summary>
/// Completes the asynchronous operation begun by 'BeginGetEmployee'.
/// </summary>
/// <param name="result">The IAsyncResult returned from 'BeginGetEmployee'.</param>
/// <returns>The 'QueryResult' returned from the 'GetEmployee' operation.</returns>
QueryResult<NumberGenerator> EndGetGeneratedNumber(IAsyncResult result);
/// <summary>
/// Asynchronously invokes the 'SubmitChanges' operation.
/// </summary>
/// <param name="changeSet">The change-set to submit.</param>
/// <param name="callback">Callback to invoke on completion.</param>
/// <param name="asyncState">Optional state object.</param>
/// <returns>An IAsyncResult that can be used to monitor the request.</returns>
[FaultContract(typeof(DomainServiceFault), Action="http://tempuri.org/EmployeeDomainService/SubmitChangesDomainServiceFault", Name="DomainServiceFault", Namespace="DomainServices")]
[OperationContract(AsyncPattern=true, Action="http://tempuri.org/EmployeeDomainService/SubmitChanges", ReplyAction="http://tempuri.org/EmployeeDomainService/SubmitChangesResponse")]
IAsyncResult BeginSubmitChanges(IEnumerable<ChangeSetEntry> changeSet, AsyncCallback callback, object asyncState);
/// <summary>
/// Completes the asynchronous operation begun by 'BeginSubmitChanges'.
/// </summary>
/// <param name="result">The IAsyncResult returned from 'BeginSubmitChanges'.</param>
/// <returns>The collection of change-set entry elements returned from 'SubmitChanges'.</returns>
IEnumerable<ChangeSetEntry> EndSubmitChanges(IAsyncResult result);
}
internal sealed class NumberGeneratorDomainContextEntityContainer : EntityContainer
{
public NumberGeneratorDomainContextEntityContainer()
{
this.CreateEntitySet<NumberGenerator>(EntitySetOperations.Edit);
}
}
}
Here is my System Domain Services class that is on the Silverlight.web
[EnableClientAccess()]
public class SystemDomainServices : DomainService
{
private NumberGenerator numberGenerator = NumberGenerator.Instance;
public int NumberGenerate()
{
return numberGenerator.NumberGenerated;
}
}
Here is the NumberGenerator class on the silverlight.web
private static readonly NumberGenerator _instance = new NumberGenerator();
[Key]
public int NumberGenerated { get; set; }
public NumberGenerator()
{
NumberGenerated = GenerateNumber();
}
public static NumberGenerator Instance
{
get
{
return _instance;
}
}
public int GenerateNumber()
{
string db_date = "";
int db_num = 0;
string todaysdate = "";
int temp_num = db_num;
int result = 0;
using (SqlConnection connection = new SqlConnection(DBConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT * FROM table", connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
db_date = reader.GetString(0);
db_num = (int)(reader.GetSqlInt32(1));
todaysdate = DateTime.Now.ToString("yMMdd");
temp_num = db_num;
}
reader.Close();
}
if (todaysdate != db_date)
{
using (SqlCommand dateUpate = new SqlCommand("UPDATE table SET tsDate='" + todaysdate + "' WHERE tsDate='" + db_date + "'", connection))
{
dateUpate.ExecuteNonQuery();
}
db_num = 0;
db_date = todaysdate;
}
db_num++;
using (SqlCommand numUpate = new SqlCommand("UPDATE table SET tsNum='" + db_num + "' WHERE tsNum='" + temp_num + "'", connection))
{
numUpate.ExecuteNonQuery();
}
result = Convert.ToInt32(db_date + db_num.ToString().PadLeft(3, '0'));
connection.Close();
connection.Dispose();
}
return result;
}
}
The answer to question two you might be have to go to Tools at the top, NuGet Package Manager, Package Management Console, and then type the following command to install the package for those three functions.
PM> Install-Package RIAServices.Silverlight.DomainDataSource

Get the exact type as property in C# class

Sorry for the general title but it's a bit hard to explain in few words what is my problem currently.
So I have a simple class factory like this:
public Model Construct<T>(T param) where T : IModelable
{
new Model {Resource = param};
return n;
}
The Model class looks like this:
public class Model
{
public object Resource { get; set; }
}
The problem is, that you can see, is the Resource is currently an object. And I would like that Resource should be the type, what is get from the Construct and not lost the type-safe...
I tried to solve it with type parameter but it fails, because I can extend Model class with type parameter but what if I would like to store it to a simple class repository?
Then Construct will work, but if I would like to get the instanced class from the repository, I have to declare the type paramter again like:
Repository.Get<Model<Spaceship>>(0) .... and of course it's wrong because I would like that Model itself knows, what type of Resource has been added in Construct.
Does anybody any idea how to handle this?
The whole code currently look like this:
/// <summary>
/// Class Repository
/// </summary>
public sealed class Repository
{
/// <summary>
/// The _lock
/// </summary>
private static readonly object _lock = new object();
/// <summary>
/// The _syncroot
/// </summary>
private static readonly object _syncroot = new object();
/// <summary>
/// The _instance
/// </summary>
private static volatile Repository _instance;
/// <summary>
/// The _dict
/// </summary>
private static readonly Dictionary<int, object> _dict
= new Dictionary<int, object>();
/// <summary>
/// Prevents a default data of the <see cref="Repository" /> class from being created.
/// </summary>
private Repository()
{
}
/// <summary>
/// Gets the items.
/// </summary>
/// <value>The items.</value>
public static Repository Data
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null) _instance = new Repository();
}
}
return _instance;
}
}
/// <summary>
/// Allocates the specified id.
/// </summary>
/// <param name="id">The id.</param>
/// <param name="parameter">The parameter.</param>
/// <resource name="id">The id.</resource>
/// <resource name="parameter">The parameter.</resource>
public void Allocate(int id, object parameter)
{
lock (_syncroot)
{
_dict.Add(id, parameter);
}
}
/// <summary>
/// Gets the specified id.
/// </summary>
/// <typeparam name="T">The type of the tref.</typeparam>
/// <param name="id">The id.</param>
/// <returns>``0.</returns>
/// <resource name="id">The id.</resource>
public T Get<T>(int id)
{
lock (_syncroot)
{
return (T) _dict[id];
}
}
}
/// <summary>
/// Class IModelFactory
/// </summary>
public sealed class ModelFactory
{
/// <summary>
/// The _lock
/// </summary>
private static readonly object _lock = new object();
/// <summary>
/// The _instance
/// </summary>
private static volatile ModelFactory _instance;
/// <summary>
/// Prevents a default instance of the <see cref="ModelFactory" /> class from being created.
/// </summary>
private ModelFactory()
{
}
/// <summary>
/// Gets the data.
/// </summary>
/// <value>The data.</value>
public static ModelFactory Data
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null) _instance = new ModelFactory();
}
}
return _instance;
}
}
/// <summary>
/// Constructs the specified param.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="param">The param.</param>
/// <returns>Model{``0}.</returns>
public Model Construct<T>(T param) where T : IModelable
{
var n = new Model {Resource = param};
return n;
}
}
/// <summary>
/// Class Model
/// </summary>
/// <typeparam name="T"></typeparam>
public class Model
{
public object Resource { get; set; }
}
/// <summary>
/// Interface IModelable
/// </summary>
public interface IModelable
{
/// <summary>
/// Gets or sets the mass.
/// </summary>
/// <value>The mass.</value>
float Mass { get; set; }
}
/// <summary>
/// Class spaceship
/// </summary>
public class Spaceship : IModelable
{
/// <summary>
/// Gets or sets the mass.
/// </summary>
/// <value>The mass.</value>
public float Mass { get; set; }
}
So the problem will be lighted here:
Add to the Repository:
Repository.Data.Allocate(1, ModelFactory.Data.Construct(new Spaceship()));
It's okay, but after:
var test_variable = Repository.Data.Get<Model>(1);
So now I have a non type-safe object from a type parameter, I don't know, that what type of class has been stored with the c model construction.
I'm very thankful for the suggestions of using type paramter on the Model class as well, but than it will come up another problem, because I have to change the Get function with it:
var test_variable = Repository.Data.Get<Model<Spaceship>>(1);
But that's definitely wrong, because I won't know, that what kind of type of class has been stored in the model..., so I would like to achieve to avoid this type parameter definition when I would like to load the instance from the Repository.
You can solve this by making your Model class generic, like this:
public class Model<T>
{
public T Resource { get; set; }
}
Then, your Construct method could work like this:
public Model<T> Construct<T>(T param) where T : IModelable<T>
{
return new Model<T>() {Resource = param};
}
You probably need a generic type in the model class:
public class Model<T>
{
public T Resource { get; set; }
}
This sort of structure is one approach you could take:
public Model<T> Construct<T>(T param) where T : IModelable
{
var n = new Model<T> {Resource = param};
return n;
}
public class Model<T> : IModel<T> where T : IModelable
{
public T Resource { get; set; }
}
public interface IModel<out T> where T : IModelable
{
T Resource { get; }
}
This covariant interface allows you to refer to the types more generically where you wish, in the same way that you can pass an IEnumerable<string> into something expecting an IEnumerable<object>.
IModel<Spaceship> shipModel = // whatever
IModel<IModelable> model = shipModel;
//or even:
List<Model<Spaceship>> shipModels = // whatever
IEnumerable<IModel<IModelable>> models = shipModels;

Unit Testing Entity Framework does not return 100% coverage using shims and fakes?

I'm trying to unit test a repository, but what happen is that when I test it I don't get a 100% coverage rather I get a 0% code coverage on that specific method.
I want to test without using third party frameworks, that's why I wanted to use shims and fakes.
Here is the class that I'm trying to test:
namespace AbstractFactory.Repository
{
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
/// <summary>
/// This class serves as the structure of the Author repository using a database
/// </summary>
public class DbAuthorRepository : IRepository<Author>
{
/// <summary>
/// The context
/// </summary>
private AbstractFactoryPatternEntities context;
/// <summary>
/// Initializes a new instance of the <see cref="DbAuthorRepository"/> class.
/// </summary>
/// <param name="context">The context.</param>
public DbAuthorRepository(AbstractFactoryPatternEntities context)
{
this.context = context;
}
/// <summary>
/// Adds the specified author.
/// </summary>
/// <param name="author">The author.</param>
public void Add(Author author)
{
this.context.Authors.Add(author);
}
/// <summary>
/// Removes the specified author.
/// </summary>
/// <param name="author">The author.</param>
public void Remove(Author author)
{
this.context.Authors.Remove(author);
}
/// <summary>
/// Saves this instance.
/// </summary>
public void Save()
{
this.context.SaveChanges();
}
/// <summary>
/// Gets all.
/// </summary>
/// <returns>
/// returns a list of all the authors
/// </returns>
public IEnumerable<Author> GetAll()
{
List<Author> result = this.context.Authors.ToList();
return result;
}
/// <summary>
/// Gets the by id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>
/// returns an entity
/// </returns>
public Author GetById(int id)
{
Author result = this.context.Authors.Find(id);
////this.context.Authors.Single(a => a.AuthorId == id);
return result;
}
}
}
Here is the test class:
[TestClass]
public class DbAuthorRepositoryTest
{
private ShimAbstractFactoryPatternEntities shimContext;
private ShimDbSet<Author> shimAuthors;
private List<Author> listAuthors;
private DbAuthorRepository repository;
private void SetupShims()
{
this.shimContext = new ShimAbstractFactoryPatternEntities(new AbstractFactoryPatternEntities());
this.listAuthors = new List<Author>
{
new Author { AuthorId = 2, FirstName = "Test2", LastName = "Test2" },
new Author { AuthorId = 3, FirstName = "Test3", LastName = "Test3" },
new Author { AuthorId = 4, FirstName = "Test4", LastName = "Test4" }
};
this.shimAuthors = new ShimDbSet<Author>();
this.shimAuthors.Bind(this.listAuthors.AsQueryable());
ShimAbstractFactoryPatternEntities.AllInstances.AuthorsGet = (a) => { return shimAuthors; };
ShimDbAuthorRepository.AllInstances.GetAll = (a) => { return this.shimContext.Instance.Authors.ToList(); };
//return this.shimContext.Instance.Authors.ToList();
//return this.shimAuthors.Instance.ToList() as IEnumerable<Author>;
//ShimDbSet<Author>.AllInstances.FindObjectArray = (a, b) => { a.ToList(); return shimAuthors.Instance.Contains(b) ; };
}
private void SetupRepository()
{
this.SetupShims();
this.repository = new DbAuthorRepository(new AbstractFactoryPatternEntities());
}
Here is the test method:
[TestMethod]
public void GetAll_MethodIsCalled_RepositoryReturnsCorrectNumberOfAuthorsInTheDbSet()
{
using(ShimsContext.Create())
{
this.SetupRepository();
var authorsCount = this.repository.GetAll().Count();
Assert.AreEqual(authorsCount,3);
}
}
I'm getting 0% code coverage on the GetAll method, how do I get it to 100% and why is it getting 0% ?
Of course you are, your Shim has replaced the functionality completely, thus you're not seeing coverage. Since you're already making sure that context.Authors is overridden, why are you still overriding the GetAll method, which doesn't depend on anything you haven't already overridden?

Can I create a List<Class<T>>?

I have a class
public class Setting<T>
{
public string name { get; set; }
public T value { get; set; }
}
now I want to create an IList<Setting<T>> but with different types of Setting<T>'s T in it, I want e.G.
List<Setting<T>> settingsList;
settingsList.Add(new Setting<int>());
settingsList.Add(new Setting<string>());
I've tried IList<Setting<T>> but this seems not possible since the compiler doesn't find Type T.
I know that I could use object but I want it to be strongly typed. So my question is if there is a possibility of getting this working.
Generic types do not have a common type or interface amongst concrete definitions by default.
Have your Setting<T> class implement an interface (or derive from a common class) and create a list of that interface (or class).
public interface ISetting { }
public class Setting<T> : ISetting
{
// ...
}
// example usage:
IList<ISetting> list = new List<ISetting>
{
new Setting<int> { name = "foo", value = 2 },
new Setting<string> { name = "bar", value "baz" },
};
You have to use a common ancestor class for all the class types that you put into the list. If it should be arbitrary types you have to use object for T
You can use T only inside your class:
class Setting<T>
{
// T is defined here
}
not outside. Outside you need to specify the concrete type:
Settings<string> stringSettings = new Settings<string>();
or
List<Settings<string>> list = new List<Settings<string>>();
list.Add(stringSettings);
ye you can do it by using reflection
i wrote such code for another question but you can use this
public abstract class GenericAccess
{
public static IList<T> GetObjectListFromArray<T>(T item)
{
var r = new List<T>();
var obj = typeof(T).Assembly.CreateInstance(typeof(T).FullName);
var p = obj.GetType().GetProperty("Id", System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (p != null)
{
p.SetValue(obj, item, null);
var m = r.GetType().GetMethod("Add");
m.Invoke(r, new object[] { obj });
}
return r;
}
}
and call it like this
IList<int> r = GenericAccess.GetObjectListFromArray<int>(1);
/// <summary>
/// 消息回调委托
/// </summary>
/// <typeparam name="T">TClass</typeparam>
/// <param name="result">返回实体</param>
/// <param name="args">内容包</param>
public delegate void CallbackHandler<in T>(T result, BasicDeliverEventArgs args);
/// <summary>
/// 注册监听程序接口
/// </summary>
public interface IRegistration
{
/// <summary>
/// 路由名称
/// </summary>
string ExchangeName { get; }
/// <summary>
/// 路由Key
/// </summary>
string RouteKey { get; }
/// <summary>
/// 回调操作
/// </summary>
/// <param name="args"></param>
void Call(BasicDeliverEventArgs args);
}
/// <summary>
/// 注册监听程序
/// </summary>
/// <typeparam name="T"></typeparam>
public class Registration<T> : IRegistration where T : class
{
/// <summary>
/// 路由名称
/// </summary>
public string ExchangeName { get; set; }
/// <summary>
/// 路由Key
/// </summary>
public string RouteKey { get; set; }
/// <summary>
/// 消息处理器委托
/// </summary>
public CallbackHandler<T> Handler { get; set; }
/// <summary>
/// 接口回调操作
/// </summary>
/// <param name="args"></param>
public void Call(BasicDeliverEventArgs args)
{
var message = Encoding.UTF8.GetString(args.Body.ToArray());
Handler?.Invoke(JsonConvertHandler.DeserializeObject<T>(message), args);
}
}
/// <summary>
/// 消息监听处理器
/// </summary>
public static class ListenerHandler
{
/// <summary>
/// 单任务锁
/// </summary>
private static readonly object Locker = new object();
/// <summary>
/// 所有注册列表
/// </summary>
public static List<IRegistration> Registrations { get; private set; }
/// <summary>
/// 单例化
/// </summary>
static ListenerHandler()
{
List<ListenerSetting> listeners = MqNoticeHandler.Setting.GetListeners();
MqNoticeHandler.Listener(OnMessage, listeners);
}
/// <summary>
/// 注册监听
/// </summary>
/// <param name="registration"></param>
public static void Register(IRegistration registration)
{
lock (Locker)
Registrations.Add(registration);
}
/// <summary>
/// 解除注册
/// </summary>
/// <param name="registration"></param>
public static void UnRegister(IRegistration registration)
{
lock (Locker)
Registrations.Remove(registration);
}
/// <summary>
/// 获取监听列表
/// </summary>
/// <param name="exchangeName"></param>
/// <param name="routeKey"></param>
/// <returns></returns>
public static IEnumerable<IRegistration> GetRegistrations(string exchangeName, string routeKey)
{
return Registrations.Where(x => x.ExchangeName == exchangeName && x.RouteKey == routeKey);
}
/// <summary>
/// 消息回调处理
/// </summary>
/// <param name="consumer">消费者</param>
/// <param name="args">消息包</param>
/// <returns></returns>
private static MqAckResult OnMessage(EventingBasicConsumer consumer, BasicDeliverEventArgs args)
{
//Example Query and Call
Registrations.First().Call(args);
//Return
return new MqAckResult { Accept = true, ReQueue = false };
}
}

Categories