If you write code in TypeScript and you need declaration files for some popular JavaScript libraries check my repository. I wrote almost complete declaration files for linqjs, jasmine and angularjs libraries so far.
Friday, October 26, 2012
Saturday, October 6, 2012
TypeScript Interactive in Visual Studio (TypeScript REPL)
Last time I have been writing about JavaScript Interactive. After announcement of a new language called TypeScript last Monday (2012.10. 01) I have upgraded my solution to work also with TypeScript language. Everything works exactly the same as previously but instead of calling Generate method at the end of the file we need to call TypeScript’s counterpart GenerateFromTS. This method takes 3 parameters: two optional parameters already described (useClipboard , history – here TS file) and one new optional parameter jsFile. TypeScript language is compiled into JavaScript so parameter jsFile specifies the path where generated JavaScript code is stored. Let’s look at the sample usage of TypeScript Interactive:
New JS/TS Interactive implementation looks like this:
<#@ template hostspecific="true"#>
<#@ output extension=".txt"#>
<#@ assembly name="System.Windows.Forms" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Windows.Forms" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Threading" #>
<#+
public void Generate(bool useClipboard = false, string historyFile = null, Func<string, string> convertFileContent = null)
{
var fileContent = useClipboard ? GetClipboardText() : File.ReadAllText(Host.TemplateFile);
convertFileContent = convertFileContent ?? (s => s);
GenerationEnvironment.Clear();
var tempFile = Path.GetTempFileName();
if(string.IsNullOrEmpty(historyFile))
{
File.WriteAllText(tempFile, convertFileContent(fileContent));
RunProcess("node", tempFile, WriteLine, WriteLine);
File.Delete(tempFile);
}
else
{
var historyFilePath = Host.ResolvePath(historyFile);
if(!File.Exists(historyFilePath))
{
WriteLine("Specified history file path '{0}' does not exist.", historyFilePath);
}
else
{
var historyLines = File.ReadAllLines(historyFilePath);
int? printedOutputLinesCount = ExtractOutputLinesCount(historyLines);
long i = 0, max = printedOutputLinesCount ?? 0;
var tempFileLines =
(printedOutputLinesCount == null ? new [] {"//0"} : new string[0])
.Concat(historyLines)
.Concat(fileContent.Split(new string[] {Environment.NewLine},StringSplitOptions.None))
.Concat(new [] {new string('/',100)})
.ToArray();
var content = convertFileContent(string.Join(Environment.NewLine, tempFileLines));
if(content == null)
return;
File.WriteAllText(tempFile, content);
var result = RunProcess("node", tempFile, s => { if(++i >= max) WriteLine(s); }, WriteLine );
File.Delete(tempFile);
if(result == 0) // ok
{
tempFileLines[0] = @"//" + i;
File.WriteAllLines(historyFilePath, tempFileLines);
}
}
}
}
public void GenerateFromTS(string jsFile = null, bool useClipboard = false, string historyFile = null)
{
Generate(useClipboard, historyFile,
tsContent =>
{
var tsTempFile = Path.ChangeExtension(Path.GetTempFileName(), "ts");
var jsTempFile = Path.GetTempFileName();
File.WriteAllText(tsTempFile, tsContent);
var result = RunProcess(@"tsc", string.Format("--out \"{0}\" \"{1}\" ",jsTempFile, tsTempFile), WriteLine, WriteLine);
File.Delete(tsTempFile);
if(result != 0) // !ok
return null;
var jsContent = File.ReadAllText(jsTempFile);
if(!string.IsNullOrEmpty(jsFile))
{
var jsFilePath = Host.ResolvePath(jsFile);
if(!File.Exists(jsFilePath))
WriteLine("Specified JavaScript file path '{0}' does not exist.", jsFilePath);
else
File.WriteAllText(jsFilePath, jsContent);
}
File.Delete(jsTempFile);
return jsContent;
} );
}
private static int? ExtractOutputLinesCount(string[] allLines)
{
int count;
string firstLine = null;
return (allLines != null) && ((firstLine = allLines.FirstOrDefault()) != null) && int.TryParse(firstLine.Replace(@"//",""), out count) ?
(int?)count : null;
}
private static int RunProcess(string processName, string processArguments, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null)
{
onOutputDataReceived = onOutputDataReceived ?? (s => {});
onErrorDataReceived = onErrorDataReceived ?? (s => {});
var processStartInfo = new ProcessStartInfo(processName, processArguments);
processStartInfo.RedirectStandardInput = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
var process = Process.Start(processStartInfo);
process.OutputDataReceived += (sender, args) => onOutputDataReceived(args.Data);
process.ErrorDataReceived += (sender, args) => { if(args.Data!=null) onErrorDataReceived(args.Data); };
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
return process.ExitCode;
}
private string GetClipboardText()
{
try
{
return Clipboard.GetDataObject().GetData(DataFormats.Text) as string;
}
catch
{
return "";
}
}
#>
Friday, September 28, 2012
JavaScript Interactive in Visual Studio (JavaScript REPL)
The time has come. The time to learn JavaScript :) As usually when I‘m learning some new technology, I spent most of my time inside IDE like Visual Studio. I’ve opened some good resources on the tab (this time it’s a book: JavaScript: The Definitive Guide), a raw text file with my notes on the other . You may ask: why inside IDE ? Because I always try to use new technologies I’m reading about in practice. The problem with JavaScript is that in most cases it runs inside web browser. But I don’t want to make any web pages to test some feature of JavaScript language. I just want to write a few lines of JavaScript code and execute them. So I need something called REPL for JavaScript. REPL works very similar to Windows command line but it usually provides one great feature, the session state. In case of JavaScript that means that every time we execute some code we have access to all state - like variables, functions, etc. defined by previous executions. Each modern web browser has a tool called console where we can execute any piece of JavaScript code and its result is retuned immediately. Some tools like Firebug go even further providing a nice code editor with syntax highlighting and intellisense support where we can select part of script and “send it to REPL”. Image below shows how we use such a tool.
If we work with F# we can do the same thing directly from Visual Studio via F# Interactive windows. If we work with C# we have to install Roslyn and we will get C# Interactive window. Unfortunately we don’t have anything similar for JavaScript (at least so far ;) ).
In this post I will show you how we can accomplish very similar behavior to that presented above with JavaScript language. Here is the full list of steps that need to be done:
- Install nodejs (don’t be afraid, It’s free and very light - just a “single” exe file)
- Create a new project in Visual Studio, for example “ASP.NET Empty Web Application” (my solution doesn’t work with Web Site projects)
- Add a new template file called JsRepl.tt and copy its content from code below (this template doesn’t generate any output text so we optionally clear its default Custom Tool property using property grid)
- Add a new JS file next to JsRepl.tt and set its Custom Tool property to TextTemplatingFileGenerator. A new file will be automatically added to the project under JS file. Open those two files one above the other, JS file is our input windows and generated file is our output window. Each time we save JS file, its content will be executed immediately.
- Now we can write any JavaScript code in JS file except the first and the last line. The first line should look like this //<#@ include file="JsRepl.tt"#> . It’s a T4 directive that includes (pastes) template file we just added to the project. The last line specifies how we want to execute JS code:
- //<# Generate();#> – executes the whole JS file
- //<# Generate(useClipboard:true);#> – executes code from the clipboard so selecting the code and pressing Ctrl+C, Ctrl+S simulates sending code to REPL :)
- //<# Generate(useClipboard:true, historyFile:"history.js");#> – works the same as code above but additionally stores all previous successfully executed code inside specified helper JS file (here history.js). Every time we try to execute code from the clipboard, the one big file is created underneath and executed. This file combines the content of history file with the code from the clipboard. This is how we can easily simulate REPL session state mechanism :)
Let’s look at a sample usage:
The output window (ReplSample.txt file) displays the result of calling selected text from input windows (ReplSample.js file). Before that I did two additional executions, first one with text declaring count variable, the second one with definition of the function printNumber. The history file looks like this:
Now it’s time to reveal the secret how the JS REPL in Visual Studio was implemented. JsRepl.tt file is the whole implementation:
<#@ template hostspecific="true"#>
<#@ output extension=".txt"#>
<#@ assembly name="System.Windows.Forms" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Windows.Forms" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Threading" #>
<#+
public void Generate(bool useClipboard = false, string historyFile = null)
{
GenerationEnvironment.Clear();
if(!useClipboard)
{
RunNode(Host.TemplateFile, WriteLine, WriteLine);
}
else
{
var text = GetClipboardText();
var tempFile = Path.GetTempFileName();
if(string.IsNullOrEmpty(historyFile))
{
File.WriteAllText(tempFile,text);
RunNode(tempFile, WriteLine, WriteLine);
}
else
{
var historyFilePath = Host.ResolvePath(historyFile);
if(!File.Exists(historyFilePath))
{
WriteLine("Specified history file path '{0}' does not exist.", historyFilePath);
}
else
{
var historyLines = File.ReadAllLines(historyFilePath);
int? printedOutputLinesCount = ExtractOutputLinesCount(historyLines);
long i = 0, max = printedOutputLinesCount ?? 0;
var tempFileLines =
(printedOutputLinesCount == null ? new [] {"//0"} : new string[0])
.Concat(historyLines)
.Concat(text.Split(new string[] {Environment.NewLine},StringSplitOptions.None))
.Concat(new [] {new string('/',100)})
.ToArray();
File.WriteAllLines(tempFile, tempFileLines);
var result = RunNode(tempFile, s => { if(++i >= max) WriteLine(s); }, WriteLine );
if(result == 0) // ok
{
tempFileLines[0] = @"//" + i;
File.WriteAllLines(historyFilePath, tempFileLines);
}
}
}
if(File.Exists(tempFile))
File.Delete(tempFile);
}
}
private static int? ExtractOutputLinesCount(string[] allLines)
{
int count;
string firstLine = null;
return (allLines != null) && ((firstLine = allLines.FirstOrDefault()) != null) && int.TryParse(firstLine.Replace(@"//",""), out count) ?
(int?)count : null;
}
private static int RunNode(string jsFile, Action<string> onOutputDataReceived, Action<string> onErrorDataReceived)
{
var processStartInfo = new ProcessStartInfo(@"node", jsFile);
processStartInfo.RedirectStandardInput = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
var process = Process.Start(processStartInfo);
process.OutputDataReceived += (sender, args) => onOutputDataReceived(args.Data);
process.ErrorDataReceived += (sender, args) => { if(args.Data!=null) onErrorDataReceived(args.Data); };
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
return process.ExitCode;
}
private static string GetClipboardText()
{
try
{
return Clipboard.GetDataObject().GetData(DataFormats.Text) as string;
}
catch
{
return "";
}
}
#>
There are a couple of things worth explanation here. You may be wondering how I actually execute JavaScript code outside of the browser ? I use node.exe console application passing JavaScript file path as an argument and I listen what result is printed. T4 template mechanism use WriteLine method to write something to the input file. This method appends passed string argument to the internal StringBuilder object stored in property called GenerationEnvironment. By default each line of the template file (JS file in this case) which is not a directive or control flow statement (code inside regions <#, <#+ or <#= ) is passed to the WriteLine method. That’s why the main method called Generate is placed at the end of template file and clears everything that was written before its execution by calling StringBuilder Clear method. Because the whole history file is executed from the beginning to the end but we want to see results only of the latest part (that from the clipboard) I use special counter stored in the first line in history file. This counter represents a number of lines printed during history file execution and it’s used to suspend printing process till the appropriate moment when the code from the clipboard is executed.
And that’s all. Simple as it is I hope you find it useful like I did :)
Sunday, May 6, 2012
Running RIA Services on the top of Raven DB
In this post I will describe how to integrate RIA Services with document database called Raven DB. If you are not familiar with those frameworks watch this video and this one (my 2 presentations in polish). RIA Services integrates very easily with relational database by providing base implementations of domain services out of the box. Using the same pattern I have created the base implementation for services working with Raven DB. We will see later in this post what exactly the domain service is and how we can use it to build n-tier business application, but now let’s look at the Raven DB API. Let’s say we have the following data model:
public partial class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Biography { get; set; }
public string Photos { get; set; }
public double AverageRating { get; set; }
}
public partial class Movie
{
public string Id { get; set; }
public string Name { get; set; }
public string Synopsis { get; set; }
public double AverageRating { get; set; }
public int? ReleaseYear { get; set; }
public string ImageUrl { get; set; }
public List<string> Genres { get; set; }
public List<string> Languages { get; set; }
public List<PersonReference> Cast { get; set; }
public List<PersonReference> Directors { get; set; }
public List<Award> Awards { get; set; }
public int CommentsCount { get; set; }
public string CommentsId { get; set; }
}
public class PersonReference
{
public string PersonId { get; set; }
public string PersonName { get; set; }
}
public class Award
{
public string Type { get; set; }
public string Category { get; set; }
public int? Year { get; set; }
public bool Won { get; set; }
public string PersonId { get; set; }
}
With this we can do some base CRUD operation:
using (var store = new DocumentStore() { Url = "http://localhost:8080", }.Initialize())
{
using (var session = store.OpenSession())
{
var davidDuchowny = new Person() { Name = "David Duchovny" };
var demiMoore = new Person() { Name = "Demi Moore" };
session.Store(davidDuchowny); // Id property is set here
session.Store(demiMoore); // Id property is set here
var theJoneses = new Movie()
{
Name = "The Joneses",
Synopsis = "A seemingly perfect family moves into a suburban neighborhood, but when it comes to the " +
"truth as to why they're living there, they don't exactly come clean with their neighbors.",
ReleaseYear = 2009,
AverageRating = 6.5,
Genres = new List<string>() { "Comedy", "Drama" },
Cast = new List<PersonReference>()
{
new PersonReference() { PersonId = davidDuchowny.Id, PersonName = davidDuchowny.Name},
new PersonReference() { PersonId = demiMoore.Id, PersonName = demiMoore.Name},
}
};
session.Store(theJoneses); // Id property is set here
session.SaveChanges();
}
using (var session = store.OpenSession())
{
var movie = (from m in session.Query<Movie>()
where m.Name == "The Joneses"
select m).FirstOrDefault();
var people = session.Load<Person>(movie.Cast[0].PersonId, movie.Cast[1].PersonId);
session.Delete(movie);
session.Delete(people[0]);
session.Delete(people[1]);
session.SaveChanges();
}
}
Raven DB stores data as a JSON documents:
Once we know how the Raven DB works we can look at the RIA Services. RIA Services gives us tools (Visual Studio extensions) and libraries that allow us to build n-tier business solution with the Silverlight or JavaScript application as a client and .Net Web Service as a server. We start building RIA Services solution from the domain service which is just a class deriving directly from DomainService or any other derived class depending on the type of a data store.
If the relation database is a data store then we can use LinqToSqlDomainService, LinqToEntitesDomainService or DbDomainService (LINQ to Entities Code First approach). TableDomainService class allows us to store data inside Windows Azure Table. If we want to store data inside any other kind of data source we have to derive directly from the DomainService class. My base service called RavenDomainService derives from DomainService and it encapsulates the logic of initializing and saving the data inside Raven DB. Let’s look at the sample usage of this class:
[EnableClientAccess]
public class NetflixDomainService : RavenDomainService
{
protected override IDocumentSession CreateSession()
{
var session = base.CreateSession();
session.Advanced.UseOptimisticConcurrency = true;
return session;
}
public IQueryable<Person> GetPeople()
{
return Session.Query<Person>();
}
public void InsertPerson(Person entity)
{
Session.Store(entity);
}
public void UpdatePerson(Person entity)
{
var original = ChangeSet.GetOriginal(entity);
Session.Store(entity, original.Etag.Value); // optimistic concurrency
}
public void DeletePerson(Person entity)
{
var original = ChangeSet.GetOriginal(entity);
Session.Store(entity, original != null ? original.Etag.Value : entity.Etag.Value); // optimistic concurrency
Session.Delete(entity);
}
public IQueryable<Movie> GetMovies()
{
return Session.Query<Movie>();
}
public void InsertMovie(Movie entity)
{
Session.Store(entity);
}
public void UpdateMovie(Movie entity)
{
var original = ChangeSet.GetOriginal(entity);
Session.Store(entity, original.Etag.Value); // optimistic concurrency
}
public void DeleteMovie(Movie entity)
{
var original = ChangeSet.GetOriginal(entity);
Session.Store(entity, original != null ? original.Etag.Value : entity.Etag.Value); // optimistic concurrency
Session.Delete(entity);
}
}
After adding “WCF RIA Services link” from the Silverlight project to projects containing domain service implemented above and rebuilding the whole solution, Visual Studio will generate appropriate code on the client side and we are ready to write code very similar to that presented earlier:
async public void BlogPost()
{
var context = new NetflixDomainContext();
var davidDuchowny = new Person() { Name = "David Duchovny" };
var demiMoore = new Person() { Name = "Demi Moore" };
context.Persons.Add(davidDuchowny);
context.Persons.Add(demiMoore);
await context.SubmitChanges().AsTask(); // Id property is set here
var theJoneses = new Movie()
{
Name = "The Joneses",
Synopsis = "A seemingly perfect family moves into a suburban neighborhood, but when it comes to the " +
"truth as to why they're living there, they don't exactly come clean with their neighbors.",
ReleaseYear = 2009,
AverageRating = 6.5,
Genres = new List<string>() { "Comedy", "Drama" },
Cast = new List<PersonReference>()
{
new PersonReference() { PersonId = davidDuchowny.Id, PersonName = davidDuchowny.Name},
new PersonReference() { PersonId = demiMoore.Id, PersonName = demiMoore.Name},
}
};
context.Movies.Add(theJoneses);
await context.SubmitChanges().AsTask(); // Id property is set here
var context2 = new NetflixDomainContext();
var movie = (await context2.Load
(
from m in context2.GetMoviesQuery()
where m.Name == "The Joneses"
select m
)).FirstOrDefault();
var people = (await context2.Load
(
from p in context2.GetPeopleQuery()
where p.Id == movie.Cast[0].PersonId || p.Id == movie.Cast[1].PersonId
select p
)).ToArray();
context2.Movies.Remove(movie);
context2.Persons.Remove(people[0]);
context2.Persons.Remove(people[1]);
await context2.SubmitChanges().AsTask();
}
RIA Services gives us a very nice implementation of Unit Of Work and Identity Map patterns so the code using DAL frameworks like LINQ to SQL, LINQ to Entities, NHibernate or Raven DB is very similar to that written on the client side. There is one thing worth mentioning at this point, RIA Services needs to know which property identifies the entity object. I didn’t show you how to do it yet but it will be presented in a moment, I am writing about it here because without this little hint, the code above wouldn’t even compile.
The last thing I would like to explain is how the optimistic concurrency pattern has been implemented in case of Raven DB. Raven DB has metadata mechanism which means that each JSON document besides the actual data has additional information like document type, last modification time, etag and so on. Etag is a single value of type Guid and its goal is very similar to Timestamp or Row Version column data type in SQL Server. Raven DB server changes the value of Etag internally every time the document is changing. With this Raven DB client API allows us to enable optimistic concurrency checking on the session object level via UseOptimisticConcurrency property. There is one problem when it comes to integration with RIA Services optimistic concurrency mechanism. Etag is part of the metadata instead of document itself and it is handled by the session object internally. RIA Services doesn’t have any notion of metadata information, all data is stored inside the entity objects so we need to add additional property storing etag value which will be used on the RIA Services level but on the Raven DB level this value will be mapped into etag metadata field.
public interface IEtag
{
Guid? Etag { get; set; }
}
[MetadataTypeAttribute(typeof(PersonMetadata))]
public partial class Person : IEtag
{
[JsonIgnore] // do not store value of this property
public Guid? Etag { get; set; }
public class PersonMetadata
{
[Key]
public object Id { get; set; }
[RoundtripOriginal] // send back value of this property to client
public object Etag { get; set; }
}
}
[MetadataTypeAttribute(typeof(MovieMetadata))]
public partial class Movie : IEtag
{
[JsonIgnore] // do not store value of this property
public Guid? Etag { get; set; }
public class MovieMetadata
{
[Key]
public object Id { get; set; }
[Display(Name="nazwa")]
[Required]
[StringLength(200)]
public object Name { get; set; }
[Range(1900, 2012)]
public object ReleaseYear { get; set; }
[RoundtripOriginal] // send back value of this property to client
public object Etag { get; set; }
}
}
Attributes like Key, Display, Required, Range, RoundtripOriginal and MetadataType are a part of the standard .net mechanism called data annotations. RIA Services similar to other frameworks like ASP.MVC or Dynamic Data uses them to define validation rules or UI controls in layout in declarative way. Key attributes tell RIA Services which property identifies the entity object, RoundtripOriginal attributes tell that the value of the property is changed on the server side so the new value should be sent back to the client after calling server method. JsonIgnore is specific to Raven DB and it means that this property should be ignore during serialization and deserialization process. IEtag interface was introduced as a marker interface so all entities implementing this interface are treated specially by listener code. Listeners are a part of Raven DB client API and we can think of them as of a client side triggers executed in various situations.For instance during JSON document conversion process (IDocumentConversionListener) or before and after document is stored inside Raven DB (IDocumentStoreListener).
public class Global : System.Web.HttpApplication
{
private static IDocumentStore _documentStore;
protected void Application_Start(object sender, EventArgs e)
{
var etagSetterListener = new EtagSetterListener();
_documentStore = new DocumentStore() { Url = "http://localhost:8080", }
.RegisterListener(etagSetterListener as IDocumentConversionListener)
.RegisterListener(etagSetterListener as IDocumentStoreListener)
.Initialize();
DomainService.Factory = new RavenDomainServiceFactory(_documentStore, DomainService.Factory);
}
}
public class EtagSetterListener : IDocumentConversionListener, IDocumentStoreListener
{
#region IDocumentConversionListener
public void EntityToDocument(object entity, RavenJObject document, RavenJObject metadata)
{
}
public void DocumentToEntity(object entity, RavenJObject document, RavenJObject metadata)
{
var etag = entity as IEtag;
if (etag != null)
{
etag.Etag = Guid.Parse(metadata["@etag"].ToString());
}
}
#endregion
#region IDocumentStoreListener
public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
{
return false;
}
public void AfterStore(string key, object entityInstance, RavenJObject metadata)
{
var etag = entityInstance as IEtag;
if (etag != null)
{
etag.Etag = Guid.Parse(metadata["@etag"].ToString());
}
}
#endregion
}
public class RavenDomainServiceFactory : IDomainServiceFactory
{
private readonly IDocumentStore _documentStore;
private readonly IDomainServiceFactory _domainServiceFactory;
public RavenDomainServiceFactory(IDocumentStore documentStore, IDomainServiceFactory domainServiceFactory)
{
_documentStore = documentStore;
_domainServiceFactory = domainServiceFactory;
}
public DomainService CreateDomainService(Type domainServiceType, DomainServiceContext context)
{
var service = _domainServiceFactory.CreateDomainService(domainServiceType, context);
if (service is RavenDomainService)
{
((RavenDomainService)service).DoumentStore = _documentStore;
}
return service;
}
public void ReleaseDomainService(DomainService domainService)
{
_domainServiceFactory.ReleaseDomainService(domainService);
}
}
IDomainServiceFactory interface allows us extend the process of creation and destruction of domain services instance on the server side. Finally let’s see how the RavenDomainService class has been implemented:
public abstract class RavenDomainService : DomainService
{
public IDocumentStore DoumentStore { get; set; }
private IDocumentSession _session;
protected IDocumentSession Session
{
get
{
if (_session == null && DoumentStore != null)
{
_session = CreateSession();
}
return _session;
}
}
private IDocumentSession _refreshSession;
private IDocumentSession RefreshSession
{
get
{
if (_refreshSession == null && DoumentStore != null)
{
_refreshSession = CreateSession();
}
return _refreshSession;
}
}
protected virtual IDocumentSession CreateSession()
{
var session = DoumentStore.OpenSession();
return session;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_session != null)
{
_session.Dispose();
_session = null;
}
if (_refreshSession!= null)
{
_refreshSession.Dispose();
_refreshSession = null;
}
}
base.Dispose(disposing);
}
protected override int Count<T>(IQueryable<T> query)
{
return query.Count();
}
protected override bool PersistChangeSet()
{
try
{
this.Session.SaveChanges();
}
catch (ConcurrencyException exception)
{
var items = base.ChangeSet.ChangeSetEntries
.Where(changeSet =>
{
var etag = changeSet.Entity as IEtag;
if (etag == null)
return false;
return etag.Etag == exception.ExpectedETag;
})
.ToArray();
foreach (var item in items)
{
var o = ChangeSet.GetChangeOperation(item.Entity);
if(o == ChangeOperation.Delete)
{
item.IsDeleteConflict = true;
}
else
{
item.ConflictMembers = new[] { "unknown" };
var id = Session.Advanced.GetDocumentId(item.Entity);
item.StoreEntity = RefreshSession.Load<object>(id);
}
}
this.OnError(new DomainServiceErrorInfo(exception));
if (!base.ChangeSet.HasError)
{
throw;
}
return false;
}
return true;
}
}
I know, I know, I hear you saying: "What did you do all this for ??? Raven DB gives you a wonderful client API for Silverlight". I totally agree with you. I am not trying to say that you should use RIA Services instead of Raven DB directly via its Silverlight API. Those two frameworks are slightly different things. They have a different functionalities, they are designed to resolve a bit different kinds of problems. Raven DB is a database system so it is focused mainly on data storage. RIA Services solves many common problems for a business n-tier application like server and client side validation, easy integration with DataGrid or DataForm UI controls, sharing common code between client and server, marshaling exceptions and so on. For me, those two frameworks can work great together and such cooperation seems to be very reasonable. I was searching google for any samples showing how to integrate them but I couldn’t find it. So I did my own !:) I hope you find it useful.
Sunday, February 19, 2012
Raven DB presentation
This presentation is an introduction to document database Raven DB. I’m very sorry for the annoying noise :( (my phone was lying next to the microphone through the entire presentation). Code samples.
Friday, February 10, 2012
RIA Services (and Raven DB) presentation
This presentation is an introduction to RIA Services framework. Most tutorials show how to integrate RIA Services with relational database using DAL frameworks such as LINQ to SQL, LINQ to Entities or NHibernate. This presentation shows how to integrate RIA Services with document database Raven DB. Source code and slides can be found here.