Showing posts with label DataObjects.Net 4. Show all posts
Showing posts with label DataObjects.Net 4. Show all posts

Saturday, May 15, 2010

On blog changes

Hi there,

Recently members of DataObjects.Net team have finally agreed on principal alteration of news and announces publishing policy. By this I mean that in order not to spread your attention between several personal blogs we decided to concentrate on exactly one, dedicated to DataObjects.Net development. So I’d strongly recommend you to update your RSS subscriptions.

In case you are interested in using the ORM under Visual Studio 2010 & .NET 4.0, there is a big announce for you, don’t you miss it!

Thanks for your attention!

Saturday, February 20, 2010

DataObjects.Net 4.2 RC’s health status

It is considered as a good and beneficial idea for a development team and the whole product’s development process to become more open for its community. DataObjects.Net 4 team shares these views and tries to move step by step towards the goal of openness and transparency.

We moved the project repository to Google Code, which means that everybody has access to the source code, revision history and changeset tracking. We started to use publicly available issue tracker. Anybody can report an issue, request a feature and vote for it. We use it by ourselves to track development tasks and therefore the current status of the development process can be seen on project’s update page. Finally, support forum is open 24 hours a day, so everybody is always welcome.

A part of our English-speaking developers began their personal blogs in order to share their thoughts, ideas, new concepts, samples and examples with the community. The most outstanding blogs are:

  • the well-known Alex Yakunin’s blog. A must have source of information if you are interested in .Net in general and in DataObjects.Net in particular.
  • Alex Kofman’s blog. Not so frequently updated but anyway, it is worth reading. Alex prefers to discuss general ORM-related concepts, often in DataObjects.Net 4 context.
  • Alex Ilyin’s blog. Although he doesn’t work on DataObjects.Net 4 directly but he makes a contribution to the project on everyday basis participating in hot discussions and reporting tons of suggestions and remarks. It is he, who tests the very green alphas of DataObjects in his sophisticated (and sometimes even a bit weird) scenarios.
  • This blog. As you are already here, I suppose that you know what the blog is about ;)

But sometimes I’m asking myself – is it enough to  feel and understand whether the project is healthy, robust, well-doing, or not? How to figure out the core understanding of the product’s quality through the deluge of these numerous activities?
Finally, I’ve got the idea. It’s all about numbers, not words. Unit tests results can be used to assess the health status of the product. For now DataObjects.Net 4 has almost 1000 unit tests for the Core part and almost 1500 tests for storage providers (In-memory database, Oracle, PostgreSQL, MS SQL Server) and such number of tests is enough for a simple statistical analysis.

Here is the indication of DataObjects.Net 4 RC’s health status for today (20-02-2010):

The green/red bar shows the percentage of successful/failed tests for the Core and every storage provider. Note that PostgreSql 8.2, 8.3, 8.4, MS SQL Server 2005 providers show no failed tests at all. The worse result is for Oracle, its 90% only. Support for Oracle 11g is still in beta stage but we are going to improve it. The next unlucky one is the provider for MS SQL Server CE 3.5, it is also waiting for a bunch of fixes as well as its big brother  - MS SQL Server 2008.

So, I’m going to invent this health status meter publishing on a regular basis. Hope you’ll find it useful. In this case it could move to the official website of the project.

BTW, in case you have any suggestions on the topic – don’t hesitate to post a comment or two. You know, you are always welcome!

Wednesday, February 17, 2010

Microsoft Sql Server CE 3.5 provider for DataObjects.Net 4

The first version of Microsoft Sql Server CE 3.5 provider for DataObjects.Net 4 is going to be officially shipped in the upcoming DataObjects.Net 4.2 release.

Let me shortly introduce Sql server CE’s limitations. So it doesn’t support:

Feature Where is used Workaround, if any
Temporary tables Local collections support Regular tables are created and dropped automatically
Full-text search Full-text queries -
Batches Batch database operations -
CROSS APPLY SQL operator “First”, “Single”, “ElementAt”, etc. LINQ methods inside sub queries;
”SelectMany” LINQ method without “DefaultIfEmpty” specifying
-
UPDATE FROM [Table] Upgrade procedure Another, but analogous SQL statement is used
INSERT DEFAULT VALUES Insert operation Default values are explicitly specified
Savepoints Nested transactions -
ROW NUMBER SQL operator “Skip” LINQ methods -
INTERSECT, EXCEPT SQL operators “Except”, “Intersect” LINQ methods -
nvarchar(max), varchar(max), varbinary(max) datatypes   text, ntext, image are used instead
COUNT_BIG function “Count” LINQ method Result of “Count” is explicitly casted to bigint type

But despite of all these limitations there is one huge advantage of CE usage: it doesn’t require separate installation. This is significant feature for developers who works on desktop applications for personal use, for example. Just ship, install and it works. No more problems with database server installation, connections, firewalls, network configurations, etc. Another case is running tests on your business model. Actually, you don’t need big database server for this.

In order to use DataObjects.Net 4 with Sql Server CE instead of his big brother, use the following connection url:

<domain connectionUrl="sqlserverce://user:password@localhost/<Path to your database file>"/>

Another option is to use native connection string:

<domain provider="sqlserverce"
        connectionString="Data Source=<Path to your database file>"/>

Sql Server CE 3.5 provider for DataObjects.Net 4 is already available in DataObjects.Net 4.2 RC.

Saturday, January 23, 2010

Updates on the development process

