Unique features summary

The following features are unique to DataObjects.Net – this means that we didn’t find these features implemented in other ORM tools available for the .NET platform. You can find a comprehensive description of each feature by clicking on it. We organized this list by an importance of each feature (of course, by our opinion).

There are some less important unique features including support of business services (DataServices), ability to automatically upgrade the database schema… You can find them in the “Introduction” section of DataObjects.Net Manual.

Reduce the data access and business layer development time by up to 80%

DataObjects.Net allows to start database application development directly from its business model. Forget to think of tables, columns and constraints – just focus on writing the business code. Never again you have to deal with table mapping and database design:

  • DataObjects.Net generates normalized database schema for your business objects and automatically updates this schema in accordance with the model (unless you disable this functionality).
  • Introduces universal instance identification and versioning.
  • Provides more then 20 attributes for manipulating indexes, column types and length.
  • No additional files required for database structure definition. The classes declaration with attributes are the only information sources.
  • Database structure (generated tables and views) is clear and convenient for use with external application.

Note: it’s much easier to change or extend the persistent objects model only rather then to change both persistent objects model and database schema!

Additional benefits of using persistent objects rather then standard relational database access methods (for example, ADO.NET):

  • Intellisense, Visual Studio.NET’s code completion feature, saves a lot of programming time. Any persistent type is a .NET class, so it is completely recognized by Intellisense.
  • You can traverse an object graph by a standard manner: myHouse.Doors[1].Lock.Unlock(someKeyObject).
  • Use ASP.NET\WindowsForms.NET data binding to display or modify your persistent objects.
  • Forget about calling a Save\Load-like methods – DataObjects.Net handles such tasks completely transparently making you fell you’re working with ordinary object instances. Transparent persistence has similar benefits as automatic garbage collection – you shouldn’t worry about persisting your changes.

Note: this doesn’t mean all changes are persisted immediately – DataObjects.Net optimizes the update sequence.

Rely on fast and stable relational database platforms

DataObjects.Net supports:

Notes:

  • DataObjects.Net is fully compatible with .NET Framework 2.0, moreover, it fully supports some of its essential features (generics, nullable types)
  • We’ll definitely support other important features of new platform in the near future
  • Mono support is currently in beta stage

You can target any supported database server \ platform without having to make any modifications to your code.

Get performance comparable with standard database access methods

DataObjects.Net is designed to provide the similar performance in comparison with standard database access methods – normally its application operates with C = 70…50% performance of application having similar functionality and accessing the database directly. We’ve made a lot to make C independent on, for example, count of queried instances (and of other similar parameters), and continue making DataObjects.Net faster.

Currently DataObjects.Net utilizes following performance-related features:

  • Caching speeds up queries and access to referenced instances by reusing already fetched data. There are two-level caching in DataObjects.Net:
    • Global cache is shared between all Sessions operating in the same Domain (usually there is one Domain object per application except NLB clustering case). Global cache has fixed size, and contains instantiation data for a set of most frequently accessed objects. The information fetched from this cache is always validated – i.e. DataObjects.Net uses it only when it really actual. Usually validation requires much less time then fetching, and moreover, DataObjects.Net gathers validation data on any query (since it’s also quite cheap) – e.g. running a query with LoadOnDemand option (such queries are quite fast, since they fetch only two numeric columns from the storage) may lead to zero subsequent fetches (queries), because all necessary data could be taken from global cache, as well as validated.
    • Session cache is a WeakReference-based storage caching already instantiated objects, as well as information necessary to instantiate them or validate the instantiation data cached in the global cache. For example, if you execute two subsequent queries and process two object sets returned by these queries, none of processed objects with the same ID will be instantiated twice, moreover, instantiation data necessary to create it won’t be even fetched twice from the underlying IDataReaders (certainly if these operations are executed in the same transaction).
    • Two-level caching layer is only a part of caching techniques used in DataObjects.Net. Lot of data \ intermediates are cached internally – for example, all evaluated effective permission sets and effective user’s role sets are cached by the security system.
  • Delayed updates: almost all types of updates are delayed by default and flushed as late as it’s possible. Late update sequence is normally executed via much less number of queries.
  • Lazy instantiation: DataObjects.Net instantiates (creates in-memory object and fetches its state date from the database) referenced objects on the first attempt to access them.
  • Lazy loading (or load-on-demand): [LoadOnDemand] attribute applied on the persistent property notifies DataObjects.Net that value of this property should be fetched from the database on the first attempt to access it. This feature is highly required while working with instances containing large amount of rarely accessed data (e.g. BLOBs). The similar behavior is available for queries – it’s possible to specify that QueryResult should internally contain only IDs of selected objects rather then complete instantiation data, but nevertheless it will transparently transform these IDs to the DataObject instances by performing additional queries. This feature helps to keep very large result sets in memory.
  • DataObjects.Net does not use high-level ADO.NET classes (for example, DataSet and DataTable aren’t used).

