Showing posts with label LINQ. Show all posts
Showing posts with label LINQ. Show all posts

Wednesday, 16 October 2013

How to serialize an Expression

Download (or use NuGet):  https://github.com/esskar/Serialize.Linq

using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Serialize.Linq.Serializers;
using Serialize.Linq.Tests.Internals;
using System;
using System.Linq.Expressions;

namespace Serialize.Linq.Tests
{
public class FilmDto
{
public string Name { get; set; }
public DateTime ReleaseDate { get; set; }
}

[TestClass]
public class HowToSerializeAnExpressionTests
{
public TestContext TestContext { get; set; }

private static IQueryable<FilmDto> GetFilms()
{
return new List<FilmDto>
{
new FilmDto { Name = "Gravity", ReleaseDate = new DateTime(2013, 10, 15) },
new FilmDto { Name = "Blade Runner", ReleaseDate = new DateTime(1975, 2, 28) },
new FilmDto { Name = "Superman", ReleaseDate = new DateTime(1985, 6, 6) },
}
.AsQueryable();
}

[TestMethod]
public void BasicExpressionSerialization()
{
var predicate = (Expression<Func<FilmDto, bool>>)(film => film.Name.ToLower().Contains("n"));
var serializer = new ExpressionSerializer(new BinarySerializer());
var bytes = serializer.SerializeBinary(predicate);
var predicateDeserialized = serializer.DeserializeBinary(bytes);
this.TestContext.WriteLine("{0} serializes to bytes with length {1}", predicate, bytes.Length);

ExpressionAssert.AreEqual(predicate, predicateDeserialized);

var films = GetFilms();
var expectedCount = films.Where(predicate).Count();

// a simple example of executing an expression, leveraging IEnumerable
var actualCount = films.Where((Expression<Func<FilmDto, bool>>)predicateDeserialized).Count();
Assert.AreEqual(expectedCount, actualCount);

// constructing the same call using more Expressions
Expression whereMethodCall = Expression.Call(
typeof(Queryable),
"Where",
new[] { films.ElementType },
films.Expression,
Expression.Quote(predicateDeserialized));

// executing the expression, leveraging IQueryable
actualCount = films.Provider.CreateQuery<FilmDto>(whereMethodCall).Count();
Assert.AreEqual(expectedCount, actualCount);
}
}

// Bear in mind this issue with .NET 45: http://forums.asp.net/t/1864636.aspx
// reproducible if you use: (film => film.ReleaseDate < new DateTime(1990, 01, 01))
// (which is fixed with .NET 451!)
// For finer grain support:
// http://msdn.microsoft.com/en-us/library/vstudio/bb882637.aspx
}


image



If you want to swap out the BinarySerializer for Json, this would be the payload:

{
"__type":"L:#Serialize.Linq.Nodes",
"NT":18,
"T":{
"G":[
{
"N":"Serialize.Linq.Tests.FilmDto"
},
{
"N":"System.Boolean"
}
],
"N":"System.Func`2"
},
"B":{
"__type":"MC:#Serialize.Linq.Nodes",
"NT":6,
"T":{
"N":"System.Boolean"
},
"A":[
{
"__type":"C:#Serialize.Linq.Nodes",
"NT":9,
"T":{
"N":"System.String"
},
"V":"n"
}
],
"M":{
"D":{
"N":"System.String"
},
"M":8,
"S":"Boolean Contains(System.String)"
},
"O":{
"__type":"MC:#Serialize.Linq.Nodes",
"NT":6,
"T":{
"N":"System.String"
},
"A":[

],
"M":{
"D":{
"N":"System.String"
},
"M":8,
"S":"System.String ToLower()"
},
"O":{
"__type":"M:#Serialize.Linq.Nodes",
"NT":23,
"T":{
"N":"System.String"
},
"E":{
"__type":"P:#Serialize.Linq.Nodes",
"NT":38,
"T":{
"N":"Serialize.Linq.Tests.FilmDto"
},
"N":"film"
},
"M":{
"D":{
"N":"Serialize.Linq.Tests.FilmDto"
},
"M":16,
"S":"System.String Name"
}
}
}
},
"P":[
{
"__type":"P:#Serialize.Linq.Nodes",
"NT":38,
"T":{
"N":"Serialize.Linq.Tests.FilmDto"
},
"N":"film"
}
]
}