The beginning of 2010 year was quite busy for the entire DataObjects.Net 4 team. One part of it headed with Alex Yakunin, has been busy with the late DataObjects.Net 4.1 release (which in fact still haven’t been shipped), the other has been working hard on the upcoming DataObjects.Net 4.2 release.

While other developers from the latter part have been working on full-text search, object-to-object mapper and other features from 4.2, personally I was working on highly demanded by customers SQL Server Compact Edition 3.5 support. The work is already done, CE support related code was committed and is being tested by our CI server during the last 2 days. The feature is going to be included into 4.2 release. As for Sql Server CE, it has a huge number of restrictions and I’m going to list all of them in one of the future posts. To sum up, it can replace its big brother (Sql Server 2005/2008) only partially.

Speaking about the upcoming DO.Net 4.2 release, we are planning to make a stable Release Candidate at the end of the January (at the beginning of the February) and deliver DO.Net 4.2 final in the middle of the February, so stay tuned.

As for me, I’ve finished working on 4.2 branch and there is a high possibility that I’ll be busy during the next 2 or 3 months with another project, not connected with DataObjects.Net development at all. The project is going to be very complicated and, as it usually happens, has very limited time-frame, but I hope that my participation will help a lot/a bit. My role will be mainly as business analyst/system architecture. I’m preparing myself for working on endless Word documents, requirements, UML diagrams, precedents, etc. So wish me good luck. =)

However, if I have enough time I’ll keep on posting some regular notes on DO.Net development process.

Wednesday, December 30, 2009

Query preprocessors, Inversion of control & Localization support

LINQ translator extension

As I promised earlier, we were going to make LINQ translator extendable and finally we’ve made this. The extension mechanism is called “Custom LINQ query preprocessors” and is already included into DataObjects.Net 4.1 code base.

Custom LINQ preprocessors must implement public interface IQueryPreProcessor which is defined in Xtensive.Storage assembly. Here it is:

public interface IQueryPreProcessor
{
  Expression Apply(Expression query);
}

As you might see, the contract is quite simple and straightforward: your preprocessor receives the whole query, modifies it in  the way you need, and returns the modified one. All preprocessors are called before the query is processed by internal LINQ translator, so it is the right time and place to apply necessary modifications.

Connecting preprocessors to translator (IoC)

After you have written you preprocessors, it is time to plug-in them to DataObjects.Net. This is done with the help of Inversion of Control concept. In order to follow it, you need to take the following steps:

1. Add reference to Microsoft.Practices.ServiceLocation.dll assembly. It is shipped with DataObjects.Net 4 and can be found in %DataObjects.Net Directory%\Lib\CommonServiceLocator directory.

2. Configure IoC container through application configuration file.

Add this line to configSections part:

    <section name="Services" type="Xtensive.Core.IoC.Configuration.ConfigurationSection, Xtensive.Core"/>

Add the corresponding configuration section:

<Services>
  <containers>
    <container name="domain">
      <types>
        <type type="Xtensive.Storage.IQueryPreProcessor, Xtensive.Storage" mapTo="Xtensive.Storage.Samples.Localization.QueryPreProcessor, Xtensive.Storage.Samples.Localization" singleton="true" />
      </types>
    </container>
  </containers>
</Services>

Note the usage of named service container (“domain”), the  IQueryPreProcessor type as an interface of a service and how it is mapped to the concrete implementation.

3. The last step is to configure Domain object & the above-mentioned service container.

// Building domain
domain = Domain.Build(DomainConfiguration.Load("Default"));

// Configuring domain-level services
var configurationSection = (ConfigurationSection)ConfigurationManager.GetSection("Services");
var container = new ServiceContainer();
container.Configure(configurationSection.Containers["domain"]);
domain.Services.SetLocatorProvider(() => new ServiceLocatorAdapter(container));

LINQ preprocessor in action

Having these actions done, we get the capability of using non-persistent localizable properties (Domain model can be found here) in LINQ queries:

using (var ts = Transaction.Open()) {

  Console.WriteLine("Implicit join through preprocessor");
  var pages = from p in Storage.Query.All<Page>()
  where p.Title=="Welcome!"
  select p;
  Console.WriteLine(pages.ToList().Count);

  ts.Complete();
}

Pay attention that neither PageLocalization type nor its members participate in the query, original p.Title expression in Where clause is used instead. As we know, Page.Title is not a persistent property and regular LINQ translator doesn’t know how to translate this expression. But having the initial query preprocessed with Xtensive.Storage.Samples.Localization.QueryPreProcessor makes such kind of expressions possible to use. The only thing the preprocessor makes is the replacement of p.Title expression to something like this:

p.Localizations.Where(localization => localization.CultureName==LocalizationContext.Current.CultureName)
          .Select(localization => (string)localization[“Title”])
          .FirstOrDefault();

That’s it.

The source code is available in our public repository in Xtensive.Storage.Samples.Localization folder.

Happy preprocessing! =)

Monday, December 28, 2009

Logging, part 3. Configuring logging through log4net

In the previous post I demonstrated how to configure and use internal DataObjects.Net logging capabilities. These are rather useful and flexible but in case you want much more flexibility or something, using one of the external logging frameworks could be the right choice for you.

As I already mentioned, external logging components are connected to DataObjects.Net through the mechanism of adapters: DataObjects.Net => adapter for logger => logger.

In case of log4net you’ll need the following assemblies:

  • log4net.dll (can be found here)
  • Xtensive.Adapters.log4net.dll (is contained in DataObjects.Net installer)

The next step is to configure both DataObjects.Net & log4net.