The upper features are only a visible set of performance-related features – for example, DataObjects.Net caches all evaluated effective permission sets and effective user’s role sets are cached by the security system.

Implement truly object-oriented data access and business tier

Use inheritance:

  • Inheritance is fully supported, even in queries. Any query selecting objects of a particular type returns descendants of this type also – for example, if you declared some persistent class Animal with a single property LegCount, extended it in the Cat and Dog classes (Animal descendants), and executed a query “Select Animal objects where {LegCount}=4”, you’ll get all Animal, Cat and Dog objects having four legs.
    Note: support of inheritance without its similar support in queries is actually almost unusable – inheritance is widely used even when the number of types is small (for example, a model of 50 persistent types is most likely will contain at least 10 types having some descendants). Most of available ORM tools don’t support this type of querying, or have a significant performance drawback on such queries.
  • Interfaces are fully supported – you can select not only the persistent types in your queries, but persistent interfaces also. The same example can be slightly modified – let’s think Animal class implements ILeggedEntity. ILeggedEntity is the persistent interface declaring only one persistent property: LegCount. So you can execute a query “Select ILeggedEntity objects where {LegCount}=4” to achieve the same effect as in previous case. Persistent interfaces bring the same benefits as standard interfaces plus provide an opportunity to use them in queries additionally.

Use references and collections:

  • Relationships between classes are completely supported and handled transparently. You can use the notation like: someAnimal.Children[0].Children[1].Mother.LegCount = 4;
  • Object associations (one-to-one, one-to-many, many-to-many) and aggregation are completely supported.
  • Declarative syntax for paired relations is supported (see [PairTo] and [Symmetric] attributes).

Use structs (ValueTypes) and ValueTypeCollections:

  • DataObjects.Net is aware of struct types – it persists them as set of columns (one per each field of struct). Any DataObjects.Net-supported type can be used in structs, including reference type. DataObjects.Net tracks all references stored in struct types in the same manner as other references – e.g. it notifies other objects when they are referenced from reference property stored in struct field, automatically resets references to removed instances stored in structs, etc… Collections of structs are also supported.

Use events:

  • Events provide complete awareness of all operations on persistent object – for example, any persistent instance can react on its creation, deletion or modification by re-computing some calculated value or demanding some security permission before the action.
  • Events, for example, allow to maintain calculated or aggregated properties (e.g. a total number of children, grandchildren and so on – for any Animal object).

Utilize built-in Access Control System

Almost any database application has some access control system. In relatively easy cases it can be implemented as part of the application, or a built-in SQL server access control system can be used… But even in this case requirements to such a system tend to grow in future. We understand the genericity of this problem – its solution was introduced in DataObjects.Net 2.0.

We bundled into the DataObjects.Net a truly generic security system supporting Permissions, Users, Roles, per-instance Access Control Lists, permission inheritance, authentication and authorization. It can be compared with NTFS – the difference is that it allows to control any operations with persistent objects, and it’s completely extendable by its nature – for example, Permissions, Users and Roles can be extended by your custom types, you can implement your custom authentication schemas and so on.

One of the most important features of this system is its performance: its presence has almost zero effect (less then 10%) on overall application performance!

Currently we don’t know any ORM tool for the .NET platform having even a closely looking feature.