If you want to swap it out for the XmlSerializer, this would be the payload:

<?xml version="1.0" encoding="UTF-8"?>
<E xmlns="http://schemas.datacontract.org/2004/07/Serialize.Linq.Nodes" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="L">
<NT>Lambda</NT>
<T>
<G>
<T>
<N>Serialize.Linq.Tests.FilmDto</N>
</T>
<T>
<N>System.Boolean</N>
</T>
</G>
<N>System.Func`2</N>
</T>
<B i:type="MC">
<NT>Call</NT>
<T>
<N>System.Boolean</N>
</T>
<A>
<E i:type="C">
<NT>Constant</NT>
<T>
<N>System.String</N>
</T>
<V xmlns:a="http://www.w3.org/2001/XMLSchema" i:type="a:string">n</V>
</E>
</A>
<M>
<D>
<N>System.String</N>
</D>
<M>Method</M>
<S>Boolean Contains(System.String)</S>
</M>
<O i:type="MC">
<NT>Call</NT>
<T>
<N>System.String</N>
</T>
<A />
<M>
<D>
<N>System.String</N>
</D>
<M>Method</M>
<S>System.String ToLower()</S>
</M>
<O i:type="M">
<NT>MemberAccess</NT>
<T>
<N>System.String</N>
</T>
<E i:type="P">
<NT>Parameter</NT>
<T>
<N>Serialize.Linq.Tests.FilmDto</N>
</T>
<N>film</N>
</E>
<M>
<D>
<N>Serialize.Linq.Tests.FilmDto</N>
</D>
<M>Property</M>
<S>System.String Name</S>
</M>
</O>
</O>
</B>
<P>
<E i:type="P">
<NT>Parameter</NT>
<T>
<N>Serialize.Linq.Tests.FilmDto</N>
</T>
<N>film</N>
</E>
</P>
</E>

Project().To<T>: Lightweight mapper

Found this very cool code today:

http://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code
// This is the kind of projection code that we are replacing

//var students = from s in context.Students
// select new StudentSummary
// {
// FirstName = s.FirstName,
// LastName = s.LastName,
// TutorName = s.Tutor.Name,
// CoursesCount = s.Courses.Count
// };


// The line below performs exactly the same query as the code above.

var students = context.Students.Project().To<StudentSummary>();

Which is all down to the following code:

public static class QueryableExtensions
{
public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
{
return new ProjectionExpression<TSource>(source);
}
}

public class ProjectionExpression<TSource>
{
private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>();

private readonly IQueryable<TSource> _source;

public ProjectionExpression(IQueryable<TSource> source)
{
_source = source;
}

public IQueryable<TDest> To<TDest>()
{
var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>();

return _source.Select(queryExpression);
}

private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
{
var key = GetCacheKey<TDest>();

return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
}

private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
{
var sourceProperties = typeof(TSource).GetProperties();
var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
var parameterExpression = Expression.Parameter(typeof(TSource), "src");

var bindings = destinationProperties
.Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
.Where(binding => binding != null);

var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression);

var key = GetCacheKey<TDest>();

ExpressionCache.Add(key, expression);

return expression;
}

private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name);

if (sourceProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
}

var propertyNames = SplitCamelCase(destinationProperty.Name);

if (propertyNames.Length == 2)
{
sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[0]);

if (sourceProperty != null)
{
var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[1]);

if (sourceChildProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
}
}
}

return null;
}

private static string GetCacheKey<TDest>()
{
return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
}

private static string[] SplitCamelCase(string input)
{
return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
}
}

Wednesday, 31 July 2013

List<string> to string

var strings = new List<string> { "blah", "de", "hoo", "ha" };

var result = strings.Aggregate((i, j) => i + " " + j);

Console.Write(result);
image001

And with a complex type, where a response has a collection of messages, each with a text property:

var messages = this.Response
.Messages
.Select(m => m.Text)
.Aggregate((i, j) => i + " " + j);

Friday, 3 August 2012

LINQ Outer Joins

image
using System;
using System.Linq;

namespace LinqPlay
{
class Program
{
static void Main(string[] args)
{
// REQUIREMENT: Using linq show all beverages for all days
// including days where beverages have zero count

// Enum beverages: Coke, Fanta, Lilt, Sprite
// Date range: yesterday, today and tomorrow
// Stats data: 3 cokes, 1 sprite yesterday
// 1 sprite, 4 Fanta today

// SETUP THE COLLECTIONS OF DATA

string[] beverages = Enum.GetNames(typeof(Beverages));

DateTime yesterday = DateTime.Today.AddDays(-1);
DateTime today = DateTime.Today;
DateTime tomorrow = DateTime.Today.AddDays(1);

DateTime[] dates = new[] { yesterday, today, tomorrow };

var stats = new []
{
new ChartData() {Text = "Coke", Timestamp = yesterday, Value = 3},
new ChartData() {Text = "Sprite", Timestamp = yesterday, Value = 1},
new ChartData() {Text = "Sprite", Timestamp = today, Value = 1},
new ChartData() {Text = "Fanta", Timestamp = today, Value = 4}
};

// JOIN THE DATA

var qry = (from x in (from b in beverages
from d in dates
select new ChartData() {Text = b, Timestamp = d.Date})
join s in stats on new { x.Text, x.Timestamp} equals new { s.Text, s.Timestamp }
into temp1 from temp2 in temp1.DefaultIfEmpty()
select new ChartData()
{
Text = x.Text,
Timestamp = x.Timestamp,
Value = (temp2 == null ? 0 : temp2.Value)
})
.OrderBy(x => x.Timestamp).ThenBy(x => x.Text);

foreach (var chartData in qry)
{
Console.WriteLine(chartData.ToString());
}
}

enum Beverages
{
Coke,
Lilt,
Fanta,
Sprite
}

class ChartData
{
public string Text { get; set; }
public DateTime Timestamp { get; set; }
public int Value { get; set; }

public override string ToString()
{
return string.Format("{0}\t{1}\t{2}", Timestamp.ToString("dd/MM/yyyy"), Text, Value);
}
}
}
}

Monday, 16 April 2012

RavenDb Quick Start

image

Introduction

Continuing the NoSQL trend in recent years RavenDb is a .NET LINQ enabled document storage engine with super fast text search capabilities thanks to Lucene.NET.  It also enables sharding out of the box which is a great feature for scalability.

It exposes a HTTP REST interface, which gives it far greater flexibility when scaling - no nasty firewalls getting in the way.

The most compelling reason from my point of view for using RavenDb over SQL Server is the ability to store a complete document (read .NET object model) without having to hit multiple tables.  SQL Server file IO can be crippling when dealing with large sets of data spread across huge tables. 

For example, if you had a customer with addresses, orders and payments in SQL you would have to access at least 4 tables (probably more) whereas in RavenDb the complete customer entity is stored as one document with one ID value.  One fast lookup and the data, which is stored internally within RavenDb as JSON, is rehydrated into the full .NET object.

Download

From the http://ravendb.net/download website you can download the latest build and/or source.

Or you can use Nuget directly from Visual Studio:

Client and Server package (recommended) http://nuget.org/packages/RavenDB
Embedded package http://nuget.org/packages/RavenDB-Embedded

 

Deployment

You have four main options for running RavenDb:

  • As a service under its own web server
  1. Download the build file from the website and extract the zip
  2. Go to the Server directory
  3. Execute the following command on the command line: Raven.Server.exe /install
    Note: Raven may ask you for administrator privileges while installing the service.  Configuration including port number is taken from Raven.Server.exe.config (defaults to port 8080).
  • Under IIS

http://ravendb.net/docs/server/deployment/as-iis-application

  • Embedded within your own application

http://ravendb.net/docs/server/deployment/embedded

  • Within a Command Prompt window, perhaps for development work with manual start up
    1. Download the build file from the website and extract the zip
    2. Run the Start.cmd batch file in the root directory.

Warning

IMPORTANT: To save any potential headaches, make sure you use the same build numbers for client and server!

RavenDB Management Studio

From the browser you can use the Management Studio, a great little tool written in Silverlight.  It enables you to write indexes (the map/reduce equivalent of queries in SQL), view/edit the JSON documents directly, run backups, import/export data or view the logs.

image

By default the Management Studio comes with a button you can press to load a sample Albums database.

The following examples are based on samples taken from the RavenDb source code.

Writing and Reading data from .NET

using System;
using Raven.Client.Document;

namespace Raven.Sample
{
class Program
{
static void Main(string[] args)
{
var documentStore1 = new DocumentStore
{ Url = "http://localhost:8080" }.Initialize();

using (var session1 = documentStore1.OpenSession())
{
session1.Store(new User { Id = "users/ayende", Name = "Ayende" });
session1.SaveChanges();
}

using (var session1 = documentStore1.OpenSession())
{
Console.WriteLine(session1.Load<User>("users/ayende").Name);
}

Console.WriteLine("Wrote and read one document to 8080");
}
}

public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
}



Sharding Example from RavenDb Source

//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="Hibernating Rhinos LTD">
// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Raven.Client.Document;
using Raven.Client.Shard;
using Raven.Client;

namespace Raven.Sample.ShardClient
{
class Program
{
static void Main()
{
var shards = new Dictionary<string, IDocumentStore>
{
{"Asia", new DocumentStore {Url = "http://localhost:8080"}},
{"Middle-East", new DocumentStore {Url = "http://localhost:8081"}},
{"America", new DocumentStore {Url = "http://localhost:8082"}},
};

var shardStrategy = new ShardStrategy(shards)
.ShardingOn<Company>(x => x.Region)
.ShardingOn<Invoice>(x => x.CompanyId);

using (var documentStore = new ShardedDocumentStore(shardStrategy).Initialize())
{
new InvoicesAmountByDate().Execute(documentStore);

using (var session = documentStore.OpenSession())
{
var asian = new Company { Name = "Company 1", Region = "Asia" };
session.Store(asian);
var middleEastern = new Company { Name = "Company 2", Region = "Middle-East" };
session.Store(middleEastern);
var american = new Company { Name = "Company 3", Region = "America" };
session.Store(american);

session.Store(new Invoice { CompanyId = american.Id, Amount = 3, IssuedAt = DateTime.Today.AddDays(-1) });
session.Store(new Invoice { CompanyId = asian.Id, Amount = 5, IssuedAt = DateTime.Today.AddDays(-1) });
session.Store(new Invoice { CompanyId = middleEastern.Id, Amount = 12, IssuedAt = DateTime.Today });
session.SaveChanges();
}


using (var session = documentStore.OpenSession())
{
var reduceResults = session.Query<InvoicesAmountByDate.ReduceResult, InvoicesAmountByDate>()
.ToList();

foreach (var reduceResult in reduceResults)
{
string dateStr = reduceResult.IssuedAt.ToString("MMM dd, yyyy", CultureInfo.InvariantCulture);
Console.WriteLine("{0}: {1}", dateStr, reduceResult.Amount);
}
Console.WriteLine();
}
}
}

}
}



Caching

using (session.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromMinutes(5)))
{
session.Load<User>("users/1");
}

Authentication


RavenDB supports Windows and OAuth security models.  You can also add support for custom users within RavenDb by implementing IAuthenticateClient.


By default, Windows anonymous access is enabled for GET only access. This can be amended in the Raven.Server.exe.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Raven/Port" value="*"/>
<add key="Raven/DataDir" value="~\Data"/>
<add key="Raven/AnonymousAccess" value="Get"/>
</appSettings>
<runtime>
<loadFromRemoteSources enabled="true"/>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Analyzers"/>
</assemblyBinding>
</runtime>
</configuration>


The full set of AnonymousAccess values are:  Get, All and None.