Add these sections to configSection block of your application configuration file:

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    <section name="Xtensive.Core.IoC" type="Xtensive.Core.IoC.Configuration.ConfigurationSection, Xtensive.Core"/>

Xtensive.Core.IoC namespace goes for basic Inversion of Control implementation however it is powerful enough to accomplish most of appropriate tasks. In this case it is used to map Xtensive.Core.Diagnostics.ILogProvider interface to some external implementation (Xtensive.Adapters.log4net.LogProviderImplementation type).

<Xtensive.Core.IoC>
  <containers>
    <container>
      <types>
        <type type="Xtensive.Core.Diagnostics.ILogProvider, Xtensive.Core" mapTo="Xtensive.Adapters.log4net.LogProviderImplementation, Xtensive.Adapters.log4net" singleton="true"/>
      </types>
    </container>
  </containers>
</Xtensive.Core.IoC>

The last step is log4net configuration:

<log4net>
  <appender name="FileAppender" type="log4net.Appender.FileAppender">
    <file value="log-file.txt" />
    <appendToFile value="true" />
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date %-5level %logger - %message%newline" />
    </layout>
  </appender>
  <root>
    <level value="WARN" />
    <appender-ref ref="FileAppender" />
  </root>
<!-- To log warnings & errors from Xtensive.Storage.* loggers --> <logger name="Storage" additivity="false"> <level value="WARN" /> <appender-ref ref="FileAppender" /> </logger> <!-- To log all SQL statements --> <logger name="Storage.Providers.Sql" additivity="false"> <level value="ALL" /> <appender-ref ref="FileAppender" /> </logger> </log4net>

Having these configuration steps done, you’ll get DataObjects.Net & log4net bundle configured & working.

Friday, December 25, 2009

Logging, part 2. Architecture & configuration

The main goal was: how to make logging and its configuration easy for simple scenarios and in the meantime highly adaptable for complex ones.

In order to achieve the required level of flexibility most logging frameworks have the following components:

  • Loggers (named instances of some public class or interface (usually ILog) that provides developers with functionality to write diagnostic messages to).
  • Appenders (output destinations of above-mentioned loggers).
  • Log manager or log provider (usually a central access point of a framework. It resolves loggers by their names).

DataObjects.Net logging system follows exactly this pattern but bearing in mind that it must provide the possibility to plug-in any logging framework it introduces its own set of abstract components (actually loggers & log manager, but not appenders) which in fact just wrap up the plugged-in ones and simply redirect diagnostic messages to them. Moreover, in case when none of standalone logging frameworks is plugged-in, DataObjects.Net contains its own simple implementation of those components.

The main access point is the public static LogProvider class with one method LogProvider.GetLog(string logName), which is used to resolve a required ILog instance by its name. Once instance of ILog is obtained, it can be used to log Debug, Info, Warning, Error & FatalError messages through the corresponding methods.

It could be considered as a good practice when members from one namespace log their messages into the same logging space. This namespace-based approach is quite useful as usually a namespace contains a set of classes that are closely coupled and execute some shared piece of programming logic, therefore the idea to merge their diagnostic output in one log seems to be rather sensible. Due to this approach DataObjects.Net contains a set of predefined loggers for most frequently used namespaces, such as: Xtensive.Core, Xtensive.Storage, Xtensive.Storage.Building and so on. Each of these loggers has name which corresponds to its namespace except "Xtensive." prefix. Say, logger for Xtensive.Core namespace is named as "Core".

For usability reasons the above-mentioned namespaces contain public static class named Log which exposes the same set of logging methods as ILog interface. As you might understand, each of these static Log classes is no more than a connector between its consumers (classes which use it as a logger) and corresponding ILog instance that is transparently constructed on demand.

Configuring internal DataObjects.Net's log output

Internal logging subsystem is not as powerful as some well-known logging monsters but rather flexible and doesn't require any additional components. Configuration of DataObjects.Net's logging is made in application configuration file (app.config or web.config).

First of all, include Xtensive.Core.Diagnostics section into configSections section:

  <configSections>
    <section name="Xtensive.Core.Diagnostics" type="Xtensive.Core.Diagnostics.Configuration.ConfigurationSection, Xtensive.Core" />

The second step is to configure logs (appenders in terms of log4net):

<Xtensive.Core.Diagnostics>
  <logs>
    <!-- Use these settings for Xtensive.Storage.* logs -->
    <log name="Storage" events="Warning,Error,FatalError" provider="File" fileName="Storage.log" />
  </logs>
</Xtensive.Core.Diagnostics>

Note that each log has a name which is equal to the namespace where it is located except "Xtensive." prefix. This is true for logs from DataObjects.Net only and might not be true for logs from your own application.

Types of events: Debug, Info, Warning, Error, FatalError.

Types of providers: File (you need to provide file name as well), Debug, Console, Null (no logs at all, analogue of /dev/null), Error.

The example of log:

2009-12-17 00:00:02,052 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Creating connection 'sqlserver://*****'.
2009-12-17 00:00:02,052 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Opening connection 'sqlserver://*****'.
2009-12-17 00:00:02,052 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Beginning transaction @ ReadCommitted.
2009-12-17 00:00:02,068 DEBUG Storage.Providers.Sql - Session 'Default, #9'. SQL batch: 
SELECT [a].[Id], [a].[TypeId], [a].[Name], [a].[Code], [a].[Description], [a].[LongDescription],
 [a].[IsForChildren], [a].[BasePrice], [a].[Price], [a].[SizeString], [a].[HasNoInnerCover]
 FROM [dbo].[Product] [a] ORDER BY [a].[Id] ASC