Operate in the completely transactional environment

  • Classical transaction architecture is supported. You can begin, commit and rollback a Transaction, use Savepoints. Nested transactions are fully supported.
  • Built-in transactional services allows to almost forget that your persistent objects or business services operate in the concurrent transactional environment – they intercept calls of your business tier methods and wraps them into transactions (outermost or nested) providing that if exception or deadlock occurs, no data will be changed.
  • Transactional services are capable to re-process method call in case of deadlock exception.
  • Participation in distributed transactions is supported.
  • Optimistic concurrency is provided for updates (“first in wins” rule) on Read Committed isolation level or for a multi-transactional data modifications.

Note: this behavior is provided completely transparently for the developer, but nevertheless it’s highly configurable.

Query the database by an object-oriented way

Use built-in OQL-like query language to query the database. For example, you can execute following queries:

  • Select Category objects where {ID}=15
  • Select Product objects where {Category.Name} like 'Dog%'
  • Select Product objects where 'Adult Male' = any {Items.item.Name}
  • Select Category objects order by {Products.Count} desc
  • Select Category objects where exists {Products[ {Name}='Poodle' ]}
  • Select Category objects where {Products.Expr[ avg(len({Name})) ]}>7
  • Select Article instances inner join $root.Author[{Name} like 'Tyler%']
  • Select Author instances left join $root.Articles $A order by {$A.Date}
  • Select Article instances where {(Article)SeeAlso[{Date}<@Date].count}>0
  • Select (Article)Article.SeeAlso instances
  • Select Article.Comments values where 'Tyler Durden' = any{Author.Name}
  • Select distinct Article instances inner join $root.Author[{Name} like 'Tyler%']

Use built-in full-text indexing and search features

DataObjects.Net provides set of base persistent classes and interfaces allowing to collect and maintain a full-text data related to any persistent object, and supports these types by the query syntax.

An example of full-text search query:

  • Select Author objects
    where {Name}>='Piter' and {Name}<='Ronald'
    textsearch top 100 freetext 'Jungle Book'
    order by {FullTextRank} desc.

Note: DataObjects.Net doesn’t execute full-text indexing and search completely by itself, it relies upon built-in SQL server features (Microsoft Search is used while running on SQL Server) to perform this task.

DataObjects.Net provides built-in managed wrapper for Microsoft Index Service filters allowing to index almost any document\file type stored on the database server (or externally). In particular you can index the following document types: Microsoft Office files (.doc, .dot, .rtf, .xls, .ppt, etc…), HTML files (.htm, .html), Adobe PDF files, etc.

Use multilinguality in your databases

  • DataObjects.Net fully supports UNICODE encoding;
  • Collations support allows to use different sorting rules for different types of string properties;
  • Translatable properties allow to store independent versions of property value for each Culture registered in the Domain (Domain is the object-oriented model of the database).

These features can significantly help to develop, for example, multilingual web applications.

Utilize versionizing

Versionizing mode provides an ability to “see” the database at any previous state (at any point in time). This mode works only if it’s turned on for the whole Domain.

Bind your entities to WindowsForms\ASP.NET controls

DataObjects.Net fully supports WindowsForms databinding

DataObjects.Net fully supports WindowsForms\ASP.NET databinding:

  • ASP.NET databinding is supported for both regular and Offline persistent entities (more information about Offline entities is provided below)
  • WindowsForms databinding is fully supported for Offline entities (WindowsForms applications require Offline layer in almost any case)

In addition, DataObjects.Net provides BindingManager component – a universal tool for binding DataObject instances to ASP.NET\WindowsForms controls in a bit different fashion in comparison to regular databinding. Its features:

  • Brings Property-PropertyEditor bindings in contrast to common Property-ControlProperty bindings. In lots of cases this is really more convenient
  • Brings two-way bindings to ASP.NET
  • Binds controls not to only DataObjects.Net types, but to any object graphs, but supports all important DataObjects.Net-specific features – access to persistent properties via GetProperty\SetProperty methods, [Translatable] properties, automatic transactions
  • Utilizes DataBinder.Eval-like paths and supports even more complex binding expressions
  • Supports advanced error reporting via IErrorInfoControls:
    • IPropertyErrorInfoControls show property-related errors on updates
    • IFormErrorInfoControls show combined error reports for the whole update
  • Relies on binding extenders (IBindingExtenders):
    • Common property-control binding behavior provided DefaultExtender
    • VersionCodeValidator extender provides VersionCode validation (automatically detects concurrent updates and shows “Object was modified by another user” errors)
    • Custom (user-defined, or third party) extenders are supported

