IRedisClient not disposed after using - redis

I am using an ServiceStack IRedis client as follows
public static IRedisList<MyType> getList(string listkey)
{
using(var redis = new RedisClient())
{
var client = redis.As<MyType>();
return client.Lists[listkey];
}
}
public void AddSomething(MyType newType)
{
var list = getList("somekey);
list.Add(newType);.
}
according to the Redis-Server output, everytime I do this a new client connection is added, and it never is getting disposed. (Client count is always increasing).
Should I not be manipulating the IRedisList as such?

IRedisList is like a proxy wrapper for a redis server-side list which includes a reference to the RedisClient it was created with in order to talk Redis.
The using(var redis = new RedisClient()) { ... } statement does dispose of the Client connection, unfortunately when you make any further calls with the Disposed client it re-opens the connection again and since it isn't in a using statement or not manually disposed will keep the connection open.
To prevent this from happening, you should not be accessing the RedisList proxy outside of the using scope of the RedisClient in which it was created. If you still want to reference the list outside of this scope you should clone the contents into an In-Memory list with:
var disconnectedInMemoryCopy = client.Lists[listKey].ToList();
This returns a populated normal C# List<T> that's de-coupled from the RedisList proxy.

Related

Microsoft Distrubted Redis Cache - Getting keys based on pattern

We are working with the Microsoft Distrbuted Cache implementation for .NET core. See https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-2.1 for more information.
Now we can get an key by the following code.
var cacheKey = "application:customer:1234:profile";
var profile = _distributedCache.GetString(cacheKey);
What i want to do is tho do the following:
var cacheKey = "application:customer:1234:*";
var customerData = _distributedCache.GetString(cacheKey);
So that we can get the following keys with this pattern:
application:customer:1234:Profile
application:customer:1234:Orders
application:customer:1234:Invoices
application:customer:1234:Payments
Could not get this work with any wildcard or without an wild card. Is there an solution without implementing another Redis nuget package?
This isn't supported via the IDistributeCache interface. It's designed to get/set a specific key, not return a range of keys. If you need to do something like this, you'll need to drop down into the underlying store, i.e. Redis. The good news is that you don't need anything additional: the same StackExchange.Redis library that is needed to support the Redis IDistributedCache implementation also provides a client you can utilize directly.
In particular to your scenario here, you'd need some code like:
var server = _redis.GetServer(someServer);
foreach(var key in server.Keys(pattern: cacheKey)) {
// do something
}
Here, _redis is an instance of ConnectionMultiplexer. This should already be registered in your service collection since it's utilized by the Redis IDistributedCache implementation. As a result, you can inject it into the controller or other class where this code exists.
The someServer variable is a reference to one of your Redis servers. You can get all registered Redis servers via _redis.GetEndpoints(). That will return an IEnumerable of servers, which you can either pick from or enumerate over. Additionally, you can simply connect directly to a particular server via passing the host string and port:
var server = _redis.GetServer("localhost", 6379);
Be advised, though, that Keys() will result in either a SCAN or KEYS command being issued at the Redis server. Which is used depends on the server version, but either is fairly inefficient, as the entire keyspace must be looked at. It is recommended that you do not use this in production, or if you must, that you issue it on a slave server.
With your question technically answered, given the complexity and the inherent inefficiency of SCAN/KEYS, you'd be better served just doing something like:
var cacheKeyPrefix = "application:customer:1234";
var profile = _distributedCache.GetString($"{cacheKeyPrefix}:Profile");
var orders = _distributedCache.GetString($"{cacheKeyPrefix}:Orders");
var invoices = _distributedCache.GetString($"{cacheKeyPrefix}:Invoices");
var payments = _distributedCache.GetString($"{cacheKeyPrefix}:Payments");
That's going to end up being much quicker and doesn't require anything special.
I know question is a bit old but based on this answear: How to get all keys data from redis cache
This is example solution:
in CustomerRepository.cs
using Newtonsoft.Json;
using StackExchange.Redis;
// ...
public class CustomerRepository : ICustomerRepository
{
private readonly IDistributedCache _redis;
private readonly IConfiguration _configuration;
public CustomerRepository(IDistributedCache redis, IConfiguration configuration)
{
_redis = redis;
_configuration = configuration;
}
///<summary>replace `object` with `class name`</summary>
public async Task<object> GetCustomersAsync(string name)
{
ConfigurationOptions options = ConfigurationOptions.Parse(_configuration.GetConnectionString("DefaultConnection"));
ConnectionMultiplexer connection = ConnectionMultiplexer.Connect(options);
IDatabase db = connection.GetDatabase();
EndPoint endPoint = connection.GetEndPoints().First();
var pattern = $"application:customer:{name}:*";
RedisKey[] keys = connection.GetServer(endPoint).Keys(pattern: pattern).ToArray();
var server = connection.GetServer(endPoint);
var result = await _redis.GetStringAsync(key);
return JsonConvert.DeserializeObject<object>(result);
}
}
in appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "localhost:6379,password=YOUR_PASSWORD_HERE"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

WCF server client async callback with object

I have Server and Client.
The client call a method on the server, The server in response have to prepare a Dictionary and send back to client.
This operation might take time.
It should be async. I read instructions and examples.
Mostly the BeginXXX and EndXXX IAsyncResult. But If i need the server to return Dictionary of objects. How do i implement this ?
I thought when sending a callback delegate I can send a signature of one of the clients' functions as delegate and when Server finish it will invoke the delegate with the proper dictionary data.
1) Adding service reference in VS (or SLSvcUtil) generates handy proxy code with events for Silverlight projects. You can use it to implement callback's pattern for your 'service/server agent' if you want. You can use this code with regular .net apps as well with some modifications.
2) VS for .Net project can generate proxy with async methods. SvcUtil - too.
3) For real long running operations you can use WCF services with callbacks (+ some binding restrictions).
If I understand you in a right way, you're asking about this (based on eLibrary project):
WCF Server side:
EBooksLibraryEntities ebe = new EBooksLibraryEntities();
public List<Book> tabBooks()
{
return ebe.Books.ToList();
}
Client side:
ServiceReference1.Service1Client sr = new ServiceReference1.Service1Client();
List<ServiceReference1.Book> llb = new List<ServiceReference1.Book>();
private void GetBooksByAuthor()
{
sr.tabBooksByAuthorCompleted += new EventHandler<ServiceReference1.tabBooksByAuthorCompletedEventArgs>(sr_tabBooksByAuthorCompleted);
sr.tabBooksByAuthorAsync();
}
void sr_tabBooksByAuthorCompleted(object sender, ServiceReference1.tabBooksByAuthorCompletedEventArgs e)
{
foreach (var item in e.Result)
{
yourList.Add(item);
}
}