2009-12-17 00:00:02,068 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Commit transaction.
2009-12-17 00:00:02,068 DEBUG Storage.Providers.Sql - Session 'Default, #9'. Closing connection 'sqlserver://*****'.

Looks pretty good, right?

In the next post I’ll describe how to use external logging framework with DataObjects.Net.

Wednesday, December 23, 2009

Logging, part 1. Introduction

In the next posts I’m going to describe how logging in DataObjects.Net is designed, how it works and how to configure and use it in most effective way. In the meantime, I’m writing exactly the same chapter in the manual, so this work will be paralleled, although I suppose that the blog version will be a bit more informal than manual’s one.

Let’s start then.

In general, logging is the feature most of software engineers use to track how the system works and analyze when it starts to behave in improper manner. It goes without saying that logging capabilities are essential for any product designed primarily for developers and software companies. But having decided that your framework must use some kind of logging, you immediately face up to another challenge: which logging system to use as there are plenty of them (log4net, NLog, etc.). Moreover, you might want to invent your own super-duper logging system.

This choice is rather simple for small products or libraries: they just use one of the most famous and simple in usage, i.e. log4net, or writes everything to some place that can be set up somewhere in configuration file, i.e. "C:\Debug\". But is this straightforward approach good enough for their customers that use these small standalone components to build something more complex and non-trivial?

In such cases the right word is "Transparent integration", it really matters how your small library can be integrated into large system, is its logging subsystem flexible enough to be easily integrated with logging framework that is used there? These are the questions DataObjects.Net development team was thinking about when logging subsystem was about to be implemented.

To be continued…

BTW, we are going to publish the updated localization sample with the generalized LINQ pre-processor that is used to automatically and transparently join localizable & localization entities and substitute calls to localizable properties soon. Stay tuned!

Wednesday, December 16, 2009

Localization support, part 4. Queries

Another interesting part in the localization support story (part 1, part 2 & part 3) is how to make queries for localizable objects.

Problem

The only problem here is the virtuality of localizable properties.

public class Page : Entity
{
  ...
  public string Title
  {
    get { return Localizations.Current.Title; }
    set { Localizations.Current.Title = value; }
  }

  public string Content
  {
    get { return Localizations.Current.Content; }
    set { Localizations.Current.Content = value; }
  }
  ...
}

As these are not persistent properties, domain model doesn’t contain even a bit of information about them nor Page table doesn’t contain corresponding Title & Content columns, therefore LINQ translator simply doesn’t know what to do when it encounters them during LINQ query parsing stage. The following query leads to InvalidOperationException:

var pages = Query<Page>.All.Where(p => p.Title=="Welcome");

Exception:
“Unable to translate '$<Queryable<Page>>(Query<Page>.All).Where(p => (p.Title == "Welcome"))' expression”.

And the Exception.InnerException shows us the detailed description of the failure:
“Field 'p.Title' must be persistent (marked by [Field] attribute)”.

Therefore, the query in order to be executable must be rewritten in the following way:

var pages = from p in Query<Page>.All
join pl in Query<PageLocalization>.All
  on p equals pl.Target
where pl.CultureName==LocalizationContext.Current.CultureName && pl.Title=="Welcome"
select p;

Certainly, it is not convenient to write such overloaded queries every time you want to filter or sort by localizable properties. The only way we can optimize it is to introduce some level of abstraction.

Solution

First step is to define LocalizationPair, a pair of target entity and corresponding localization:

public struct LocalizationPair<TTarget, TLocalization> where TTarget: Entity where TLocalization: Model.Localization<TTarget>
{
  public TTarget Target { get; private set; }
  public TLocalization Localization { get; private set; }
}

The next one is to build a class that hides the complexity of join and filter operation. I named this class as “Repository”, but frankly speaking it isn’t real repository as it doesn’t implement all functionality from well-known DDD Repository pattern. Anyway, here it is:

public static class Repository<TTarget, TLocalization> where TTarget: Entity where TLocalization: Model.Localization<TTarget>
{
  public static IQueryable<LocalizationPair<TTarget,TLocalization>> All
  {
    get
    {
      return from target in Query<TTarget>.All
        join localization in Query<TLocalization>.All
          on target equals localization.Target
        where localization.CultureName==LocalizationContext.Current.CultureName
        select new LocalizationPair<TTarget, TLocalization>(target, localization);
    }
  }
}

And this is how we can use these 2 classes in queries:

var pages = from pair in Repository<Page, PageLocalization>.All
where pair.Localization.Title=="Welcome!"
select pair.Target;

Simple and functional enough to use.

Conclusion

The above-mentioned sample is built on basis of DataObjects.Net 4.1, no changes were made to ORM itself to achieve the declared features and I think this is quite promising in terms of maturity and flexibility.

There are 2 ways how we are going to develop the very idea of localization:

  • LINQ extension API will be implemented. This will help us to connect custom LINQ query rewriters which will transparently alter queries in order to insert joins, filters and stuff. Particularly this feature will eliminate the necessity of LocalizationPair & Repository classes, defined in the sample. As soon as this functionality appears, I’ll update the sample and make a post on this topic.
  • The actual localization support on ORM level will be implemented at the same time with full-text search realization or a bit later because these features are interconnected.

Friday, December 11, 2009

Localization support, part 3. CRUD operations

