News, examples, tips, ideas and plans.
Thoughts around ORM, .NET and SQL databases.

Monday, November 28, 2011

DataObjects.Net 4.3.8 RC 1 & 4.4.1 RC 1 are published

Today DataObjects.Net 4.3.8 RC 1 & DataObjects.Net 4.4.1 RC 1 are published and available for download.

Just as a reminder: the list of fixes, updates and new features is here, the detailed overview of index management-related features is here.

No special actions for migration to the new version are required except updating the references.

We are waiting for your feedback! Write to us to support@x-tensive.com.

Thank you!

Thursday, November 24, 2011

DataObjects.Net 4.5 Beta 4 binaries are updated

DataObjects.Net 4.5 Beta 4 package is updated to the latest revision, incorporating all the fixes and features from branches 4.3 & 4.4. The updated binaries can be downloaded from here.

Monday, November 21, 2011

DataObjects.Net Extensions, part 1

I'm writing this post on behalf of Alexander Ovchinnikov who is the author of DataObjects.Net Extensions project. Earlier Alexander developed LINQPad provider for DataObjects.Net, which is included into standard DataObjects.Net installation package starting from version 4.5, so he is deservedly one of the most productive contributors in DataObjects.Net community.

This project is officially called DataObjects.Net Extensions and is distributed under MIT license. Source code is available on github. The latest version of DataObjects.Net Extensions contains 2 main set of features:
- Batch server-side update and delete operations
- Operation reprocessing

In this post I'll describe the first part, batch server-side update and delete operations.

How to install the extension

1. The extension is distributed in a form of NuGet package, so to use it you should install NuGet Manager if you haven't installed it already. As far as I know, Visual Studio 2010 SP 1 already includes this manager by default.

2. Open the desired solution and in the context menu select "Manage NuGet packages..."


3. Search for "DataObjectsExtensions" package


4. Click "Install", choose projects to add the reference to and click "OK".


5. Check that the package is successfully installed and start using it.

Sample domain model