Redis on Appharbor - Booksleeve GetString exception

i am trying to setup Redis on appharbor. I have followed their instructions and again i have an issue with the Booksleeve API. Here is the code i am using to make it work initially:
var connectionUri = new Uri(url);
using (var redis = new RedisConnection(connectionUri.Host, connectionUri.Port, password: connectionUri.UserInfo.Split(new[] { ':' }, 2)[1]))
{
redis.Strings.Set(1, "greeting", "welcome to remember your stuff!");
try
{
var task = redis.Strings.GetString(1, "greeting");
redis.Wait(task);
ViewBag.Message = task.Result;
}
catch (Exception)
{
// It throws an exception trying to wait for the task?
}
}
However, the issue is that it sets the string correctly, but when trying to retrieve the same string from the key value store, it throws a timeout exception waiting for the task to eexecute. However, this code works on my local redis server connection.
Am i using the API in a wrong way? or is this something related to Appharbor?
Thanks
Like a SqlConnection, you need to call Open() (otherwise your messages are queued for delivery).
Unlike SqlConnection, you should not fire up a RedisConnection each time you need it - it is intended to be used as a shared, thread-safe, multiplexer - i.e. a single connection is held somewhere and used by lots and lots of unrelated callers. Unless of course you only need to do one thing!

Changing connection string at runtime for OData/WCF Data Service which uses basic authentication