Import or export your persistent objects using built-in serialization support and Adapter component

Serialization is usually required to implement:

  • Import\export features (Save\Open-like operations);
  • Cut\Copy\Paste operations (Undo\Redo operations are also possible candidates);
  • Backup\Restore operations.

DataObjects.Net provides excellent serialization layer implemented as an extension over standard .NET serialization mechanisms.

  • Use Binary, SOAP or custom formatters to serialize or deserialize your persistent objects;
  • Serialize an object graph containing both persistent and non-persistent objects;
  • Use set of pre-defined serialization scenarios: specified objects only, all reachable objects, specified + contained objects.
  • Two ways of persistent object serialization are available: standard (when all object properties are serialized) and “as reference” (only an ID of instance is serialized). The second way is useful when it’s necessary to serialize some object set while restoring all of its references to objects lying out of this set is required.
  • Serialization\deserialization is fully integrated with built-in security system – you can be sure this part is security-safe.
  • Additionally DataObjects.Net provides Adapter component allowing to export graphs of persistent entities graphs into DataSets and importing back the changes.

Implement truly n-tier solutions

Any persistent object or business service can be marshaled to another application domain via .NET Remoting (as well as all other DataObjects.Net-related objects, e.g. Query). This means that you can access your data and business tier from a completely different network or across the Internet with almost no additional code.

DataObjects.Net supports two marshaling models:

DoPetShop RemotingClient Screenshot
  • Marshal-by-reference access: all persistent entities\services, as well as most part of other built-in classes are MarshalByRefObject descendants, so they’re fully ready for this type of remote access. This means that your BLL and DAL tiers can be located on remote application server, and accessed by the client-side code (usually – UI tier) via .NET Remoting.
  • Marshal-by-value access using built-in implementation of Data Transfer Object (DTO) pattern: DataObjects.Net provides Offline layer (a set of classes from DataObjects.Net.Offline namespace) allowing to produce MBV copies of server-side entities, and marshal them through AppDomain boundaries inside disconnected ObjectSets (by value). ObjectSets are very similar to DataSets by marshalling behavior, but contains copies of server-side entities rather then tables and rows. This approach allows to marshal large sets of objects between different tiers “at once” (in a single remote call), that is quite important for e.g. WindowsForms applications – usually they should provide fast access to small part of the storage (a set of visible\accessed objects). Next section provides more information about this feature.

Get benefits of advanced implementation of Data Transfer Object (DTO) pattern

Notes:

  • Please read this chapter- the information provided here is really important, especially for WindowsForms application developers.
  • Currently only few ORM tools for the .NET platform provide some kind of support for this feature, although having it is quite important for implementing n-tier solutions. Because of that almost all of them provide the only one opportunity: to execute all BLL code on the client side – in fact, they don’t allow you to utilize a middle-tier server. To check this, just ask a question: “Using this ORM tool is it a strong requirement that e.g. WindowsForms client should be able to establish a direct connection to RDBMS server to use persistent entities provided by ORM tool, or it may connect to some middle-tier server and use it as service providing such entities, as well as allowing to apply the changes made to them?”

DTO pattern

DTO pattern came from EJB (Enterprise Java Beans) – EntityBeans are very similar to DataObject instances:

  • They represent the state of persistent entities
  • They’re also MBR objects (remote clients access them by reference)
  • Most of their methods are transactional.

DTO pattern is invented mainly to reduce the number of remote calls made to the application server, in particular on UI operations with persistent entities (actual set of problems it resolves is wider – please refer to corresponding section of DataObjects.Net Manual for details).

Key requirements to any DTO pattern implementation:

  • Client should be able to request a set of marshal-by-value objects (Data Transfer Objects, DTOs) necessary for some fully client-side operation (e.g. editing in grid) from the application server
  • Application server should be able to import to import back the changes made to the DTOs