This model will be used in code samples.
[HierarchyRoot]
    public class Bar : Entity {

        public Bar(Session session) : base(session){}

        [Field, Key]
        public int Id { get; set; }

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

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

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

    [HierarchyRoot]
    [Index("Name", Unique = true)]
    public class Foo : Entity{

        public Foo(Session session) : base(session){}

        public Foo(Session session, int id) : base(session, id){}

        [Field, Key]
        public int Id { get; set; }

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

        [Field]
        [Association("Foo")]
        public Bar Bar { get; set; }
    }

Batch server-side operations

Why they are important?

Well, Captain Obvious to the rescue, sometimes there could be a scenario when a set of records needs to be updated without fetching them on client. There might be various reasons for that such as performance issues, limitations of business logic, etc. In such cases users have to utilize old good plain SQL to execute "UPDATE" or "DELETE" commands via underlying ADO.NET providers. But while this is not always convenient, this also could lead to potentially erroneous results because as soon as domain model changes, it becomes out of sync with these SQL commands.

DataObjects.Net Extensions sorts out the problem providing a set of IQueryable extension methods that are translated to the desired UPDATE or DELETE commands. For instance:
Query.All<Bar>()
  .Where(a => a.Id == 1)
  .Set(a => a.Count, 2)
  .Set(a => a.Name, a => a.Name + "suffix")
  .Update();
Listed below are some scenarios which you might encounter while using the package. Every case is followed by the corresponding translation to SQL.

Updating persistent property with constant value

Query.All<Bar>()
  .Where(a => a.Id == 1)
  .Set(a => a.Count, 2)
  .Update();
is translated to:
DECLARE @p0 Int SET @p0 = 2
UPDATE [dbo].[Bar]
SET [Count] = @p0
FROM [dbo].[Bar] AS j0 INNER JOIN (
SELECT
  [a].[Id]
FROM
  [dbo].[Bar] [a]
WHERE
  ([a].[Id] = 1)
) AS j1 ON (j0.[Id] = j1.[Id])

Updating persistent property with expression, computed on server

Query.All<Bar>()
  .Where(a => a.Id==1)
  .Set(a => a.Count, a => a.Description.Length + a.Count * 2)
  .Update();
is translated to:
UPDATE [dbo].[Bar]
SET [Count] = ((DATALENGTH([Description]) / 2) + ([Count] * 2))
FROM [dbo].[Bar] AS j0 INNER JOIN (
SELECT
  [a].[Id]
FROM
  [dbo].[Bar] [a]
WHERE
  ([a].[Id] = 1)
) AS j1 ON (j0.[Id] = j1.[Id])

Setting a reference to an entity that is already loaded into Session

// Emulating entity loading
var bar = Query.Single<Bar>(1);

Query.All<Foo>()
  .Where(a => a.Id == 2)
  .Set(a => a.Bar, bar)
  .Update();
is translated to:
DECLARE @p0 Int SET @p0 = 1
UPDATE [dbo].[Foo]
SET [Bar.Id] = @p0
FROM [dbo].[Foo] AS j0 INNER JOIN (
SELECT
  [a].[Id]
FROM
  [dbo].[Foo] [a]
WHERE
  ([a].[Id] = 2)
) AS j1 ON (j0.[Id] = j1.[Id])

Setting a reference to an entity that is not loaded into Session, 1st way

Query.All<Foo>()
  .Where(a => a.Id == 1)
  .Set(a => a.Bar, a => Query.Single<Bar>(1))
  .Update();
In this case DataObjectsExtensions are smart enough to extract the value of key from Query.Single method argument and use that value in command translation:
DECLARE @p0 Int SET @p0 = 1
UPDATE [dbo].[Foo]
SET [Bar.Id] = @p0
FROM [dbo].[Foo] AS j0 INNER JOIN (
SELECT
  [a].[Id]
FROM
  [dbo].[Foo] [a]
WHERE
  ([a].[Id] = 1)
) AS j1 ON (j0.[Id] = j1.[Id])
All overloads of Query.Single, Query.SingleOrDefault methods are also supported.

Setting a reference to an entity that is not loaded into Session, 2nd way

Query.All<Foo>()
  .Where(a => a.Id == 1)
  .Set(a => a.Bar, a => Query.All<Bar>().Single(b => b.Name == "test"))
  .Update();
is translated to:
UPDATE [dbo].[Foo]
SET [Bar.Id] = (SELECT TOP 1 [a].[Id] FROM [dbo].[Bar] [a] WHERE ([a].[Name] = N'test'))
FROM [dbo].[Foo] AS j0 INNER JOIN (
SELECT
  [a].[Id]
FROM
  [dbo].[Foo] [a]
WHERE
  ([a].[Id] = 1)
) AS j1 ON (j0.[Id] = j1.[Id])
All overloads of Queryable.Single, Queryable.SingleOrDefault, Queryable.First, Queryable.FirstOrDefault methods are supported.
Note, this way always leads to a subquery, so if key of referenced entity is known, it is strongly recommended to use the 1st way.

Constructing update expressions of the fly

Set extension method can be used in scenarios when the update expression is constructed in runtime, for example:
bool condition = CheckCondition();
var query = Query.All()<Bar>
  .Where(a => a.Id == 1)
  .Set(a => a.Count, 2);

if(condition)
  query = query.Set(a => a.Name, a => a.Name + "test");
query.Update();

Updating lots of properties at once

In a scenario when many properties must be updated at once, the alternative version of update extension syntax might be more convenient to use. The main method is called Update instead of Set and it takes a constructor with object initializer as argument:
Query.All<Bar>()
  .Where(a => a.Id == 1)
  .Update(a => new Bar(null) { Count = 2, Name = a.Name + "test", dozens of other properties... });
Note, while you have to populate the constructor with arguments because compiler requires that, it will never be called, only the object initializer will be processed. So it is safe you set fake arguments to the constructor. In all other ways these 2 kinds of syntax are equal, just choose the one that is convenient for you.

Deleting entities

Delete syntax is pretty straightforward, no new rocket science is here. It is almost the same as Update syntax, but use Delete instead.
Query.All<Foo>()
  .Where(a => a.Id == 1)
  .Delete();
is translated to:
DELETE [dbo].[Foo]
FROM [dbo].[Foo] AS j0 INNER JOIN (
SELECT
  [a].[Id]
FROM
  [dbo].[Foo] [a]
WHERE
  ([a].[Id] = 1)
) AS j1 ON (j0.[Id] = j1.[Id])

Known limitations

1. This version supports Microsoft SQL Server only.
2. When updating a reference field, you can't refer to the entity that is being updated, for instance:
Query.All<Foo>()
  .Where(a => a.Id == 1)
  .Set(a => a.Bar, a => Query.All<Bar>().FirstOrDefault(b => b.Name == a.Name))
  .Update();
3. Session.SaveChanges() method is executed before any batch server-side operation. This might lead to validation of entities, depending on validation configuration.
4. Every batch server-side operation cleans Session cache to provide data consistency.
5. Batch server-side operations don't support compiled LINQ queries.

Credits

All credits go to Alexander Ovchinnikov. The extension is a must-have, I'm absolutely sure that it will be very popular among DataObjects.Net community.

Thanks, Alexander!

P.S.
The second part is coming soon. Stay tuned.

Friday, November 18, 2011

Precise control over indexes

This is a detailed description of the index-related features from the upcoming release mentioned in the previous post.

Database indexes were invented to improve the speed of data retrieval operations on a database table. But as always, they have the cost: slower writes and increased storage space. So database architects have to keep the balance between faster data retrieval and slower writes. Ideally, indexes should be applied only on columns that are used in filtering, ordering, etc.

1. Control over indexes on reference fields

Versions of DataObjects.Net prior to the upcoming one were pretty straightforward and applied indexes on every reference property, for example, in this model index would be placed on Owner property automatically.

public class Penguin : Entity {

  [Field]
  public Person Owner { get; set; }
}

DataObjects.Net behaved so in assumption that such indexes would boost performance in queries like:
var PoppersPenguins = session.Query.All<Penguin>().Where(a => a.Owner == MrPopper);

But what if the application doesn't have such queries? And the index that is created and maintained by the database is absolutely useless, moreover, it makes the performance worse? The upcoming version of DataObjects.Net has the answers. Here is what you can do to prevent automatic index creation on a reference field:

public class Penguin : Entity {

  [Field(Indexed = false)]
  public Person Owner { get; set; }
}

2. Control over index clustering

There are 2 main approaches in physical organization of data tables: unordered and ordered. The first one means that the records are stored in the order they are inserted, the second implies that the records are pre-sorted before storing. Both of them have advantages and disadvantages, depending on structure of primary key, usage scenarios, etc. Some database servers supports both cases, others - only one. Microsoft SQL Server supports both: unordered approach is a "heap table (file)" and ordered one is based on "clustered index" when data rows are physically sorted according to position of primary key in internal index structures (B+ tree).

For Microsoft SQL Server DataObjects.Net always chose "clustered index" as an organization of physical storage because this is quite efficient in most cases with only few exceptions: when your primary key is not integer auto-incremented data type but uniqueidentifier or char/varchar, clustered indexes become less efficient on data inserts. Until now there was no way to say that there is no necessity to make primary index clustered, but that has changed: DataObjects.Net provides easy way to control clustering of indexes. Here is how:

// Penguin table should not be clustered
[HierarchyRoot(Clustered = false)]
public class Penguin : Entity {

  [Field]
  public Person Owner { get; set; }
}

Note, removing clustering from hierarchy root you remove it for all its descendants.

Additional feature is the ability to define an index that is not primary and then use it for clustering, like this:

// Person table should be clustered by Name column
[HierarchyRoot]
[Index("Name", Clustered = true)]
public class Person : Entity {

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

This can be done for each class separately except the case when SingleTable inheritance scheme is used. Another obvious restriction is that there can be only one clustered index per table.

3. Support for partial (filtered) indexes

A partial index, also known as filtered index is an index which has some condition applied to it so that it includes a subset of rows in the table. This allows the index to remain small, even though the table may be rather large, and have extreme selectivity.

Our goal was not only to add the support, but to make it easy to use and prevent users from the necessity to write database server-dependent expressions in index definitions. Therefore, we decided to implement the following technique that gives us compile-time validation, type-safety and the power of standard .NET expressions:

[HierarchyRoot
[Index("Owner", Filter = "OwnerIndex")]
public class Penguin : Entity {

    public static Expression<Func<Penguin, bool>> OwnerIndex()
    {
      return p => p.Owner != null;
    }

    [Field]
    public Person Owner { get; set; } 
}

Note that depending on inheritance scheme applied, partial indexes can or can't contain expressions with columns defined in ancestors/descendants.

In case you want to move filter expression to a separate class, you should use the following construct:

[HierarchyRoot]
[Index("Owner", Filter = "PenguinOwnerIndex", FilterType = typeof(FilterExpressions))]
public class Penguin : Entity {

    [Field]
    public Person Owner { get; set; } 
}

public static class FilterExpressions {

    public static Expression<Func<Penguin, bool>> PenguinOwnerIndex()
    {
      return p => p.Owner != null;
    }
}

Partial indexes are supported in Microsoft SQL Server and PostgreSQL providers.

In the next post I'll describe other features of the upcoming release, stay tuned. In the meantime, we are preparing the new builds, installers and stuff.

Monday, November 14, 2011

Upcoming major update

Hello everybody,

Despite the fact that there was no news in this blog from DataObjects.Net team for a couple of months, this doesn't mean that we were having rest or watching endless TV-series. We've been fixing bugs and inventing some really cool features for our customers. Let's take a closer look at the details.

First of all, bugs fixed:
Updates to the existing functionality:
The most interesting part, features:
Simply put, the features are so dramatic that we are releasing the stuff as a major version update. DataObjects.Net 4.3.7 branch will become 4.3.8 and 4.4.0 will be known as 4.4.1 (one of our lead developers call this update as DataObjects.Net R2 and I understand him pretty much). 

While the above mentioned changes are being thoroughly tested under various environments and being prepared for the release, we'll describe the features listed above in a dedicated series of posts.

Thank you! Stay tuned.

Wednesday, August 17, 2011

Update of licensing component in DataObjects.Net 4.5 Beta 4

As you might remember, a year ago DataObjects.Net changed its license and become proprietary. In order to protect the product from being stolen we decided to use Eziriz .NET Reactor library, which fitted rather well to DataObjects.Net distribution model (trial mode → purchase → simple activation via internet). And it seemed smooth and efficient until one nuisance was discovered.

.NET Reactor operates with so-called Hardware Id (HWID) which can be calculated from identifiers of four hardware components: processor, motherboard, network card & hard disk drive. We had decided to choose the first three and unfortunately that was rather short-sighted decision because (it was found out later), .NET Reactor takes into account all network cards, even virtual, so when a DataObjects.Net customer plugged-in his mobile phone or opened a VPN connection, a new network interface appeared, HWID changed and therefore, valid DataObjects.Net license suddenly became invalid.

To sort out the problem we are changing the implementation of licensing scheme in DataObjects.Net 4.5 - network cards identifiers will be taken into account no more. As a result of the algorithm alteration, HWID will change and this will require 2 simple actions: revoking previous HWID license and activating the new one. We understand that this might be a noticeable inconvenience, that's why the measure is applied only to the upcoming version while DataObjects.Net 4.4 stays untouched.

For users of DataObjects.Net 4.4 who regularly face with HWID changes, I recommend the following path:
  • make a backup of your %DataObjects.Net directory%
  • download DataObjects.Net 4.5 Beta 4
  • take Xtensive.LicenseManager.dll & Xtensive.Aspects.Weaver.dll from there and replace the corresponding files in %DataObjects.Net directory%\Bin\Latest, %DataObjects.Net directory%\Bin\Debug & %DataObjects.Net directory%\Bin\Release folders.
Here is how the process of hardware license update looks like:
 1. When using the licensing component from DataObjects.Net 4.5 you might see the following screen:


Note that License Manager will show you the new HWID, which is calculated without data about network cards. This HWID is not yet activated. If you have Ultimate license, go directly to the step 4.

2. Open Explorer and point it to C:\ProgramData\DataObjects.Net folder. Usually this folder stores primary (or company) license (DataObjects.Net.license file) and the hardware one (XXXX-XXXX-XXXX-XXXX-XXXX.hwl file).


Here "3CA3-0442-7A86-4820-423A.hwl" is the file with my previous hardware license. So open your browser and go to My Subscriptions to revoke the old hardware license.

 3. Revoke the outdated hardware license. As you have upgraded to DataObjects.Net 4.5, you wouldn't need the hardware license from the previous version.


4. In License Manager click "Install License...".


Note that primary (company) license is still valid and doesn't require any changes. Choose "Hardware key activation: Internet" and voilà, activation is successful.


DataObjects.Net 4.5 Beta 4 is available for download on the website.

Sunday, July 31, 2011

DataObjects.Net 4.3 & 4.4 are updated to the latest revision

Today both stable versions of DataObjects.Net are updated to the revision 7685. This build includes the extension of Upgrade mechanism that was described in the previous post as well as the following changes:
The updated installers as well as binaries can be downloaded from the official website: dataobjects.net.

Saturday, July 16, 2011

More flexibility to database schema upgrade

In this post I'm going to describe one of the most advanced and appreciated features of DataObjects.Net — automatic database schema upgrade.

You know, automatic database schema generation for Code-First & Model-First ORMs is a vital requirement, however while the task is not trivial by itself, it is not so difficult in comparison with continuous domain model & database schema synchronization. One can imagine all kind of operations on domain model: creating, renaming, moving, splitting, merging, deleting types, fields, associations, field options, index options, validation constraints, etc. All these types of changes must be propagated by ORM to database schema more or less transparently and without any possible loss of data.

In DataObjects.Net this goal is achieved with the help of mature upgrading framework that had been polished for years in version 3.x branch (starting from 2003 year) and then was migrated to version 4.x one with numerous core updates and extensions.

This could be surprising, but the core component of the upgrade framework is not translation of upgrade actions to SQL or making changes to a database schema, but the effective comparison of 2 abstract models. I'm talking about Xtensive.Modelling that was invented for describing and comparing models of any thing. In case of DataObjects.Net it is being used to compare 2 models of a storage.

Here is how the whole process is constructed. We build domain model and convert it to a unified model of storage. In parallel, we extracting metadata about database schema and converting it to the model of existing storage. After that we compare these two and additionally pass to the comparison procedure a set of upgrade hints that are provided by a user. As a result, we get a difference between 2 models and a sequence of upgrade actions that must be executed in order to convert the existing storage to the new one.


Upgrade actions are taken place only when a domain is being built in DomainUpgradeMode.Perform or PerformSafely. These two have one principal distinction: while Perform silently alters whatever is required even with data loss, PerformSafely guarantees that nothing will be removed unless is explicitly declared by a user by using special upgrade hints and attributes. The hints are nonetheless important and useful in both scenarios as they provide additional information for the comparison routine on what changes are made to domain model.

What are these hints?
  • RenameTypeHint — for renaming a persistent type
  • RenameFieldHint — for renaming a persisting field
  • RemoveTypeHint — for removing a persistent type
  • RemoveFieldHint — for removing a persistent field
  • ChangeFieldTypeHint — for a situation when type of a persistent field is changed
  • MoveFieldHint — for moving a persistent field from one type to another
  • CopyFieldHint — for copying a persistent field from one type to another
The hints mechanism is intended to be used in custom implementation of UpgradeHandler class. This can be included into an assembly that is going to be upgraded from version "1.0.0.0" to another one and can look like this:

public class MyUpgradeHandler : UpgradeHandler
{
  public override bool CanUpgradeFrom(string oldVersion)
  {
    return oldVersion == "1.0.0.0";
  }

  protected override void AddUpgradeHints()
  {
    var hintSet = UpgradeContext.Hints;
 
    hintSet.Add(
      new RenameTypeHint("MyProduct.Model.Customer", typeof (Person)));
    hintSet.Add(
      new RenameFieldHint(typeof (Person), "Name", "FullName"));
  }
}
More about the hints and usage examples in our manual.

The upgrade hints infrastructure serves well for the overwhelming majority of scenarios but there are relatively rare situations where a change in domain model can't be described with these hints. We were gathering information and analyzing such cases and afterall, decided to extend the interface of UpgradeHandler class to provide an additional point where the upgrade routine can be corrected by user. On the picture above there is the last point, where upgrade actions are generated based on the storage models comparison result. Until now, these actions were automatically applied to a database schema and there was no way to control this process.

We are adding a method to UpgradeHandler class that exposes a sequence of upgrade actions before thay are applied to database scheme:

public class UpgradeHandler : IUpgradeHandler

    // ...

    public virtual void OnBeforeExecuteActions(UpgradeActionSequence actions)
    {
      // In overridden method actions can be added, edited, removed, etc.
    }
}

UpgradeActionSequence contains all actions that will be played against a database scheme, split in groups. An action is a regular SQL command. In this method any action can be removed, edited, moved from one group to another, new actions can be added to the sequence and so on.


The actions are executed in the following order:

01. NonTransactionalEpilogueCommands
02. Open transaction
03. CleanupDataCommands
04. PreUpgradeCommands
05. UpgradeCommands
06. CopyDataCommands
07. PostCopyDataCommands
08. CleanupCommands
09. Commit transaction
10. NonTransactionalEpilogueCommands

Note the transaction boundaries: all commands except those that don't support transactional execution are run in one transaction so on any error the scheme will stay valid and integral.

The new bits are being tested now and will be available soon as nightly builds. After the thorough testing, the updated DataObjects.Net will be available as usual on our website.

Thanks for your attention.

Sunday, June 26, 2011

DataObjects.Net 4.5 Beta 3

Today DataObjects.Net team is happy to publish the third beta of 4.5 version. This release mostly contains changes in the security system as well as bug fixes merged from the stable branch.

After the Beta 2 was published, we received great feedback concerning the security system and we are very glad for that. Thanks to all of you who dropped us a line concerning this stuff.

Changes from Beta 2

First and the most important one is that roles are made persistent. There were numerous requests for this feature, moreover the idea of Terje Myklebust that he formulated as a templates for roles fits to this pattern very well — a persistent class serves as a template, so you can have as many parameterized instances of it as you need. Here is the updated Role hierarchy:
public interface IRole : IEntity
  {
    [Field]
    string Name { get; }

    [Field]
    [Association(PairTo = "Roles", OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear)]
    EntitySet Principals { get; }

    IList Permissions { get; }
  }

  [Index("Name", Unique = true)]
  public abstract class Role : Entity, IRole
  {
    [NotNullConstraint(Mode = ConstrainMode.OnSetValue)]
    [Field(Length = 128)]
    public string Name { get; protected set; }

    [Field]
    public EntitySet Principals { get; private set; }

    public IList Permissions { get; }

    protected void RegisterPermission(Permission permission);

    protected abstract void RegisterPermissions();

    protected Role(Session session)
      : base(session)
    {
      Name = GetType().Name;
    }
  }
Xtensive.Practices.Security contains the abstract class Role that implements all the required infrastructure for permission registration and handling. Each instance of Role must contain unique name. By default, Role class sets name property to name of the concrete type. In case you want more than one instances of a role of the same type, you must give them unique names. Role and Principal now are connected via paired entitysets. So a principal knows all its roles and vice versa.

Note that the Role class doesn't define hierarchy root and key fields. You must do this manually as you do in case of Principal, for example:

[HierarchyRoot(InheritanceSchema = InheritanceSchema.SingleTable)]
  public abstract class EmployeeRole : Role
  {
    [Field, Key]
    public int Id { get; set; }

    protected override void RegisterPermissions()
    {
      // This is base role for every employee

      // All employees can see products
      RegisterPermission(new Permission());
      // All employees can see employees
      RegisterPermission(new Permission());
    }

    protected EmployeeRole(Session session)
      : base(session)
    {
    }
  }
All other roles inherit EmployeeRole. Note that in case of roles the SingleTable inheritance scheme is the best option as all role types are similar and can be effectively placed inside a single table.

Other changes:

Session.ValidatePrincipal method is renamed to Session.Authenticate.
IPrincipalValidationService is renamed to IAuthenticationService
GenericPrincipalValidationService is renamed to GenericAuthenticationService
GenericRoleProvider, PrincipalRole, PrincipalRoleSet, RoleSet are discarded as useless.

Roles as templates

There was a fruitful discussion after the previous post and the idea of role templates was suggested. Here is how it can be implemented in the Beta 3. I'll take the same scenario with branches and office manager roles.

Fisrt, let's define branches
[HierarchyRoot]
  public class Branch : Entity
  {
    [Field, Key]
    public int Id { get; private set; }

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

    public Branch(Session session)
      : base(session)
    {}
  }
And then, define a branch office manager role
public class BranchOfficeManagerRole : EmployeeRole
  {
    [Field]
    public Branch Branch { get; set; }

    private IQueryable GetCustomers(ImpersonationContext context, QueryEndpoint query)
    {
      return query.All()
        .Where(c => c.Branch == Branch);
    }

    protected override void RegisterPermissions()
    {
      RegisterPermission(new Permission(true, GetCustomers));
    }

    public BranchOfficeManagerRole(Session session, Branch branch)
      : base(session)
    {
      Branch = branch;
      Name = branch.Name + "OfficeManager";
    }
  }

See, the role class is not tied to a concrete branch office instance, it only declares a connection between a role type and a branch type instead. The concrete Branch instance is passed in the constructor. Also note how Name property is formed in the constructor to avoid non-uniqueness.

Having these classes declared we can easily use them in creating the appropriate role instances as many as we need. Moreover, we can create branches and branch-dependent roles in runtime.
// Branches
var southBranch = new Branch(session) { Name = "South"};
var northBranch = new Branch(session) { Name = "North"};

// Roles
var southBranchOfficeManagerRole =  new BranchOfficeManagerRole(session, southBranch);
var northBranchOfficeManagerRole =  new BranchOfficeManagerRole(session, northBranch);

// Employees
var user1 = new Employee(session);
user1.Roles.Add(southBranchOfficeManagerRole);

var user2 = new Employee(session);
user2.Roles.Add(northBranchOfficeManagerRole);

// By adding both roles to an employee we can give him a superset of permissions
var user3 = new Employee(session);
user3.Roles.Add(southBranchOfficeManagerRole);
user3.Roles.Add(northBranchOfficeManagerRole);

Download

DataObjects.Net 4.5 Beta 3 can be downloaded from the website.

P.S.
Dear DataObjects.Net users and contributors. The Beta 3 is very close to the Release Candidate. But before the main branch is frozen, I want to clarify
a question: should permissions be persistent or not? Is it vital? If yes, how filter expressions should be stored?

If the question is given a sensible answer, we'll continue implementing persistent permissions and make Beta 4; otherwise we will proceed to RC 1.

Thank you.

P.P.S.
Dear Malisa Ncube,
It seems that after these alterations you'll have to update the SalesPoint database creation script for MySQL. Sorry for that, hope this won't tale too much time.

Wednesday, June 22, 2011

DataObjects.Net on Twitter

Hello everybody,

Finally DataObjects.Net reached Twitter. =)
Follow us for the latest news and updates.