I have an ODATA services with a single schema. These point to a development database, and is served through a WCF Data Service which is then used by clients running Excel/Powerpivot to fetch their own data for reports and such.
The service is secured at runtime through pretty much the same basic authentication explained here: http://msdn.microsoft.com/en-us/data/gg192997
Now how this needs to work in the live environment is sit on the server and connect to different databases based on the username/password supplied. the Users will be typing in 'username#clientID' and 'password'. 'username#clientID' is then split() and username/password is checked against the SQL database. But the database server URL to check against will be determined by ClientID.
Also, once it is authorized the WCF data service needs to return data from the Database corresponding to the ClientID.
The approach I tried was to modify the connection string in the web.config file, but this doesn't work because it says the file is read-only. I'm not even sure if this would have worked at all. What I need to do is get the EDMX/WCF Data service to return the data from the correct database. Here's what I tried to do:
private static bool TryAuthenticate(string user, string password, out IPrincipal principal)
{
Configuration myWebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
myWebConfig.AppSettings.Settings["test"].Value = "Hello";
myWebConfig.Save();
string newConnStr = myWebConfig.ConnectionStrings.ConnectionStrings["IntelCorpEntities"].ToString();
newConnStr.ToString().Replace("SERGEIX01", "SERVERX01");
myWebConfig.ConnectionStrings.ConnectionStrings["IntelCorpEntities"].ConnectionString = newConnStr;
myWebConfig.Save();
if (user.ToLower().Equals("admin") && password.Equals("password"))
{
principal = new GenericPrincipal(new GenericIdentity(user), new string[] { "Users" });
return true;
}
else
{
principal = null;
return false;
}
}
In your DataService derived class override the CreateDataSource method and in it figure out the right connect string, create a new instance of the EF object context for the connection string and return it.
The WCF DS Service will not use the default constructor on the EF object context then, it's completely up to you construct the instance with the right connection string.
In your svc.cs file add following :
protected override NorthWindEntity CreateDataSource()
{
System.Data.EntityClient.EntityConnection connection = new System.Data.EntityClient.EntityConnection();
connection.ConnectionString = "";
NorthWindEntity ctx = new NorthWindEntity(connection);
return ctx;
}

NHibernate - ISession

About the declaration of ISession.
Should we close the Session everytime we use it, or should we keep it open?
I'm asking this because in manual of NHibernate (nhforge.org) they recommend us to declare it once in Application_Start for example, but i don't know if we should close it everytime we use.
Thanks
You can keep one single static reference to a ISessionFactory, which can be indeed instantiated in Application_Start for web applications.
However, ISession must not be kept open and cannot be shared between two or more requests. You should adopt the "one session per request" pattern which allows you to build a single ISession for each HTTP request and dispose it safely once the request has been handled (this is assuming you are writing a web application).
For instance, the code handling NHibernate sessions in you project might look like this:
public static class NHibernateHelper {
static ISessionFactory _factory;
public static NHibernateHelper(){
//This code runs once when the application starts
//Use whatever is needed to build your ISessionFactory (read configuration, etc.)
_factory = CreateYourSessionFactory();
}
const string SessionKey = "NhibernateSessionPerRequest";
public static ISession OpenSession(){
var context = HttpContext.Current;
//Check whether there is an already open ISession for this request
if(context != null && context.Items.ContainsKey(SessionKey)){
//Return the open ISession
return (ISession)context.Items[SessionKey];
}
else{
//Create a new ISession and store it in HttpContext
var newSession = _factory.OpenSession();
if(context != null)
context.Items[SessionKey] = newSession;
return newSession;
}
}
}
This code is probably far to simple and has not been tested (nor compiled in fact), but it should work. For a more safe handling of your sessions you could also use an IoC container (Autofac for instance) and register your ISessions with a lifetime that depends on HTTP requests (Autofac will handle everything for you in that case).
Sessions should be closed when you are done with them. There are multiple possible ways to manage the lifetime of a session and choosing the right one is specific to each scenario. "Unit of Work" and "Session per Request" are the two most often used session lifetime management patterns.
In Application_Start, you should create the SessionFactory, not the Session. The SessionFactory does not need to be closed.