 |
Unique features summary
The following features are unique to DataObjects.Net – this means that we didnt 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: its 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.NETs 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 youre working
with ordinary object instances. Transparent persistence has similar benefits as automatic
garbage collection – you shouldnt worry about persisting your changes.
Note: this doesnt 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)
- Well 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. Weve 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 its 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 wont 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 users role sets are cached by the security system.
- Delayed updates: almost all types of updates are delayed by default and flushed as late
as its 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 – its 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
arent 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 users 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, youll 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
dont 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 –
lets 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 its 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 dont 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 its 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 doesnt 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 its turned on for the whole Domain.
Bind your entities to WindowsForms\ASP.NET controls
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 its 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:
- Marshal-by-reference access: all persistent entities\services, as well as most part
of other built-in classes are MarshalByRefObject descendants,
so theyre 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 dont 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
- Theyre 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 isnt 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. its quite
inefficient to always send large BLOB values to the client, even if its known
that they arent required for the current client-side operation.
- There is no possibility to transparently download the data
that isnt 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. Its better to send back the changes only.
- Finally, its 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 doesnt have WeakReference-based cache –
it maintains strong references to all instances that was fetched into
it or created in it
- It doesnt 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 isnt 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.
Its 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, weve 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 cant 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.
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 applications 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.
|
|