In the previous posts we discussed the options for localization implementation on database level & on domain model level. Let’s continue the topic and see how CRUD operations can be applied to localized entities.

Scenarios

Generally, there are two scenarios:

1. In first you deal with localized properties as if they are regular ones and DataObjects.Net does all the other stuff in background. You act as there is no such notion as localization. You simply get and set values to properties as usual. In this case you deal with currently active culture and corresponding localization object. Switching of Thread.CurrentCulture property or localization scope usage are the best ways to do this.

var english = new CultureInfo("en-US");
var russian = new CultureInfo("ru-RU");
var welcomePage = new Page();

// Editing localizable properties through localization scope
using (new LocalizationScope(english)) {
  welcomePage.Title = "Welcome!";
  welcomePage.Content = "My dear guests, welcome to my birthday party!";
}

// Editing localizable properties through CurrentThread.CurrentCulture
Thread.CurrentThread.CurrentCulture = russian;
welcomePage.Title = "Здравствуйте!";
welcomePage.Content = "Добро пожаловать, дорогие гости! На базаре сейчас всё так дорого.";

Note, code that works with localizable properties, is the same that one that work with any other regular persistent properties.

2. In the second scenario your code knows that some localization takes place and wants to get or update localized properties for several cultures at a time. Say, you edit Page object from website administration system and want to see all available localizations for it. Therefore, you should have the possibility to get them all and to make some changes directly to the chosen ones. The approach with localization scope is not an option for such kind of task.

var goodbyePage = new Page();

// Editing localizations directly
goodbyePage.Localizations[english].Title = "Goodbye!";
goodbyePage.Localizations[english].Content = "Goodbye, my dear friends.";
goodbyePage.Localizations[russian].Title = "До свидания!";
goodbyePage.Localizations[russian].Content = "Надеюсь больше никогда вас не увидеть.";

Both scenarios are supported in the above-mentioned Localization sample and Page instances as well as PageLocalization instances are persisted transparently.

Results

Here are localizations for 2 Page instances:

Table

Note that first 2 columns are key columns. First one is string representation of CultureInfo, and the second on is a reference to localizable object. All other columns are localized versions of properties from Page class.

In the next post we’ll figure out what should be done to use LINQ with localized entities.

Thursday, December 10, 2009

Localization support, part 2. Domain modeling

In the previous post we discussed the theoretical possibilities of localization feature implementation in terms of physical organization on database level. Let’s continue the discussion on Domain model level.

Obviously, all these implementation approaches require some common infrastructure, such as automatic binding to Thread.CurrentCulture, temporary switching of current culture and so on. In that case, let’s start with this part.

Infrastructure

  1. To begin with, let’s think of what information do we need.
    First and the most necessary one is an instance of CultureInfo class which is used to represent current culture in particular application code block.
  2. Another point, not so obvious as first one, but also important is some kind of localization policy which describes what should be done if requested localization is not found. Say, you have localizations for “en-US” & “ru-RU” cultures but don’t have one for “fr-FR” culture. What should be done if someone switches Thread.CurrentCulture to “fr-FR” culture and tries to access localized properties? Should new localization for the specified culture be created or default one should be used? If so, which culture is default then?

To answer these questions the notion of immutable LocalizationContext class is introduced. It is defined in pseudo-code as follows:

public class LocalizationContext
{
  public CultureInfo Culture { get; }

  public string CultureName { get; }

  public LocalizationPolicy Policy { get; }

  public static LocalizationContext Current { get; }

}

LocalizationContext.Current property provides a programmer with valid current localization context everywhere it is required.

For now, LocalizationContext.Current is bound to Thread.CurrentThread.CurrentCulture property and changes its value each time current culture of current thread is being changed. Hence, if you want to temporarily change localization context (activate another culture) you are to change Thread.CurrentThread.CurrentCulture property and after doing some work revert it back, which is not robust nor convenient at all. To overcome this problem, LocalizationScope class is added. It acts as a disposable region where specified localization context is activated and after disposal it restores the previous localization scope value. Here is how it works:

// LocalizationContext.Current.Culture is en-US

using(new LocalizationScope(new CultureInfo("ru-RU"))) {
  // LocalizationContext.Current.Culture is ru-RU
  // do some work with ru-RU culture
}

// LocalizationContext.Current.Culture is en-US again

So now LocalizationContext.Current property logic must take into account the presence and configuration of currently active localization scope and fall back to Thread.CurrentCulture in case current localization scope is absent.

Modeling Domain

Say we have a Page class in Domain model with 2 persistent properties: Title & Content, both of them we want to make localizable. Then this is how we do it:

  1. We define PageLocalization - localization class for Page, which contains localized persistent properties. Its primary key consists of 2 fields: a reference to Page and a string representation of CultureInfo, which in turn can be represented as CultureInfo.Name.
  2. We define localizable properties in Page class as NOT persistent. They are no more than wrappers for appropriate localized persistent properties located in localized instance.

Here is localization for page class:

[HierarchyRoot]
public class PageLocalization : Localization<Page>
{
  [Field(Length = 100)]
  public string Title { get; set; }

  [Field]
  public string Content { get; set; }

  public PageLocalization(CultureInfo culture, Page target)
    : base(culture, target)
  {}
}

It inherits Localization<Page> class where key fields are declared.

And here is the Page class:

[HierarchyRoot]
public class Page : Entity
{
  [Field, Key]
  public int Id { get; private set; }

  public string Title
  {
    get { return Localizations.Current.Title; }
    set { Localizations.Current.Title = value; }
  }

