[en] LINQ support

Topics: Announcements
Coordinator
Mar 12, 2011 at 2:53 PM
Edited Mar 12, 2011 at 2:55 PM

I've added basic LINQ support. Client-side code looks like this:

using (var conn = new ZyanConnection(Settings.Default.ServerUrl))
{
   // this is an extension method
   var proxy = conn.CreateQueryableProxy();

   // system assemblies loaded by server
   var assemblies =
      from asm in proxy.Get<Assembly>()
      where asm.FullName.ToLower().StartsWith("system")
      orderby asm.GetName().Name.Length ascending
      select asm;

   // list of files in server's MyDocuments folder
   var files =
      from file in proxy.Get<FileInfo>()
      where file.Length > 1024
      select new 
      {
         file.Name,
         file.Length
      };

   // foreach (var f in query) ...
}

Server code is as follows (querying POCO objects):

using (var host = new ZyanComponentHost(Settings.Default.ServiceUri, Settings.Default.TcpPort))
{
  host.RegisterComponent<IQueryRemoteHandler>(() => new ZyanServerQueryHandler(new SampleSource()));

  Console.WriteLine("Linq server started. Press ENTER to quit.");
  Console.ReadLine();
}

// Sample POCO object source could look like this

public class SampleSource : IObjectSource
{
   public IEnumerable<T> Get<T>() where T : class
   {
      // Get<FileInfo>() returns file list of MyDocuments folder
      if (typeof(T) == typeof(FileInfo))
      {
         var folder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
         foreach (var s in Directory.GetFiles(folder))
         {
            var fi = new FileInfo(s);
            yield return (T)(object)fi;
         }

         yield break;
      }

      // Get<Assembly>() returns list of loaded assemblies
      if (typeof(T) == typeof(Assembly))
      {
         foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
         {
            var asm = (T)(object)assembly;
            yield return asm;
         }

         yield break;
      }

      throw new NotSupportedException(string.Format("Type {0} is not supported", typeof(T).Name));
   }
}

Query serialization is handled by InterLINQ (Zyan integration was pretty straightforward, see Zyan.InterLinq project). InterLINQ is able to plug in several back-ends (Entity Framework, NHibernate, LinqToSql) to execute LINQ queries on the server side. Every back-end provider is implemented as a separate class library.

InterLINQ supports returning anonymous classes over Remoting or WCF, as in FileInfo example. It could be quite handy if you need to query for some non-serializable type (like AppDomain, for example).

Coordinator
Mar 12, 2011 at 4:36 PM

I´m very impressed.

Mar 12, 2011 at 8:07 PM
Rainbird wrote:

I´m very impressed.

I like the things the new Developer does :)

Keep the good work going. Congrats.

Coordinator
Mar 12, 2011 at 8:28 PM
Edited Mar 12, 2011 at 8:30 PM

Rainbird, it's still just a sketch (although it actually works as expected). I need your help to design some decent API over it.

For now, there are two problems:

  • It's impossible to have more than one query handler per component host (they all share IQueryRemoteHandler interface).
  • Queryable proxy is always IQueryHandler with two methods: Get(Type t) and Get<T>(). There's no easy way to add more features of my remote component to this proxy.

The first problem could be solved if Zyan could host several components with the same interface, something like this:

// server
host.RegisterComponent<IQueryRemoteHandler>(ServiceNames.PocoServerName, () => new ZyanServerQueryHandler(/* ObjectSource setup */));
host.RegisterComponent<IQueryRemoteHandler>(ServiceNames.DataContextName, () => new ZyanServerQueryHandler(/* Linq2Sql DataContext setup... */)); 

// client
var pocoProxy = connection.CreateQueryableProxy(ServiceNames.PocoServerName);
var dataContextProxy = connection.CreateQueryableProxy(ServiceNames.DataContextName);

String names are attached to different components sharing same interface. I don't really like string names, but I just don't see how it could be done in compiler-friendly manner.

The second problem arises from .NET Remoting restrictions (proxies with generic methods are not allowed). So we have two different interfaces here: one for the server (IQueryRemoteHandler) and one for the client (IQueryHandler with generic method Get). If I need to extend my queryable proxy, I would probably have to subclass IQueryHandler, IQueryRemoteHandler and ZyanServerQueryHandler, which looks like way too much for such a small task.

Any ideas? :)

Coordinator
Mar 12, 2011 at 8:30 PM
blackdog wrote:

Keep the good work going. Congrats.

Thanks a lot :)

Coordinator
Mar 13, 2011 at 2:45 PM
yallie wrote:

String names are attached to different components sharing same interface. I don't really like string names, but I just don't see how it could be done in compiler-friendly manner.

The second problem arises from .NET Remoting restrictions (proxies with generic methods are not allowed). So we have two different interfaces here: one for the server (IQueryRemoteHandler) and one for the client (IQueryHandler with generic method Get). If I need to extend my queryable proxy, I would probably have to subclass IQueryHandler, IQueryRemoteHandler and ZyanServerQueryHandler, which looks like way too much for such a small task.

Any ideas? :)

You´re right, yallie. Working with unique string names for component registration is the only solution.
I´ve already integrated that feature (http://zyan.codeplex.com/SourceControl/changeset/changes/9439). I think this can be useful in other scenarios too.

When no unique name is provided, Zyan takes the interface FullName instead.

Coordinator
Mar 13, 2011 at 9:57 PM
Edited Mar 13, 2011 at 9:57 PM

Great!

Rainbird wrote:

I think this can be useful in other scenarios too.

Yes, definitely. This mode is supported by almost every service container.