Brief description of usual DTO parent implementation:

  • DTO type is associated with each type of persistent business entity
  • DTO types are serializable marshal-by-value types
  • Each DTO holds values of all properties of corresponding persistent entity
  • A set of DTOs (DTO graph) form a kind of “snapshot” of a part of the storage
  • DTO graphs are built on the application server and sent to the client in the serialized form (actual transport layer isn’t important)
  • Client may modify DTOs and send the modified graph back. In this case application server detects the changes and applies them to corresponding persistent entities. The way of doing this is also not important, but one of commonly used ways of doing this is comparison of original and new graphs.

Weaknesses of usual DTO pattern implementation:

  • There is no possibility to explicitly say which property values should be available (exported) in each particular DTO graph (i.e. all properties of server-side object are “exported” into its DTO copy). E.g. it’s quite inefficient to always send large BLOB values to the client, even if it’s known that they aren’t required for the current client-side operation.
  • There is no possibility to transparently “download” the data that isn’t available in the DTO graph from the application server. This feature is quite useful e.g. for downloading mentioned infrequently accessed BLOB values.
  • Sending back the whole DTO graph on update is also inefficient – usually relatively large graphs are delivered to the clients, but most of clients perform only few or even no changes at all. It’s better to send back the changes only.
  • Finally, it’s desirable to have a “todo queue” allowing to schedule an execution of generally any operation on the server

DTO pattern implementation in DataObjects.Net

DataObjects.Net provides an advanced implementation of DTO pattern. Its key types:

  • Offline.ObjectSet: represents a single graph of Offline.DataObject instances. Very similar to Session, but:
    • It doesn’t have WeakReference-based cache – it maintains strong references to all instances that was fetched into it or created in it
    • It doesn’t need a connection to the database and opened transaction to operate – it provides access to non-transactional data, and state of data it keeps is determinable without any transactions
    • Nevertheless it has Session property: if set, this Session is used to fetch the data “on demand”, as well as for applying the changes when ObjectSet.ApplyChanges method is invoked (actually all these tasks are handled by special DataServices that becomes available via this Session). So this Session is the only “gateway” to the application server for the ObjectSet.
  • Offline.DataObject: DTO for “online” DataObject instance
    • Offline DataObjects also have proxies (currently they provide support for client-side transactions)
    • Each “online” type is “mapped” to its own “offline” type by the following rule:
      Namespace.TypeName -> Namespace.Offline.TypeName
    • Offline types should have the same structure as their online analogues, but types of such properties as DataObjectCollection should also be substituted to corresponding offline types
    • It isn’t necessary to declare offline analogues for all online types – if no offline type is found for a particular online type, it will be converted to nearest base type of necessary offline type. All properties will be anyway available via GetProperty\SetProperty methods.
  • Offline.DataObjectCollection, Offline.ValueTypeCollection, Offline.QueryResult, etc.: offline analogues of corresponding online types.
  • FillDescriptors provide a way to specify what exactly should be “exported” on each particular ToOffline(…) operation
  • During the whole lifetime ObjectSet gathers information about all invocations of offline methods marked by [OfflineBusinessMethod] attribute.
    • This information is stored in MethodCallDescriptor objects. It’s forwarded by ObjectSet to the application server when its ApplyChanges method is invoked, where completely the same sequence of method calls is executed, but on online objects
    • Offline objects passed as method call arguments are certainly properly converted to corresponding online objects
    • By doing this, we reproduce the whole sequence of updates made to the client-side ObjectSet on actual business entities “living” on application server – so our BLL code still works only on the server side (offline entities only simulate the activity of server-side objects)
    • All changes made to online objects during ApplyChanges operation are transparently propagated to the offline entities on its completion!

This approach provides usual DTO pattern implementation:

  • All types including ObjectSet are serializable non-MBR (MBV) types, so it can freely traverse AppDomain boundaries in serialized form
  • Fill: “online” types provide ToOffline(…) methods producing their “offline” analogues
  • Update: ObjectSet.ApplyChanges method sends all changes to the Session object it is bound to