  public string Content
  {
    get { return Localizations.Current.Content; }
    set { Localizations.Current.Content = value; }
  }

  [Field, Association(PairTo = "Target", OnOwnerRemove = OnRemoveAction.Cascade)]
  public LocalizationSet<PageLocalization> Localizations { get; private set; }
}

Localizable properties such as Title & Content redirect all calls to currently active PageLocalization which is accessed through Page.Localizations.Current property. What is LocalizationSet<PageLocalization> then?

Believe it or not, LocalizationSet<PageLocalization> is no more than common EntitySet<T> with some additional functionality:

public class LocalizationSet<TItem> : EntitySet<TItem> where TItem : Localization
{
  public TItem this[CultureInfo culture] { get; }

  public TItem Fetch(string cultureName)

  public TItem Current { get; }

  private TItem GetCurrent()

  private TItem Create(CultureInfo culture)
}

Here the decision what to do if localization for the current culture is requested, is made according to localization policy in current localization context.

In the next post we’ll try to figure out how to deal with CRUD operations, LINQ queries and localized entities.

Stay tuned.

Monday, December 7, 2009

Localization support, part 1. Theory

DataObjects.Net 3.x, the successful predecessor of DataObjects.Net version 4.x, contained tons of useful features and feature named “Multilingual database support” was among them. It was implemented at the very core level of ORM as any other feature in 3.x branch by the only reason: the product was architecturally monolithic and all modules were highly coupled with each other.

The design of 4.x version propagates the idea of low coupling, the ORM consists of a set of separate modules which are mostly independent from each other. This approach we are going to apply to localization feature as well.

Requirements

Let’s list requirements for the feature. What do we want from it?

  1. Simplicity and power in one place. The less developer need to do to add localization support to his application, the better. Declarative approach with minimum coding activity on Domain modeling stage will be the right one.
  2. Adequate performance. Usage of localization should not add performance drawbacks.
  3. Automatic integration with Thread.CurrentCulture and/or Thread.CurrentUICulture infrastructure.
  4. Possibility to add new culture(s) in runtime (optional). The less changes to database schema are required in order to add new culture, the better.
  5. Transparent LINQ support.
  6. The less ORM knows about localization, the better. Ideal option is standalone add-on, made on top of the ORM.
  7. There should be a way to get not only one localization for current culture for the particular entity but a set of localizations. This might be required for application administration (translation, adding new cultures and so on).

Database schema level implementation

While there is numerous ways to implement localization support in Domain modeling level, there are only several ways to do it on database schema level. Personally, I see the following options:

1. Localized columns

LocalizableColumns

Every localizable persistent field is mapped to a set of columns, each of them is mapped to the corresponding culture. This approach was used in DataObjects.Net 3.x.

Pros:

  • No performance drawbacks. No additional queries, joins, subqueries is required.
  • Simplicity and obviousness. It is easy to edit culture-dependent columns right in database.
  • Data integrity out of the box because all culture dependent columns are stored in the same row as entity itself.
  • Possibility to configure parameters of each localizable column (length, nullability, type).

Cons:

  • ORM must know about localization in order to fetch or persist to the required set of columns.
  • Database schema alteration is required in order to add new culture to application.
  • No way go retrieve column values for cultures other than the current one.
  • Is not clear how to cache localized data in Session-level and Domain-level cache.

2. Localized tables

LocalizableTables[1]

Localized columns are moved to separate tables, one for the particular culture. This is an analogue of approach in .NET application localization when localized strings and other resources are located in separate assembly and are loaded automatically.

Pros:

  • Data integrity is provided by the foreign keys constraints with ON REMOVE = CASCADE option.
  • Simplicity. All culture-dependent values for the particular entity are located in corresponding table.
  • Possibility to configure parameters of each localizable column (length, nullability, type).

Cons:

  • Join operation is required in order to fetch columns for appropriate culture.
  • All from “Localized columns” approach.

3. Localized entities

LocalizedEntities

Localizable entity is split into 2 parts: common part - “Page” and localized one - “PageLocalization”. Second entity contains localizable fields and its primary key consists of 2 fields: a reference to localizable entity and string representation of CultureInfo (generally, CultureInfo.Name).

Pros:

  • ORM doesn’t know anything about localization at all.
  • Database schema alteration is not required in order to add new culture.
  • It is rather easy to fetch all translations for the particular entity with 1 query.
  • Standard ORM-level caching out of the box.
  • Data integrity is provided by the foreign keys constraints and ORM.
  • Possibility to configure parameters of each localizable column (length, nullability, type).

Cons:

  • Join operation is required to fetch a set of columns for corresponding culture.

4. Localized strings

LocalizedStrings

The most weird one. Could be invented by some geek in experimental goals only. Anyway, let’s investigate it.

Pros:

  • Database schema alteration is not required in order to add new culture.
  • It is rather easy to fetch all translations for the particular entity with 1 query.
  • Data integrity is provided by the foreign keys constraints.

Cons:

  • ORM must know about localization in order to fetch or persist to the required set of columns. 
  • Is not clear how to cache localized data in Session-level and Domain-level cache.
  • Separate query is required to fetch localized data for the particular entity.
  • Parameters of each localizable column (length, nullability, type) can’t be configured separately. “Strings.Value” column is used for all localizable fields, such as strings, integer types, dates and so on, hence maximal value size should be used with no constraints.
  • It is not obvious how to handle localizable field renaming.

Having this options evaluated should definitely help us to choose the most appropriate one for adding localization support to DataObjects.Net 4.1.

Which option for ORM would you take if you are to decide?

Wednesday, December 2, 2009

Npgsql2 provider, version 2.0.7

The time has come, the long awaited version of Npgsql2 provider is released.

You may ask me about the reason, why it was being awaited for so long? The answer is: this summer our PostgreSql support team carried out a set of investigations and proposed some critical speed-related improvements to some parts of Npgsql2 provider source code and sent the patch to PgFoundry. The patch contains 2 optimizations:

  1. Parameters handling in NpgsqlCommand.GetClearCommandText method. A set of parameters was stored in an array-based collection and linear search algorithm was used to find the requested one. This pattern is good enough for relatively small number of parameters but as you might know -- DataObjects.Net 4 uses Über-Batching Technology™, hence large number of parameters in one command might be achieved with ease. Using a dictionary to store and search for requested parameter speeded up this method up to 5 times (400% increment).
  2. NpgsqlCommand contains static readonly Regex field which is used for splitting the entire command text into chunks. We suggested to add RegexOptions.Compiled to its initialization, which might increase the startup time but yields faster execution.

Both of them were accepted and merged into the main branch. So now, with the recent release of Npgsql2 provider version 2.0.7 which includes the above-mentioned changes, we can safely move to Npgsql2 provider in DataObjects.Net (Npgsql provider version 0.97 is currently used).

And last but not the least, we are proud to know that we made such a good contribution to a well-known, successful open source project such as Npgsql2 provider.

Thursday, November 26, 2009

Nested transactions

Hi there, did you miss me? =)

I’ve got several good news about current DataObjects.Net’s development phase.
First of all, we started to implement nested transactions. It might had required to change the current transaction API but we managed to keep the compatibility with the previous version of the API.

In DO 4.0.5 we wrote:

using (var tx = Transaction.Open()) {

  // your code here
  tx.Complete();
}
and that meant that we wanted to say that a transaction is required for that part of code so DO must open one if it is absent; otherwise DO should do nothing.

In DO 4.1 this code means exactly the same thing: I need a transaction, please, open one if it is absent; otherwise do nothing.

And this is how we are going to tell that a new transaction is definitely required:

using (var tx = Transaction.Open(TransactionOpenMode.New)) {

  // your code here
  tx.Complete();
}

Note new TransactionOpenMode enum. It is introduced in DO 4.1 and has 2 options: Auto and New. Auto goes for default behavior (I don’t care which transaction I need, just provide me with one) and New goes for new (or nested one, if an outer transaction is already opened).
Rolling back nested transaction does not make any harm to outer transaction whereas commit of outer transaction automatically commits all nested transactions.

Nested transaction are implemented for SQL-based storages with the help of Savepoint feature. It is supported by most SQL servers such as MS SQL Server, Oracle, PostgreSQL, etc. Moreover, Savepoint notion is included in SQL standard.

P.S. Please remember that this is preliminary API that might be changed in final version.

Wednesday, November 11, 2009

DataObjects.Net goes to Ohloh.net

Here is the link to project’s page.

Join our camp! We’d be glad to see you among DataObjects.Net contributors and users.

Thanks!

Tuesday, November 3, 2009

DataObjects.Net goes to Google Code

Great news!

DataObjects.Net v4 moved to the public Mercurial-based repository. From this moment anyone can join the project and participate, and even build his own DataObjects.Net version. Why not? It is so easy. I bet that Alex is going to write a post in his blog describing how to check out the source code and build your own local copy of DataObjects.Net v4.

In the update list you can might that the major part of DataObjects.Net team except one or two developers works mainly on manual and manual-related tasks. This part has the highest priority for now. And this work is not hidden anymore, anyone can browse the repository, see the overall progress and read the manual even if it is not completed yet. Here is the link to Manual folder. Check it out and begin to read. Every help, suggestions, every found mistake is highly appreciated. Thanks in advance!

BTW, maintaining the source code in public repository, we can exclude the source code from the future DataObjects.Net installers.

CodeProject

Friday, October 30, 2009

DataObjects.Net v4 manual

Finally, we started with long awaited DataObjects.Net v4 full-fledged manual.

But before we started we had spent lot of time in discussions about tools to be used for manual creating.
Three options were suggested:

Microsoft Word approach

Use Microsoft Word for editing then export as HTML and clean up the resulting HTML from Word-ish garbage.

Advantages:
  • WYSIWYG;
  • Easy editing & formatting;
  • Proofing;
  • Inserts images;
  • Inserts highlighted source code from Visual Studio.
Disadvantages:
  • You must run “Export to HTML” & make a cleanup every time you want to view page in browser;
  • Almost no version control;
  • Difficult to make references between resulting HTML pages;
  • Difficult to manage inserted images.

Wiki approach

Use Wiki to build the whole manual then grab its content and convert into local HTML pages.

Advantages:
  • No need for local editors, just your browser and you;
  • Simple version control;
  • Supports highlighting of source code fragments.
Disadvantages:
  • Grabbing & converting wiki into manual is not a trivial task;
  • No proofing (proofing can be provided by some browsers while writing text in textarea control);
  • Wiki markup (you are to know it to format your text).

Plain HTML approach

Advantages:
  • There are a lot of WYSIWYG HTML editors that support Microsoft Word’s functionality (formatting, proofing);
  • Clean HTML code (you control it);
  • You see the resulting HTML at any moment (no need for grabbing or conversion);
  • You control where and how your images are placed and named;
  • Making references between pages is simple;
  • Full version control support as HTML file is a text file.