Moreover, we’ve resolved most annoying disadvantages of it:

  • FillDescriptors provide a way to explicitly say what types and properties should be exported into the ObjectSet on each ToOffline(…) operation
  • ObjectSet supports transparent downloading of required, but not available content (unavailable instances, property values and collection content). Since such operations are performed in different transactions, VersionID\VersionCode checks can be enforced to ensure the integrity of the ObjectSet after such operation. See LoadOptions enumeration for details.
  • As it was mentioned, ObjectSet uses MethodCallDescriptors to describe the updates made to it. It constantly tracks the changes made to it, and populates MethodCallDescriptor objects. When ApplyChanges method is invoked, only a collection of MethodCallDescriptors is sent to the special DataService on the application server, which “applies” each MethodCallDescriptor by executing the same method, but on the corresponding “online” object (method call arguments are certainly properly converted). See UpdateOptions enumeration for additional information.

And finally, ObjectSets bring some other interesting features:

  • Excellent Savepoint support
    • Multiple savepoints are supported
    • Each Savepoint internally maintains its own undo log and MethodCallDescriptors list
    • Savepoint can be either rolled back, or removed. In the last case its undo log and MethodCallDescriptors collection are merged with the previously created Savepoint
    • There is a SavepointController object, which purpose is very similar to TransactionController in the “online” layer – all base operations “require” a Savepoint, thus an exception can’t lead to impossibility to use the ObjectSet (e.g. because this exception was thrown in the middle of operation – when a part of ObjectSet contains inconsistent data). ObjectSet-level transaction will be simply rolled back in this case, and it will revert it to its previous consistent state
  • Merge support
    • Any number of ObjectSets can be merged into one
    • VersionID\VersionCode validation can be enforced during this operation, see MergeOptions for details
    • Lots of built-in operations in the ObjectSet (such as transparent content downloading) internally use Merge method
  • TrackingSet is used on ApplyChanges execution
    • A server-side service that actually executes all MethodCallDescriptors on the application server internally uses TrackingSet to additionally gather the information about all modified objects. Finally it delivers all changes back to the ObjectSet to make it being properly updated.
  • WindowsForms databinding support
    • DataObject type supports IEditableObject and IListSource interfaces
    • DataObjectCollection type supports IBindingList, ITypedList and IListSource interfaces

Study the product faster – samples for quick start are bundled into the installation package

There are 14+ sample applications shipped with DataObjects.Net. The most interesting sample DoPetShop is real-world application demonstrating the best practices. DoPetShop is a DataObjects.Net-based clone of Microsoft .NET Pet Shop sample.

DataObjects.Net PetShop Screenshot

Comparison facts:

  • DoPetShop contains only 51 Kb of data and business tier code while .NET Pet Shop – 140 Kb (including Business Logic Layer, Model, DAL and two its implementations – SQL Server DAL and Oracle DAL, but without BLL\OrderInsert.cs – read further about this). This means that use of DataObjects.Net reduced business and data tier code size by 3 times!
  • Moreover, it includes administration module. First DoPetShop version (that was very close to the original .NET Pet Shop by the feature set) was much smaller – its data and business tier code size was less then 20 Kb, so it was nearly 7 times smaller then its original!
  • DoPetShop utilizes DataObjects.Net access control system – this means that DataObjects.Net takes complete care about the authentication and authorizes access to application’s business objects
  • DataObjects.Net brings true full-text search to DoPetShop, while .NET Pet Shop always uses like to locate necessary products
  • DoPetShop fully supports 6 database server platforms (Microsoft SQL Server 2000 \ 2005, MSDE 2000, Microsoft Access, Oracle, Firebird, SAP DB \ MaxDB) while .NET Pet Shop – only SQL Server 2000 \ MSDE and Oracle
  • DoPetShop provides full, but safe access to its data and business tier via .NET Remoting (while .NET Pet Shop allows to perform only one simple operation via its web service), so generally you can perform any operations remotely, including any manipulations with persistent objects (creation, changing, deletion) – all depends on your security permissions.
  • There are some other benefits of the DoPetShop – for example, it provides more information on the cart and order-related pages and it has much more extendable architecture.