Disadvantages:
  • Good enough HTML editor must be found;
  • No support for inserting highlighted source code fragments from Visual Studio.

 

After intense discussions we decided to use Plain HTML approach. Firstly we used SharePoint Designer 2007 as HTML editor, then moved to Visual Studio. At last we found Microsoft Expression Web 3, which actually had been made on top of SharePoint Designer. It is faster than VS and more convenient. I like it except its black theme – it is too dark for me.

Microsoft Expression Web 3

One more useful tool that we found is “Copy As HTML” add-in for Visual Studio. It allows you to copy source code from the Code Window and convert it into HTML while preserving syntax highlighting, indentation and background color.

Copy As HTML

 

And what about you? What tools do you use to write help or manuals? Are there any other, more powerful tools or more convenient ways to do this?

Thanks in advance for your ideas.

CodeProject

Tuesday, October 27, 2009

Arbitrary keys & hierarchies. Complete reference set

Here are references to all parts of the “Arbitrary keys & hierarchies” series gathered in one place:

  1. Introduction
  2. Hierarchies
  3. Evolution of Key
  4. Working with keys
  5. Key providers
  6. Identity fields
  7. Custom key generators

Thanks for your interest in DataObjects.Net v4.

CodeProject

Monday, October 26, 2009

Arbitrary keys & hierarchies, part 7. Custom key generators

As DataObjects.Net v4 supports wide variety of keys but has default implementation only for key generators with exactly one identity field, there could be scenario in which custom key generator is required. To close the gap DataObjects.Net v4 declares the following abstract class:

public abstract class KeyGenerator
{
  public KeyProviderInfo KeyProviderInfo { get; private set; }

  public abstract Tuple Next();

  public virtual void Initialize() {}

  protected KeyGenerator(KeyProviderInfo keyProviderInfo) {}
}

Custom generator type must inherit KeyGenerator type and implement at least the abstract method KeyGenerator.Next(). All necessary information concerning the structure of key, caching behavior and so on can be found in KeyProviderInfo type. Here is the custom implementation of key generator for Guid type:

public sealed class MyGuidKeyGenerator : KeyGenerator
{
  public override Tuple Next()
  {
    return Tuple.Create(KeyProviderInfo.TupleDescriptor, Guid.NewGuid());
  }

  public MyGuidKeyGenerator(KeyProviderInfo keyProviderInfo)
    : base(keyProviderInfo)
  {}
}

In order to indicate that a hierarchy must be served with custom key generator, KeyGeneratorAttribute was introduced. Here is how it is intended to be  used:

[HierarchyRoot]
[KeyGenerator(typeof(MyGuidKeyGenerator))]
public class Author : Entity
{
  [Field, Key]
  public Guid Id { get; private set; }

  [Field]
  public EntitySet<Book> Books { get; private set; }
}

In scenarios when key generator for a hierarchy is not required at all, this must be set up appropriately:

[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.None)]
[TableMapping("Metadata.Type")]
[Index("Name", Unique = true)]
public class Type : MetadataBase
{
  [Field, Key]
  public int Id { get; private set; }

  [Field(Length = 1000)]
  public string Name { get; set; }

  public Type(int id, string name) 
    : base(id)
  {
    Name = name;
  }

Here is how system class Metadata.Type is declared in DataObjects.Net v4. Pay attention to KeyGeneratorAttribute usage together with the absence on parameterless constructor. This means that identity field values are provided from the outside and there is actually no need in key generator.

This is the last post in “Arbitrary keys & hierarchies” series. Hope you’'ll find it useful.

Part 6. Identity fields

P.S.
If you want me to blog on some particular topic concerning DataObjects.Net v4 domain – make a request in comments.

CodeProject

Arbitrary keys & hierarchies, part 6. Identity fields

DataObjects.Net v4 supports the following .Net types to be used in identity fields:

  • Boolean;
  • Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64;
  • String, Char;
  • Double, Single, Decimal;
  • Guid, DateTime, TimeSpan;
  • Reference to Entity (is stored as Key)

Usage of Structure and EntitySet<T> types is not allowed, however restriction for Structure usage could disappear in future versions of DataObjects.Net.

Identity fields are set once for all persistent hierarchy, so all descendants of hierarchy root share the same Key structure. In order to set up structure of Key KeyAttribute class should be used:

[HierarchyRoot]
public class Animal : Entity
{
  [Field, Key]
  public int ID { get; private set; }

  [Field]
  public int Name { get; set; }
}

Key attribute must be placed on each identity field. Also note, that identity field must be immutable, it is prohibited for identity field to have a public, protected or internal setter.

For persistent types with complex keys (which have more than one identity field) explicit identity field order is strictly recommended, because the particular order of list of properties, got with the help of .NET reflection, is not guaranteed at all.

[HierarchyRoot]
public class BookReview : Entity
{
  [Field, Key(1)]
  public Person Reviewer { get; private set; }

  [Field, Key(0)]
  public Book Book { get; private set; }

  [Field(Length = 4096)]
  public string Text { get; set; }

  public BookReview(Book book, Person reviewer)
    : base(book, reviewer)
  {}

Pay attention to KeyAttribute usage (Key(0), Key(1) lines). In this example it also used to set the position of identity field within complex key. So for BookReview type the structure of Key is {Book, Person}. Also note that values for both identity fields are required in BookReview constructor and passed to the base constructor of Entity where key is constructed.

Part 5. Key providers, Part 7. Custom key generators

CodeProject