move to the future. think objects, not sql. © 2005 x-tensive.com
TRANSCRIPT
Move to the future.Think Objects, not SQL.
Move to the future.Think Objects, not SQL.
© 2005 X-tensive.com
http://www.x-tensive.com/Products/DataObjects.NET/http://www.x-tensive.com/Forum/
Important notesImportant notes
• Be ready to spend 2-4 hours on this presentation – it’s large (~ 140 slides). But you’ll get the imagination about almost all features of DataObjects.NET after watching it
• Feel free to skip any “schema slide”, if you don’t understand it. You may return back to it after studying the code example (they’re usually located in the end of each section)
• All UML-like diagrams aren’t actually UML diagrams – by the following reasons:– Regular tools generate quite “overflooded” UML diagrams for
DataObjects.NET (too many additional classes and properties – look e.g. on .HxS/.Chm help), so it was necessary to manually “draw” all of them
– PowerPoint isn’t a good tool for authoring UML – it doesn’t provide a comfortable way to use necessary arrow types, boxes and so on
– So we decided to use the notation that:• Is still understandable and convenient (UML-like)• Can be easily authored in PowerPoint
• It’s a good idea to have DataObjects.NET Manual, Samples and .HxS/.Chm Help somewhere nearby
Presentation planPresentation plan
• Example• Architecture overview• Application layout examples• Features overview
– Low-level \ persistence features– Integrated features– Add-ons
Example
(from Demo_FirstStep)
DataObjects.NET-based application develpment: key stepsDataObjects.NET-based application develpment: key steps
• Declare a persistent type(s)• Write Domain build code• Use your persistent types
Step 1: persistent entity code
// This is our first persistent class.// Don't worry about abstract modifiers –// DataObjects.NET will automatically create // a descendant of this class called "proxy" // class that will implement the abstract member. // This class will be created transparently // for you in the runtime // (to be exact - during the execution of the// Domain.Build(...) method).public abstract class Animal: DataObject { // A single persistent property public abstract int Age {get; set;}}
Step 2: Domain build code
domain = new Domain( "mssql://localhost/DataObjectsDotNetDemos", productKey);// At least one culture should be registereddomain.RegisterCulture( new Culture("En","U.S. English", new CultureInfo("en-us",false)));domain.Cultures["En"].Default = true; domain.RegisterTypes("Demo_FirstStep");domain.Build(DomainUpdateMode.Recreate);
Step 3: persistent entity usage, part 1
long instanceID = 0;// You should work with Session by the same wayusing (Session session = new Session(domain)) { session.BeginTransaction(); // Let’s create our first persistent instance Animal a = (Animal)session.CreateObject(typeof(Animal)); // Instance is already persisted to the storage now // Let's set the first persistent property a.Age = 2; // And read the instance ID instanceID = a.ID; // And finally commit our work session.Commit();}
Step 3: persistent enrtity usage, part 2
using (Session session = new Session(domain)) { session.BeginTransaction(); // Fetching the instance by its ID Animal a = (Animal)session[instanceID]; Console.WriteLine("Age: {0}", a.Age); // Output: 2 Console.WriteLine("Creating savepoint..."); Savepoint sp = new Savepoint(session); Console.WriteLine("Savepoint created."); a.Age = 3; Console.WriteLine("Age: {0}", a.Age); // Output: 3 Console.WriteLine(
"Rolling back to the savepoint..."); sp.Rollback(); Console.WriteLine("Rollback completed."); Console.WriteLine("Age: {0}", a.Age); // Output: 2 session.Commit();}
Architecture Overview
DataObjects.NET-based application layersDataObjects.NET-based application layers
DataSetsDataSets
Client connection
ways
Application
Server
RDBMS Server
DataObjects.NET (DAL)DataObjects.NET (DAL)
DataObjects.NET RDBMS driverDataObjects.NET RDBMS driver
RDBMS
WebServices, Remoting(MarshalByValue)
Remoting(MarshalByRef)
ObjectSetsObjectSets
DataObjects, DataServices (BLL) DataObjects, DataServices (BLL)
Offline LayerOffline LayerAdapterAdapter
DataObjects.NET structureDataObjects.NET structure
Drivers
MS SQLMS SQL
OracleOracle
FirebirdFirebird
MaxDB/SAPDBMaxDB/SAPDB
Native OracleNative Oracle
Versionizing
Full-text search
Versionizing
Full-text search
Persistence Engine
PersisterPersisterPersisterForCollectionsPersisterForCollections
ModelsDatabaseModelDatabaseModel ObjectModelObjectModel
UpdateActionsUpdateActions
Schema Update Layer
ExtractorExtractor
DatabaseModelComparerDatabaseModelComparer
Builders
ObjectModelBuilderObjectModelBuilder
DatabaseModelBuilderDatabaseModelBuilder
ProxyBuilderProxyBuilder
Drivers Layer RDBMS Abstraction Layer
DAL (DataObject,…) Core
Integrated Features
Build-tim
e onlyR
ead-only after buildR
untime
Offlin
eO
ffline
Aa
pte
rA
ap
ter
Bin
din
g
Ma
na
ger
Bin
din
g
Ma
na
ger
Ad
d-o
ns
Low-level Features
VersionizingVersionizing
PreloadingPreloading
CachingCaching
UpdateActionTranslatorUpdateActionTranslator
NamingManagerNamingManager
……
SerializationSerialization
Full-text searchFull-text search
NTFS-like security NTFS-like security
……
Domain build process schemaDomain build process schema
Proxy AssemblyProxy Assembly
Extractor
RDBMS
ObjectModelBuilder
DatabaseModelBuilder
ProxyBuilder
UpdateActionsUpdateActionsSchema Upgrade
SQL Script
Schema Upgrade
SQL Script
UpdateActionsTranslator
DatabaseModelDatabaseModel
DatabaseModelComparer
ExtractedDatabaseModelExtractedDatabaseModel
ObjectModelObjectModel
.NET Reflection-provided model.NET Reflection-provided model
RDBMS Abstraction Layer: types schemaRDBMS Abstraction Layer: types schema
SessionSession
DomainDomain
ConnectionUrl
Driver
…
*…1
SessionBoundObjectSessionBoundObject AA
+Session
DriverDriver AA
Creates
1…1
PersisterForCollectionsPersisterForCollections AAPersisterPersister AA
1…1
1…**…1
InfoInfo AA
UtilsUtils AA
NamingManagerNamingManager AA
UpdateAction TranslatorUpdateAction Translator AA
1…*
Core types: descriptionCore types: description
• Domain– Root object providing access for the whole object storage– Contains database connection URL & other configuration options, all
models, driver, etc.– Provides persistent type \ DataService registration methods (see
RegisterXXX methods)– Provides Build method– Allows to create Session objects
• Session– IDbConnection analogue– Allows to create \ access (fetch) DataObjects– Allows to create other SessionBoundObjects, such as DataService,
Transaction, Query• DataObject
– Abstract base class for any persistent entity– Its descendants are business objects: their transactional methods form
BLL– Its persistent properties and their attributes serve as persistence
descriptors for DAL (DataObjects.NET)
Core types: schemaCore types: schema
+User
…
+ID
+VersionID
+TypeID
+Permissions
+State
…
AADataObjectDataObject
DomainDomain
ID
VersionID
TypeID
…
IDataObjectIDataObject
+Session
SessionBoundObjectSessionBoundObject AA
MarshalByRefObjectMarshalByRefObject AA
SessionSession
1…*
*…1
To create Domain instance:
Domain domain = new Domain(…);
domain.Build(…);
To create Session instance:
domain.CreateSession(…);
[or]
Session s = new Session(domain, …)
To create DataObject instance:
DataObject o = sesion.CreateObject(
typeof(MyType)); // New
[or]
DataObject o = session[id]; // Fetch
#DisableSecurity()
#EnableSecuriry()
Persistent types and their proxiesPersistent types and their proxies
+ID
+VersionID
…
DataObjectDataObject
Cat_ProxyCat_Proxy
Animal_ProxyAnimal_Proxy
AA
+Age
+LegCount
AnimalAnimal AA
+Name
HomeAnimalHomeAnimal AA
+Color
CatCat AA
+Session
+Domain
SessionBoundObjectSessionBoundObject AA
#DisableSecurity()
#EnableSecuriry()
MarshalByRefObjectMarshalByRefObject AA
You always work with instances of
these types.
DataObjects.NET builds them during
Domain.Build(…) execution.No Proxy – marked by
[Abstract] attribute
DataObject o = session.CreateObject(
typeof(Animal),…);
Debug.Assert(o.GetType().ShortName==
“Animal_DataObjectsDotNetProxy”);
Proxies: abstract property implementationProxies: abstract property implementation
DataObjectDataObjectCat_ProxyCat_Proxy
get_Age
GetProperty(“Age”)
object ageValue = …
Lots of other calls
happen here
Type conversion is performed here.
(object-to-int in this case)
AA
Instances of the same entity in the applicationInstances of the same entity in the application
Domain domainDomain domain
Session s2Session s2Session s1Session s1
+ID=10
+LegCount=2
Cat_ProxyCat_Proxy
+ID=10
+LegCount=5
Cat_ProxyCat_Proxy
Different objects: s1[10]!=s2[10]
Moreover: s1[anyID]!=s2[anyID]
There can be several CLR instances describing the same
persistent entity (IDs of such instances are equal) in the
different Sessions. This means that the same entity may
looks different in the different Sessions.
DataObject: type documentationDataObject: type documentation
• Next 9 slides shows DataObject class properties and methods
• We recommend to read their names at least – this will give you an overview of capabilities of one of the most important types
• If it seems boring, feel free to skip next 9 slides
DataObject: type documentationDataObject: type documentation
DataObject: properties, part 1DataObject: properties, part 1
DataObject: properties, part 2DataObject: properties, part 2
DataObject: public methods, part 1DataObject: public methods, part 1
DataObject: public methods, part 2DataObject: public methods, part 2
DataObject: protected methods, part 1DataObject: protected methods, part 1
DataObject: protected methods, part 2DataObject: protected methods, part 2
DataObject: protected methods, part 3DataObject: protected methods, part 3
DataObject: protected methods, part 4DataObject: protected methods, part 4
Application layout examples
Web: simpleWeb: simple
ASP.NET HostASP.NET Host
BLL & DAL
ASP.NET Application (UI)ASP.NET Application (UI)
Thin ClientS
erv
er
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
DataSetsDataSets ObjectSetsObjectSets
RDBMS
Offline LayerOffline LayerAdapterAdapter
Web: separate RDBMS serverWeb: separate RDBMS server
ASP.NET HostASP.NET Host
BLL & DAL
ASP.NET Application (UI)ASP.NET Application (UI)
Thin ClientA
pp
licati
on
Serv
er
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
DataSetsDataSets ObjectSetsObjectSets
Offline LayerOffline LayerAdapterAdapter
RDBMS
Web farmWeb farm
ASP.NET HostASP.NET Host
BLL & DAL
ASP.NET Application (UI)ASP.NET Application (UI)
Thin Client
Serv
ers
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
DataSetsDataSets ObjectSetsObjectSets
Offline LayerOffline LayerAdapterAdapter
1…n
RDBMS
Web: single app. server, multiple UI serversWeb: single app. server, multiple UI servers
BLL & DAL
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
DataSetsDataSets ObjectSetsObjectSets
Offline LayerOffline LayerAdapterAdapter
RDBMS
ASP.NET HostASP.NET Host
ASP.NET Application (UI)ASP.NET Application (UI)
Thin Client
Server
1…n
This configuration is not
recommended (it’s inefficient in
comparison to others – there is
unnecessary .NET Remoting
interaction), but it can be
implemented
WindowsForms: simpleWindowsForms: simple
BLL & DAL
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
DataSetsDataSets ObjectSetsObjectSets
Offline LayerOffline LayerAdapterAdapter
RDBMS
WindowsForms Application
(UI)
WindowsForms Application
(UI)
Rich/Smart Client
Client PCs
This configuration is not
recommended (BLL & DAL code
works on the client side here),
but it can be implemented
WindowsForms: single application serverWindowsForms: single application server
BLL & DAL
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
ObjectSetsObjectSetsDataSetsDataSets
Offline LayerOffline LayerAdapterAdapter
RDBMS
Ap
plic
ati
on
Serv
er
WindowsForms Application
(UI)
WindowsForms Application
(UI)
Rich/Smart Client
Clie
nt
PC
Combined configuration: an exampleCombined configuration: an example
ASP.NET HostASP.NET Host
BLL & DAL
RDBMS
ASP.NET Application (UI)ASP.NET Application (UI)
Server
DataObjects.NET (DAL)
Single Domain Object
DataObjects.NET (DAL)
Single Domain Object
DataObjects, DataServices (BLL)DataObjects, DataServices (BLL)
DataSetsDataSets ObjectSetsObjectSets
Offline LayerOffline LayerAdapterAdapter
Client PC
WindowsForms Application
(UI)
WindowsForms Application
(UI)
Rich/Smart Client
Thin Client
Feature overview
Features mapFeatures map
Low-level Integrated
Persistence
Complete inheritance support
Supported property types
Relationships
Referential integrity control
Transparent persistence
VersionID/VersionCode
Transactions
(+ nested & distributed)
Savepoints
Automatic transactions
Queries (Query, SqlQuery)
Multilingual properties
Versionizing
Database schema
Automatic schema building and upgrading layer
Integrated
Configuration
Serialization
Property validators, correctors, modifiers
Full-text indexing and search
DataServices
RuntimeServices
IXxxEventWatcher
TrackingSet
Security system
Etc. (e.g. Pager, QueryPager)
Adapter
DataObjects-DataSets gateway
Models
Domain.ObjectModel
Domain.DatabaseModel
Domain. ExtractedDatabaseModel
Domain.UpdateActions
Performance
Caching Lazy instantiation
Lazy loading (load-on-demand)
Preloading
Delayed updates
Security checks caching
Manual caching support
Offline layer
Implementation of well-known DTO (Data Transfer Object) pattern
BindingManager
DataObjects.NET-ASP.NET & WindowsForms controls gateway
Add-ons
Automatic database schema upgardingAutomatic database schema upgarding
• Automatically adds\removes\modifies:– Tables– Columns– Indexes (& full-text indexes)– Primary\foreign keys– Views
• Upgrade modes:– Skip– SkipButExtract– SkipButExtractAndCompare– Perform– PerformSafe– PerformComplete– Recreate– Block
• Supports content integrity validation
ModelsModels
• Domain.ObjectModel– Provides all necessary information much faster then Reflection in
the runtime– Tightly bound with Domain.DatabaseModel (they’re mutually
related)• Domain.DatabaseModel
– Complete model of the database schema– Tightly bound with Domain.ObjectModel (they’re mutually related)
• Domain.ExtractedDatabaseModel– Complete model of the extracted database schema
• Domain.UpdateActions– A collection of objects describing schema upgrade SQL Script
See also: Domain.DebugInfoOutputFolder – a way to get all models dumped into text files
Model build schema (this slide was already shown earlier)Model build schema (this slide was already shown earlier)
Proxy AssemblyProxy Assembly
Extractor
RDBMS
ObjectModelBuilder
DatabaseModelBuilder
ProxyBuilder
UpdateActionsUpdateActionsSchema Upgrade
SQL Script
Schema Upgrade
SQL Script
UpdateActionsTranslator
DatabaseModelDatabaseModel
DatabaseModelComparer
ExtractedDatabaseModelExtractedDatabaseModel
ObjectModelObjectModel
.NET Reflection-provided model.NET Reflection-provided model
Persistence featuresPersistence features
• Complete inheritance support• Supported property types• Relationships• Referential integrity control• Transparent persistence• VersionID/VersionCode• Transactions (+ nested & distributed), Savepoints• Automatic transactions• Queries (Query, SqlQuery)• Multilingual properties• Versionizing
Persistence: complete inheritance supportPersistence: complete inheritance support
• Unified instance identification (64-bit integer identifiers)• Reference properties and collection items can be of any
persistent type• Hierarchy queries: "Select Animal objects where
{LegCount}=4“: returns all Animal, Cat and Dog objects having four legs
• Persistent interfaces:– IDataObject descendants– Reference properties and collection items can be of persistent
interface type– Interface queries: " Select ILeggedEntity objects where
{LegCount}=4"– Can contain paired reference\collection properties
Persistent property typesPersistent property types
• Base CLR types• Struct types• Serializable types• Reference types (DataObject\IDataObject or its
descendants)• DataObjectCollection type (collections of
references)• ValueTypeCollection type (collection of structs)• Delegates
Persistent field types schemaPersistent field types schema
FieldField
ValueTypeCollectionFieldValueTypeCollectionField
BooleanFieldBooleanField
ByteFieldByteField
LongFieldLongField
IntFieldIntField
EnumFieldEnumField
DoubleFieldDoubleField
DecimalFieldDecimalField
ShortFieldShortField
SingleFieldSingleField
TimeSpanFieldTimeSpanField
BlobFieldBlobField
DateTimeFieldDateTimeField
DelegateFieldDelegateField
StringFieldStringField
ObjectFieldObjectField
NumericFieldNumericField
GuidFieldGuidField
IHasContainedFieldsFieldIHasContainedFieldsField
IReferenceHolderGroupFieldIReferenceHolderGroupField
IReferenceHolderFieldIReferenceHolderField
IPairToTargetIPairToTarget
IPairToAllowedIPairToAllowed
DataObjectCollectionFieldDataObjectCollectionField
KeyFieldKeyField
ReferenceFieldReferenceField
ObjectIDFieldObjectIDField
PrimitiveFieldPrimitiveField ExtrnalFieldExtrnalField StructFieldStructField
Instances of Field descendants handle
persistence of each persistent property:
Domain.ObjectModel.Types[“Person”].Fields[“Ag
e”] is an IntField instance handling persistence of
Age property of Person.
Persistent properties: base CLR types
public abstract class Person: DataObject{ public abstract string Name {get; set;} public abstract string SecondName {get; set;} public abstract string Surname {get; set;} public abstract int Age {get; set;} public abstract string Info {get; set;}
[NotPersistent] public virtual string FullName { get { return Name+" "+SecondName+" "+Surname; } } }
Persistent properties: structs
public struct Point{ public double X; public double Y; public Point(double x, double y) { X = x; Y = y; }}
public abstract class Rectangle: DataObject{ public abstract Point LeftTop {get; set;} public abstract Point RightBottom {get; set;} }
Persistent properties: serializable types
public abstract class Author: DataObject{ public abstract Image Image {get; set;} ...}
Author a = (Author)session[aid];a.Image.RotateFlip(RotateFlipType.Rotate90FlipNone);
a.Image = a.Image;
Persistent proeprties: references, collections, pairs
public abstract class Article: DataObject{ ... public abstract Author Author {get; set;}}public abstract class Author: Person{ ... [Contained] [ItemType(typeof(Article))] [PairTo(typeof(Article), "Author")] public abstract DataObjectCollection Articles {get;}}
...
// First way to add an author to paired collectionarticle1.Author = author;// Second way to do absolutely the same thingauthor.Articles.Add(article1);// So both parts of paired relationship are maintained// in sync automatically
Persistent properties: ValueTypeCollections, part 1
public struct Quote{ [Length(250)] public string Body; public Quote(string body) { Body = body; }}
public abstract class Author: DataObject{ public abstract string Name {get; set;} ... [ItemType(typeof(Quote))] public abstract ValueTypeCollection Quotes { get; }}
Persistent properties: ValueTypeCollections, part 2
Author Tyler = (Author)session.CreateObject(typeof(Author));Tyler.Name = "Tyler Durden";Tyler.Quotes.Add(new Quote("You're not your job."));Tyler.Quotes.Add(new Quote("You're not how much money you
have in the bank."));Tyler.Quotes.Add(new Quote("You're not the car you
drive."));Tyler.Quotes.Add(new Quote("You're not the contents of your
wallet."));Tyler.Quotes.Add(new Quote("You are not a beautiful or
unique snowflake."));
Console.WriteLine("Quotes with length > 30:");q = new Query(session, "Select Author.Quotes values where
len({Body}) > 30");ValueTypeQueryResult vr = q.ExecuteValueTypeQueryResult();Console.WriteLine(" Result: {0} values.", vr.Count);foreach (ValueTypeQueryResultEntry entry in vr) Console.WriteLine(" {0}", ((Quote)entry.Value).Body);
Persistent properties: delegates
public delegate void CallbackDelegate(DataObject sender); public abstract class DelegateOwner: DataObject { [LoadOnDemand(Threshold=5)] public abstract CallbackDelegate Callback {get; set;} public virtual void OnCallback() { if (Callback!=null) Callback(this); } }
...CallbackDelegate staticDelegate = new CallbackDelegate( SomeClass.SomeStaticMethodThatConformsToCallbackDelegateSpec); someDelegateOwner.Change = staticDelegate;
CallbackDelegate regularDelegate = new CallbackDelegate( SomeClass.SomeMethodThatConformsToCallbackDelegateSpec); someDelegateOwner.Change += regularDelegate; someDelegateOwner.Change = someDelegateOwner.Change;
Relationship typesRelationship types
• Regular references (to one)• Mutually dependent references (one-to-one)• Collections• Mutually dependent reference-collection pair (one-
to-many)• Mutually dependent collections (many-to-many,
symmetric)• N-ary relationships (via ValueTypeCollections)• Mutually dependent N-ary relationships
Mutual relationship: one-to-one
public abstract class Passport: DataObject{ public abstract string SerialNumber {get; set;} public abstract Person Person {get; set;}}
public abstract class Person: DataObject{ ... [PairTo(typeof(Passport),"Person")] public abstract Passport Passport {get; set;} }
Mutual relationships: one-to-many
public abstract class Article: DataObject{ ... public abstract Author Author {get; set;}}
public abstract class Author: Person{ ... [ItemType(typeof(Article))] [PairTo(typeof(Article), "Author")] public abstract DataObjectCollection Articles {get;}
}
Mutual relationship: many-to-many
public abstract class Principal: FtObject{ [ItemType(typeof(Role))] public abstract RoleCollection Roles {get;} // RoleCollection is "typed" DataObjectCollection // descendant ...}
public abstract class Role: Principal{ [ItemType(typeof(Principal))] [PairTo(typeof(Principal),"Roles")] // !!! public abstract PrincipalCollection Principals {get;} // PrincipalCollection is "typed" DataObjectCollection // descendant ...}
Mutual relationship: symmetric
public abstract class Person: DataObject { public abstract string Name {get; set;}
[ItemType(typeof(Person))] [Symmetric] // == [PairTo(typeof(Person),"Friends")] public abstract DataObjectCollection Friends {get;}}
...
Person me = (Person)session.CreateObject(typeof(Person));me.Name = "Alex";Person bob = (Person)session.CreateObject(typeof(Person));bob.Name = "Bob";me.Friend.Add(bob);if (me.Friends.Contains(bob))
Console.WriteLine("Bob is a friend of my.");if (bob.Friends.Contains(me))
Console.WriteLine("I'm a friend of Bob.");
Mutual relationship: n-ary relationship (ternary), part 1
[Serializable] public struct OrderPosition { public int Index; // In Order's collection public Order Order; public Product Product; public Vendor Vendor; public double Quantity; public OrderPosition(int index, Product product, Vendor
vendor, double quantity) { Index = index; Order = null; Product = product; Vendor = vendor; Quantity = quantity; } }
Mutual relationship: n-ary relationship (ternary), part 2
public abstract class Order: DataObject { ... [ItemType(typeof(OrderPosition), OwnerField = "Order")] public abstract ValueTypeCollection Positions {get;} } public abstract class Product: DataObject { ... [ItemType(typeof(OrderPosition), OwnerField = "Product")] [PairTo(typeof(Order), "Positions")] public abstract ValueTypeCollection OrderPositions {get;} } public abstract class Vendor: DataObject { ... [ItemType(typeof(OrderPosition), OwnerField = "Vendor")] [PairTo(typeof(Order), "Positions")] public abstract ValueTypeCollection OrderPositions {get;} }
Referential integrity controlReferential integrity control
• Automatic for all references and collections• Controlled via [AutoFixup(AutoFixupAction)]• AutoFixupAction types:
– Clear (default): a reference property should be automatically set to null on removal of its target
– Block: prevents removal of reference target. An exception will be thrown on attempt to do this except the case when reference holder object is also removing.
– None: reference fixup is turned off for a particular reference property – useful e.g. when there is a service similar to XxxFtIndexer that actually performs the same, but more efficiently
• Session.RemoveObjects allows to remove a group of objects “at once” – i.e. the number of reference search queries won’t be proportional to the number of removing objects (usually just few queries will be executed)
• Session.RemoveObjects is used by DataObjects.NET to remove groups of [Contained] objects
Transparent persistenceTransparent persistence
• Transparent change propagation– No Apply changes / Save – like methods
• Transparent cached data invalidation \ reloading– No necessity to use Reload– See TransactionContext for additional information
Transparent persistence
Cat sonya = ... ; // Some code that fetches the Cat // instancesonya.Name = "Sonya";
// DataObjects.NET transparently // persist a new property valueCat kitty = sonya.Children[0];
// DataObjects.NET queries // a collection and fetches // the collection itemConsole.WriteLine(kitty.Parent.Name);
// output: "Sonya"
VersionID/VersionCode supportVersionID/VersionCode support
• VersionID– Int32 value– Updated automatically on any change (actually – once per each persist
operation)– Automatically used for cached data invalidation– Supported by Adapter \ Offline layer \ BindingManager– Can be used by developers (e.g. to perform optimistic updates)
• VersionCode– String value– “VersionID” analogue for UI – shouldn’t be changed on any modifications
made by services (e.g. by workflow engine) to prevent unexpected “Object was modified by another user”-like messages in UI
– Can be based on any other combination of persistent properties (e.g. ModifyDate.ToString())
– Checked by DataObject.CheckVersionCode method– Initially – VersionID-based– Can be reimplemented by developers
Transactions, SavepointsTransactions, Savepoints
• Transactions– Explicit \ automatic– Nested transactions are supported– Distributed transactions are supported
• Savepoints– Nested transactions implementation is based on
Savepoints in DataObjects.NET– Some RDBMS (e.g. MaxDB) doesn’t support
savepoints, but supports true nested transactions – in this case underlying DataObjects.NET RDBMS driver internally uses nested transactions to simulate them
Automatic transactionsAutomatic transactions
• Provided for DataObject\DataService methods• All transactional methods should be virtual
(otherwise ProxyBuilder will be unable to override them to add automatic transaction handlers)
• [Transactional] attribute controls automatic transaction handler behavior
• Automatic deadlock re-processing is supported• All built-in methods accessing the data are already
transactional (e.g. DataObject.GetProperty, DataObjectCollection.Count, etc.)
Transactional method proxy codeTransactional method proxy code
AnimalAnimalCat_ProxyCat_Proxy
Feed(food)
r = base.Feed(food)
r
Actual method execution
TransactionController
creation code
Try-Catch block
Reprocessing check,
Commit
AA
Possible jump in case
with deadlock
reprocessing
Transactional method example 1: transactional property & method
public abstract class Person: DataObject{ public abstract string Name {get; set;} public abstract string SecondName {get; set;} public abstract string Surname {get; set;}
[NotPersistent] [Transactional(TransactionMode.TransactionRequired)] public virtual string FullName { get { return Name+" "+SecondName+" "+Surname; } }
[Transactional(TransactionMode.TransactionRequired)] public virtual string GetFullName() { return Name+" "+SecondName+" "+Surname; } }
Transactional method example 2: direct SQL query execution
[ServiceType(DataServiceType.Shared)] public abstract class AggregateInfo : DataService { [Transactional(TransactionMode.TransactionRequired)] public virtual double MinimalAnimalAge() { Demand(...); DisableSecurity(); // Otherwise Session.CreateRealCommand() // call will fail. try { IDbCommand cmd = Session.CreateRealCommand(); cmd.CommandText = "Select min([Age]) from " + Session.Types[typeof(Animal)].RelatedView.Name; return cmd.ExecuteScalar(); } finally { EnableSecurity(); } } }
Queries: features, examplesQueries: features, examples
• Text-based (Query object), support:– Options:
Select top 5 Item instances with (LoadOnDemand, FastFirst, ForUpdate) where {Name} <> 'Rattleless‘
– Parameters: Select Category instances where {Products.Count} > @Min
– Type casts: Select Cat instances where exists{(Dog)Friends.(Mouse)Friends}
– Distinct, joins: Select distinct Author instances inner join $root.Articles as $a order by {$a.Date}
– Reference\collection queries: Select Building.Addresses.Item.City instances
– ValueTypeCollection queries: Select Author.Quotes values where len{Body} > 100
– Subqueries: Select top 100 IFtObject instances as $fto with (SkipLocked) where not exists {Select FtRecord objects where {FtObject}={$fto} }
– Full-text queries: Select Author instances where {Name}>='D' textsearch top 5 freetext 'Jungle' order by {FullTextRank} desc
• Sql-based (SqlQuery object)
Query-related types: schemaQuery-related types: schema
QueryBaseQueryBase
…
AA
SessionBoundObjectSessionBoundObject AA SessionSession
QueryQuery
+Text
…
Other QueryBase properties are
overriden to be read-only
SqlQuerySqlQuery
…
QueryParameterQueryParameter
QueryParameterCallQueryParameterCall
IListIList
ICollectionICollection
IEnumerableIEnumerable
1…1
*…1
*…1
Multilingual propertiesMultilingual properties
• Multilingual ([Translatable]) propertiesA set of values is maintained for such property, one for each Culture registered in the Domain. Each of such values is stored in its own column having a culture suffix (e.g. if property name is "Title", the name of the column can be "Title-En" for a culture having "En" name)
• [Collatable] string propertiesA set of columns with different collations (one per each registered Culture) is created in the database to keep the value of single string to allow use different sorting rules for this property
ID
…
CreateDate
Title
Content
DocumentDocument
EnEn RuRu DeDe
EnEn RuRu DeDe[Translatable]
[Indexed(FullText=true)]
[Translatable], [Collatable] example: declaration
public abstract class Person: DataObject{ [Collatable] [Indexed] public abstract string Name {get; set;} ...}
public abstract class Book: DataObject{ [Indexed] public abstract string Title {get; set;}
[Translatable] public abstract string Description {get; set;}
[Translatable] [ItemType(typeof(Comment))] public abstract ValueTypeCollection Comments { get; }
...}
[Translatable], [Collatable] example: usage
Culture cEn = domain.Cultures["En"];Culture cRu = domain.Cultures["Ru"];
// [Translatable] property usageBook b = (Book)session.CreateObject(typeof(Book));b.Title = "DataObjects.NET Internals";session.Culture = cEn; // Switches current culture in the sessionb.Description = "DataObjects.NET is...";session.Culture = cRu; // Switches current culture in the sessionb.Description = "DataObjects.NET есть...";
Book b = (Book)session.CreateObject(typeof(Book));b.Title = "DataObjects.NET Internals";bk1["Description",cEn] = "DataObjects.NET is... ";bk1["Description",cRu] = "DataObjects.NET есть... ";
// [Collatable] property usageCulture cEn = domain.Cultures["En"];Culture cRu = domain.Cultures["Ru"];session.Culture = cEn; // Switches current culture in the sessionQueryResult qr1 = session.CreateQuery( "Select Person object where {Name}>'Й' order by {Name}").Execute();session.Culture = cRu; // Switches current culture in the sessionQueryResult qr2 = session.CreateQuery( "Select Person object where {Name}>'Й' order by {Name}").Execute();
VersionizingVersionizing
• Enables to switch any Session into read-only “browse past” mode to see the storage at any previous point of time (after completion of any previous successful transaction)
• All data necessary to enter “browse past” mode is stored automatically when versionizing is activate in the domain (see Domain.DatabaseOptions)
• No any storage modifications are performed on entering the “browse past” mode – this happens immediately on invocation of special method of Session object
• All “read-only” code works the same in the “browse past” mode – you can read any available data in the version you’re browsing, execute queries, use Adapter or Offline layer to export the data, etc.
• “Regular” to “Versionized” schema upgrade is performed automatically on versionizing mode activation
• Such additional information as transaction history and set of stored versions for each instance is maintained automatically and is always available on request
Versionizing example (from Demo_Versionizing)
session.BeginTransaction();
Query q = session.CreateQuery("Select Person instances");DataObject[] persons = q.ExecuteArray(1);Person p = (Person)persons[0];Account a = p.Accounts[0];
Console.WriteLine("Person: {0}", p.FullName);VersionInfo[] versions = a.GetVersionInfo();for (int i = 0; i < versions.Length; i++) { VersionInfo version = versions[i]; session.BrowsePast(version.RemovedInTransaction); Console.WriteLine(“ Account operations history till {0}", version.RemovedInTransactionID==0 ? "Now" : version.RemovedInTransaction.StartTime.ToString( "dd/MM/yyyy HH:mm:ss:fff"));
... Console.WriteLine(" Balance: {0}", a.Balance); Console.WriteLine(" Total Profits: {0}", a.TotalProfits());}
session.Commit();
Performance-related featuresPerformance-related features
• Caching– Built-in transparent caching of instances and collection content– Two-level caching architecture:
• WeakReference-based Session-level cache• Queue-based Domain-level (global) cache with any specified size
• Lazy instantiation & loading (load-on-demand)– Reference target \ collection item is instantiated on
the first traversal attempt– [LoadOnDemand] attribute, LoadOnDemandAttribute.Threshold– With (LoadOnDemand) option– FastLoadData column
• Preloading (Session.Preload(…) methods)• Delayed updates• Security checks caching• Manual caching support (see CacheableValue,
CacheableCollection, CacheableQuery)
Caching: instance caching architectureCaching: instance caching architecture
DomainDomain
1…1
GlobalCacheGlobalCache
RDBMS
SessionCacheSessionCache
1…*
1…11…1
Session cache interacts with Persister:
-When new instantiation info is fetched by RDBMS
driver, it's sent to Session cache
- When new (ID, VersionID) pair is fetched, Session
cache uses it to validate or invalidate corresponding
cached instance or instantiation info
Session cache interacts with Global cache:
- When no data is found in the Session cache, it’s requested from the
Global cache
-When new instantiation info is fetched by RDBMS driver, it’s
propagated into Global cache
- When new (ID, VersionID) pair is fetched, Global cache uses it to
possibly invalidate corresponding cached instantiation info
- Keeps instantiation info
- Queue-based (last accessed
entry moves to the top)
- Size-limited, thread-safe
- WeakReference-based
- Keeps reference to DataObject
instances or instantiation info
SessionSession
PersisterPersister
Caching: collection content cachingCaching: collection content caching
#Implementation
CollectionCollection
SessionSession
RDBMS
Makes “dirty” job,- interacts
with RDBMS driver
[Implementation][Implementation]PersisterForCollectioinsPersisterForCollectioins
- Allways empty or fully loaded- Handles Collection[int index] – like operations
[ItemByIndex cache][ItemByIndex cache]
- Can be partionally loaded- Handles Collection[long itemId] - like operations (Contains, Remove)
[ItemByItemID cache][ItemByItemID cache]
*…1
1…11…
1
1…1
1…1
1…1
Interact with each
other
DataObjectCollection or
ValueTypeCollection
Lazy instantiationLazy instantiation
• DataObjects.NET transparently instantiates (creates in-memory object and fetches its state data from the database) referred objects on the first access attempt:– Evaluation of cat.Children[0].Friends[1].Parent can lead to instantiation of
up to 3 new objects:• cat.Children[0]• cat.Children[0].Friends[1] • cat.Children[0].Friends[1].Parent
– Actual number of fetched objects depends on content of Session & Domain-level caches
• Even if Query\SqlQuery had fetched the data for some instance, its instantiation is delayed until the first attempt to access it (e.g. via QueryResult[index], or by any other way):– So if you don’t access some instances from QueryResult, time+memory
isn’t spent on their instantiation– Nevertheless its instantiation data (a struct containing all base fields, such
as ID, TypeID, VersionID, FastLoadData, Permissions) is stored both in Session and Domain-level caches, so it can be used further
Lazy loadingLazy loading
• [LoadOnDemand] attribute applied on the persistent property notifies that value of this property should be fetched only on the first attempt to access it, but not on the first attempt to access the instance– [LoadOnDemand] attribute supports Threshold specification – it
allows to setup the size of the [LoadOnDemand] property until which property is loaded by a regular way
– Threshold can be specified for collection properties – in this case no additional queries are necessary to fetch the collection data while its size is lower then Threshold value
• With (LoadOnDemand) option of Query (see QueryOptions also) \ SqlQuery specifies that only (ID, VersionID) pairs should be actually fetched on query execution– Nevertheless produced QueryResult behaves absolutely the same – all
data is transparently fetched on attempt to access its item, if necessary– This option helps to significantly reduce the traffic between SQL and
application server in case when it’s well-known when Domain or Session-level cache contains most part of required objects
– Fetched information (ID, VersionID pairs) is also internally used to validate\invalidate the data contained in caches
PreloadingPreloading
• Preloading is useful when you’re planning to process large amount of data – it allows to optimize its fetching sequence
• Session.Preload(…):– Fills Session-level cache with instances \ collections \
[LoadOnDemand] properties that are planned to be accessed further
– Executes minimal necessary number of queries on preload operations
– Fetches only those data that isn’t available in Session-level or Domain-level caches
• DataObjectCollection \ ValueTypeCollection.Preload()– Loads the whole collection (populates its ItemByIndex cache)
• QueryResult.Preload()– Loads all QueryResult items that aren’t available in the Session\
Domain-level caches
Delayed updatesDelayed updates
• When you change a property value or collection content, DataObjects.NET normally doesn’t immediately flushes the change to the database
• Almost all types of such updates are delayed by default and flushed as late as it's possible (usually – right before the first Query\SqlQuery execution or Transaction.Commit)
• Delayed update sequence is normally executed via much less number of queries
Manual caching example: CacheableQuery usage
CacheableQuery qr = new CacheableQuery( session.CreateQuery( "Select Country instances with (Count)"), new TimeSpan(1000));
Console.WriteLine("Number of countries: {0}", qr.Count);
Thread.Sleep(500);// Is the same anyway hereConsole.WriteLine("Number of countries: {0}",
qr.Count);
Thread.Sleep(500);// Can be different here Console.WriteLine("Number of countries: {0}",
qr.Count);
Manual caching example: CacheableValue usage (calculated property caching)
public abstract class CIsAMultipliedByB: DataObject { public abstract double A {get; set;} public abstract double B {get; set;}
private CacheableValue cachedC; private void CalculateC(object source, object sender, CacheableValueEventArgs e) { e.CacheableValue.Value = A*B; }
public double C { get { if (cachedC==null) cachedC = new CacheableValue(Session, new TimeSpan(60000), new CacheableValueEventHandler(CalculateC)); return (double)cachedC.Value; } }
protected override void OnPropertyChanged(string name, Culture culture, object value) { base.OnPropertyChanged(name, culture, value); if (name=="A" || name=="B") cachedC = null; // Invalidation of cached value }}
Integrated features mapIntegrated features map
• Configuration• Serialization• Property validators, correctors, modifiers• Full-text indexing and search• DataServices
– RuntimeServices– IDataObjectEventWatcher, ITransactionEventWatcher,
IQueryEventWatcher
• TrackingSet• Security system• Etc. (e.g. Pager, QueryPager)
Configuration section example
<?xml version="1.0" encoding="utf-8"?><configuration> <configSections> <section name="DataObjects.NET" type="DataObjects.NET.ConfigurationSectionHandler,Dataobjects.NET" /> </configSections> ... <DataObjects.NET> <!-- connectionUrl="oracle://admin:admin@localhost/DoPetShp" --> <!-- connectionUrl="sapdb://admin:admin@localhost/DoPetShp" --> <domain connectionUrl="mssql://localhost/DoPetShop" productKeyPath="..\..\..\ProductKey.txt" debugInfoOutputFolder="C:\Debug" foreignKeyCreationMode="CreateForeignKeys, CreateNullRows" securityMode="Modern" securityOptions="Standard, AllowCreateUnauthenticatedSessions" sessionSecurityOptions="Standard" updateMode="Perform" > <cultures defaultCulture="En"> <culture name="En" title="U.S. English" cultureName="en-US" compareOptions="IgnoreWidth,IgnoreKanaType" /> </cultures> <types> <assembly name="DoPetShop.Model" /> </types> <services> <assembly name="DoPetShop.Model" /> </services> </domain> </DataObjects.NET> ...</configuration>
Configuration section usage
Domain domain = Configuration.DefaultDomain;...domain.Build();
SerializationSerialization
• DataObjects.NET completely supports .NET Serialization. It allows to serialize or deserialize a graph of objects containing persistent instances using binary or SOAP formatters, user-specified formatters are also supported.
• DataObject descendants aren’t serializable by their nature – any such instance belongs to some Session and has relationships with set of other non-serializable instances. So it’s impossible to serialize a DataObject instance by the usual way. To perform serialization or deserialization, you should use special class – Serializer. This class is bound to Session (see Session.CreateSerializer) and capable to serialize\deserialize a graph of objects that contains some DataObject instances using BinaryFormatter\SoapFormatter or user-specified formatter.
• Serializer internally uses a set of helper objects to handle its job. Basically its task is to configure a formatter (add special SurrogateSelector and SerializationBinder) in the way allowing to serialize DataObject instances without difficulties.
Serialization example
Serializer serializer = session.CreateSerializer();serializer.FormatterType = FormatterType.Soap;serializer.SerializationOptions =
SerializationOptions.IncludeContainedInstances;
using (StreamWriter sw = new StreamWriter(“Data.xml")) { serializer.Serialize(sw.BaseStream, myObjectGraph); Console.WriteLine("Serialized: {0} instance(s), {1}
external(s).", serializer.LastOperationInstanceCount, serializer.LastOperationExternalInstanceCount);}
using (StreamReader sr = new StreamReader(“Data.xml")) { myObjectGraph = serializer.Deserialize(sr.BaseStream); Console.WriteLine("Deserialized: {0} instance(s), {1}
external(s).", serializer.LastOperationInstanceCount, serializer.LastOperationExternalInstanceCount);}
Full-text indexing and search featuresFull-text indexing and search features
• Unified, but absolutely customizable full-text data population• Two fill-text indexing & search driver types:
– Native: uses RDBMS-provided full-text indexing and search features. Requires support of these features by underlying RDBMS driver. Currently only MS SQL driver supports native full-text indexing and search.
– External: uses external full-text search engine. These driver types are RDBMS-independent, i.e. you can use such driver even while RDBMS doesn’t support full-text indexing and search at all (e.g. Firebird). DotLucene full-text indexing and search driver is built-in into DataObjects.NET.
• Unified full-text search queries:– Query-based: Select Author instances where {Name}>='D‘ textsearch top 5
freetext 'Jungle‘ order by {FullTextRank} desc – SqlQuery-based (see SqlQuery.FtsCondition)– Three full-text search modes:
• FreeText – well-known free-text search mode, supported by all FtsDrivers• Condition – allows to specify exact full-text search condition on the language of
underlying full-text search engine• LikeExpression – doesn’t require full-text search engine at all, uses SQL “like”
predicate to execute the query (not recommended mode, since it’s actually can be very slow on >1000000-object database)
• Built-in managed wrapper for Microsoft Index Service Filters allows to index content of almost any imaginable file type (such as HTML, Adobe PDF and Microsoft Office files).
Full-text I&S: implementation stepsFull-text I&S: implementation steps
1. Inherit the types that require full-text search from FtObject or implement IFtObject (actually – tagging interface), override\implement ProduceFtData method(s)
2. Select full-text search driver by specifying Domain.FtsConnectionUrl. Examples:
native://localhost/ lucene://localhost/?IndexPath=C:\Debug\LuceneIndex
&Analyzer-Ru=Lucene.Net.Analysis.RU.RussianAnalyzer,%20\Lucene.Net&CreateSummaryFieldsForCultures=true
3. Add full-text indexer service to RuntimeServices collection of Domain(FtsDriver-provided RuntimeService that gathers full-text content produced by your IFtObject implementers and sends it to the underlying full-text indexing and search service)
4. Use Query or SqlQuery+FtsCondition to execute full-text search queries
Full-text I&S: persistent typesFull-text I&S: persistent types
IFtObjectIFtObject
ProduceFtData(FtData)
ProduceFtData(FtData,Culture)
string ProduceFtData(Culture)
1…1
[Translatable]
[Indexed(FullText=true)]
DataObjectDataObject AA
FtObjectFtObject AAIn fact – empty, since IFtObject
support is implemented on
DataObject level
IsFtRecordUpToDate
IDataObjectIDataObject +FtData
+FtObject
+IsIndexed
FtRecordFtRecord
+ProduceFtData(…)
+UpdateFtData(…)
Derive your types from these
to get full-text I&S support for
them
Full-text I&S: drivers schemaFull-text I&S: drivers schema
SBOSBO AASessionSession
1…1
RuntimeServiceRuntimeService AA
*…1
FtsDriverFtsDriver AA
Creates full-text indexers
LuceneFtsDriverLuceneFtsDriver
NativeFtsDriverNativeFtsDriver
LuceneFtsIndexerLuceneFtsIndexer
NativeFtsIndexerNativeFtsIndexer
*…1
1…*
Execute full-text queries,
create full-text indexers
DomainDomain
FtsConnectionUrl
FtsDriver
Indexes IFtObjects
(creates, updates and removes
FtRecord objects)
DataServiceDataService AA
Full-text I&S: FtIndexer.Execute() stepsFull-text I&S: FtIndexer.Execute() steps
IsFtRecordUpToDate
IFtObject AIFtObject A
null
FtRecord RAFtRecord RA
IFtObject BIFtObject B
IFtObject CIFtObject C
FtRecord RBFtRecord RB
IsFtRecordUpToDate
IsFtRecordUpToDate
FtDataIsIndexed
FtData = “Old”IsIndexed
FtRecord RCFtRecord RC
FtDataIsIndexed
FtRecord RXFtRecord RX
FtDataIsIndexed
FtData = “New”IsIndexed
First step:
XxxFtIndexer creates new FtRecord objects for all IFtObject
instances not having them
Second step:
- NativeFtIndexer updates FtData property (for each culture)
by executing FtRecord.UpdateFtData method on each
FtRecord having FtObject.IsFtRecordUpToDate==false;
- LuceneFtIndexer sets IsIndexed property to true on each
FtRecord object having
FtObject.IsFtRecordUpToDate==false || IsIndexed==false
and sends the result of FtRecord.ProduceFtData() execution
to Lucene
The last step:
XxxFtIndexer removes all FtRecord objects having no
associated IFtObject instances
Full-text I&S: query execution schemaFull-text I&S: query execution schema
QueryBaseQueryBase FtsDriverFtsDriver PersisterPersister
Execute
IDbCommand cmd
yes no
FtsDriver is native?
FtsDriver.ExecuteQuery(ftsCondition)
ftsResult
BuildQueryCommand(…,ftsCondition,ftsResult)
FtsResult==null ?
Build IDbCommand
yes no
Build full-text search restriction
& “order by” clause
Build.”in (…)” restriction
Execute Command
ArrayList result
yes no
Reorder result by ranks
Fts.Condition.OrderByRank==true
&& FtsResult!=null ?…
Full-text I&S example, step 1: producing full-text search data
public abstract class FtFile: FtObject{ [Length(256)] [Length(83, DriverTypes="Firebird")] // Firebird - specific declaration [Indexed] public abstract string Name {get; set;}
[NotPersistent] public virtual string Extension { get { return Path.GetExtension(Name).Substring(1); } }
[LoadOnDemand] [StorageValueModifier(typeof(Compressor))] public abstract byte[] Content {get; set;}
public override string ProduceFtData(Culture culture) { string filteredContent = ""; try { filteredContent = Filter.Create(Extension).GetFilteredContent(Content); } catch {} return base.ProduceFtData(culture) + "\n File name: "+Name + "\n Content: "+filteredContent; }}
Full-text I&S example, step 2: using XxxFtIndexer
// Adding full-text indexer to// Domain.RuntimeServices collection// to make it to be periodically executeddomain.RuntimeServices.AddRuntimeService( "FtIndexer", domain.FtsDriver.GetFtIndexerType());
// Immediate full-text indexer execution// (use this approach only if necessary)RuntimeService ftIndexer = domain.FtsDriver.CreateFtIndexer(s, 1000000);
ftIndexer.Execute();
Full-text I&S example, step 3: full-text search queries
result = session.CreateQuery( "Select FtFile instances "+ "textsearch freetext “+ s.Utils.QuoteString(searchString)+" "+ "order by {FullTextRank}”).ExecuteArray();
Console.WriteLine(" Found files: {0}", result.Length);foreach (FtFile f in result) Console.WriteLine(" "+f.Name);
DataServices, RuntimeServicesDataServices, RuntimeServices
• DataServices– Allow to use automatic transactions & transparent deadlock handling with
non-persistent classes (DataService descendants)
– Greatly simplify the development of services operating with persistent instances
– Two DataService types:• Shared: only one instance of such service can be created in each Session.
Session.GetService(…) method provides access to such services• Regular: any number of instances of such service can be created in each
Session; Session.CreateService(…) method allows to create such services
• RuntimeServices– DataServices of special type (RuntimeService descendants) can be
periodically executed in the separate Thread and Session maintained by the Domain
– Use Domain.RuntimeServices collection to add or remove RuntimeService from the execution queue
– RuntimeServices’ purpose is to execute maintenance tasks periodically
DataServicesDataServices
DomainDomain
+Session
SessionBoundObjectSessionBoundObject AA
+RegisterService(…)
+RegisterServices(…)
+GetService(…)
+CreateService(…)
SessionSessionDataServiceDataService AA
SomeSharedServiceSomeSharedService AA
SomeServiceSomeService AASomeSharedService_ProxySomeSharedService_Proxy
*…1
1…*
SomeSharedService_ProxySomeSharedService_Proxy
*…1
1…1
- Actually – proxy objects
- Supports [Transactional] attribute
- Don’t support persistent properties
[ServiceType(DataServiceType.Shared)]
RuntimeServicesRuntimeServices
…
DomainDomain
+Session
SessionBoundObjectSessionBoundObject AA
+RuntimeServices(…)
…
+GetService(…)
+CreateService(…)
SessionSession
+Add(RuntimeService)
+Remove(…)
RuntimeServicePoolRuntimeServicePool
-Session
-Thread
1…1
RuntimeServiceRuntimeService AA
+Execute(…)
+GetDelay(…)
…
DataServiceDataService AA
ThreadThread
1…1
1…1
*…1
*…1
DataService example: direct SQL query execution
[ServiceType(DataServiceType.Shared)] public abstract class AggregateInfo : DataService { [Transactional(TransactionMode.TransactionRequired)] public virtual double MinimalAnimalAge() { Demand(...); DisableSecurity(); // Otherwise Session.CreateRealCommand() // call will fail. try { IDbCommand cmd = Session.CreateRealCommand(); cmd.CommandText = "Select min([Age]) from " + Session.Types[typeof(Animal)].RelatedView.Name; return cmd.ExecuteScalar(); } finally { EnableSecurity(); } } }
RuntimeService example: XxxFtIndexe registration
// Adding full-text indexer to// Domain.RuntimeServices collection// to make it to be periodically executeddomain.RuntimeServices.AddRuntimeService( "FtIndexer", domain.FtsDriver.GetFtIndexerType());
IDataObjectEventWatcher, ITransactionEventWatcherIDataObjectEventWatcher, ITransactionEventWatcher
• Implement IDataObjectEventWatcher interface by a shared DataService to make it being notified on DataObject-related events, such as instance creation, modification and deletion– All DataObject.OnXXX events are also “forwarded” to
IDataObjectEventWatcher implementors
• Implement ITransactionEventWatcher interface by a shared DataService to make it being notified on Transaction-related events, such as transaction creation, commit or rollback
• Implement IQueryEventWatcher interface by a shared DataService to make it being notified on Query\SqlQuery-related events, such as query execution. This interface allows to transparently modify underlying IDbCommand – e.g. to apply an additional restriction to its “where” clause
TrackingSetTrackingSet
Purpose:• Tracks different types of activity of DataObject instances in the Session it was created in,
such as:– Instance creation– Modification– Deletion
• Its usage allows UI tier to get the information about changes made by arbitrary BLL action (method execution) – to properly refresh the controls displaying modified objects or their properties
Features:• Provides StartTracking() \ StopTracking() methods• Constantly maintains a set of IDs of instances that tried to perform specified activity
types while tracking is turned on• Automatically stops tracking on commit of transaction it was created in• Internally uses TrackingService – an implementor of IDataObjectEventWatcher and
ITransactionEventWatcher interfaces• Used by Adapter on Adapter.Update execution – to properly re-fill all changed objects
existing in it (as you know, changing a single property may lead to lots of modifications in the whole storage – all BLL logic is fully depend on you)
• Used by Offline layer on ObjectSet.ApplyChanges execution
Security system featuresSecurity system features
• NTFS-like:– Based on Permissions, Principals (Users and Roles) and secured objects (DataObject
instances)– Supports Allowed \ Denied permissions– Permission inheritance:
• Each DataObject has SecurityParent property (DataObject)• There is a root object: ISecurityRoot implementor• Permissions are inherited by NTFS rules
• Completely extendable– Declare your custom permission types– Demand them in BLL code (e.g. in OnGetProperty methods)– Override built-in permission checks:
• All built-in permission demands are implemented in DataObject.OnXXX(…) methods, so they can be overridden
• You can override even DataObject.IsAllowed(…) and DataObject.Demand(…)– Declare your own User and Role types (descendants of our base classes)– Implement your own authentication methods
• Transparent– All security restrictions take effect immediately on any security-related changes in the Session -
it’s not necessary to reopen the Session or to invoke some method to apply new security restrictions:
• When you adding a User to some Role, this immediately affects on its security restrictions• Even rollback of the inner transaction immediately affects on security restrictions
• Extremely fast– Up to 4000000 permission demands per second on 2,8GHz P4!
Security classes schemaSecurity classes schema
+SecurityParent
+Permissions
…
+Demand(…)
+IsAllowed(…)
DataObjectDataObject AA
+Roles
+AllRoles
PrincipalPrincipal AA
+Principals
RoleRole AA
+IsDisabled
…
+Authenticate(…)
UserUser AA -Password
…
+SetPassw.(…)
…
StdUserStdUser
+Inherit
…
AccessControlListAccessControlListIListIList
ICollectionICollection
IEnumerableIEnumerable
1…1
1…*
*…1
Provides access to the
following entries
PrincipalPrincipal
Allowed Permission SetAllowed Permission Set
Denied Permission SetDenied Permission Set
Built-in Permissions
ReadPermissionReadPermission
ChangePermissionChangePermission
AdministrationPermissionAdministrationPermission
…
+Clear()
+Union(…)
+Subtract(…)
+Intersect(…)
+IsSubsetOf(…)
+IsSupersetOf(…)
PermissionSetPermissionSet
IPermissionIPermission
*…*
*…1
1…1
Security system: implementation stepsSecurity system: implementation steps
1. Implement your custom permission type(s)2. Put demands of new permissions into BLL code3. Allow\deny permissions on necessary entities 4. Authenticate Users in Sessions to get security
system working
Note: You may disable using of unauthenticated Sessions at all – see Domain.SecurityOptions.
Security implementation example, step 1: delcaring custom permission
[Serializable]public class ChangeAccountPropertiesPermission: Permission{ private static ChangeAccountPropertiesPermission _value = new ChangeAccountPropertiesPermission(); private static ReadOnlyPermissionCollection gigList = new ReadOnlyPermissionCollection(new IPermission[] {});
public static ChangeAccountPropertiesPermission Value { get {return _value;} }
public override ReadOnlyPermissionCollection GrantedIfGrantedAnyOf {
get {return gigList;} }}
Security implementation example, step 2: putting demands into BLL code
public abstract class Account: DataObject, IPerson { ... protected override void OnSetProperty(string name, Culture culture, object value) { if (name=="CurrentOrder") { Demand(OwnerPermission.Value); return; } if (this.Type.Fields[name]!=null && this.Type.BaseType.Fields[name]==null) { Demand(ChangeAccountPropertiesPermission.Value); return; } base.OnSetProperty(name, culture, value); } ...}
Security implementation example, step 3: allowing permissions
Account a = (Account)session[...];
// Granting ChangeAccountPropertiesPermission// for someUser on aa.Permissions.Allow( someUser, ChangeAccountPropertiesPermission.Value);
// Granting OwnerPermission// for someRole on aa.Permissions.Allow( someRole, OwnerPermission.Value);
Security implementation example, step 4: using authenticated Session
Session session = domain.CreateSession("Alex", "AlexPassword");
// Security is enforced for User // with Name=="Alex“ here...
session.Authenticate("Dmitry", "DmitryPassword");
// Security is enforced for User // with Name=="Dmitry“ here...
Property validators, correctors, modifiersProperty validators, correctors, modifiers
• DataObjects.NET provides [Validator], [Corrector] and [StorageValueModifier] attributes
• [Validator] \ IPropertyValueValidator validates DataObject property value before it is actually set by DataObject.SetProperty method
• [Corrector] \ IPropertyValueCorrector corrects DataObject property value before it is actually set by DataObject.SetProperty method
• [StorageValueModifier] \ IPropertyStorageValueModifier pre-processes internal property value before persisting it to the database (e.g. compress it) and do the same on fetching it (e.g. decompress it)
• Such attribute-based approach allows you to implement custom validators, correctors and storage value modifiers
[Validator], [Corrector], [StorageValueModifier] example
public abstract class Person: DataObject{ [Validator(typeof(DisallowLessThan), "Abraham")] [Validator(typeof(DisallowGreaterThan), "Zorro")] [Validator(typeof(DisallowEqualTo), "Guest")] [Validator(typeof(DisallowLongerThan), 32)] [Corrector(typeof(Truncator), 64)] public abstract string FullName {get; set;}
[LoadOnDemand] [StorageValueModifier(typeof(Compressor), CompressionMethod.Zip, CompressionLevel.Best)] public abstract System.Drawing.Image Image {get; set;}}
Add-ons mapAdd-ons map
• Adapter (DataObjects – DataSets gateway)• Offline layer (DataObjects.NET.Offline
namespace)• BindingManager (DataObjects – ASP.NET\
WindowsForms controls gateway)
Adapter componentAdapter component
• DataObjects.NET-DataSetgateway
• VisualStudio.NET integration• Well-known Fill-Update pattern• VersionID/VersionCode
validation (optimistic updates)• Uses TrackingSet on Update method execution –
to properly re-fill all rows related to all changed objects• Mapping:
– Stored in Adapter – Can be automatically generated in VS.NET Adapter designer– Can be automatically generated in the runtime
DataObjects.NET.Offline namespaceDataObjects.NET.Offline namespace
• Advanced implementation of well-known DTO (Data Transfer Object) pattern
• Fill-Update pattern• VersionID\VersionCode validation• Offline analogues of almost all “online” types:
– ObjectSet (Session)– DataObject, DataObjectCollection– Savepoint, QueryResult, …
• Fill descriptors (“what to disconnect?”)• Update actions (“how to send back the updates?”)• Transparent “downloading”• Merge• Savepoints
Offline layer: DTO (data transfer object) pattern concepts, part 1Offline layer: DTO (data transfer object) pattern concepts, part 1
• Server-side objects are MBR objects, so accessing their properties one-by-one may require significant amount of time, since each property access operation is actually executed are remote method invocation– It’s completely unacceptable to bind such objects to
WindowsForms UI controls – remote interaction is quite slow for this
– Moreover, server-side objects (“online” objects) are “live” – i.e. each operation on them requires a transaction. Consequences:
• If we’ll explicitly setup “wide” transaction boundaries (e.g. for the whole form fill\update operation), we’re risking to get large amount of deadlocks
• Otherwise (when no transaction boundaries are explicitly specified) we’re getting another problem: we may display inconsistent data (i.e. a data fetched in different transactions)
• How to solve these problems?
Offline layer: DTO (data transfer object) pattern concepts, part 2Offline layer: DTO (data transfer object) pattern concepts, part 2
• Original DTO pattern (came from EJB) implies that a DTO type should be added for each persistent business entity:– All DTO types are serializable MarshalByValue types– Each DTO holds all property values of corresponding persistent
entity– A set of DTOs 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– Client may modify DTOs and send the modified graph back to
allow application server to detect and apply the changes
• How this pattern solves enumerated problems?– Lots of remote calls are replaced by a single call– DTO graph is formed in a single transaction, so it’s consistent– Updates from changed graph are also extracted in a single
transaction; optimistic locking is usually used to detect update conflicts in this case.
Offline layer: DTO (data transfer object) pattern concepts, part 3Offline layer: DTO (data transfer object) pattern concepts, part 3
Disadvantages of regular DTO pattern:• No possibility to explicitly say which property values should
be available in each particular DTO graph (i.e. all properties of the object are “exported” to it). It’s quite inefficient to always send some large BLOB values to the client, even if it’s known that they aren’t required for the current client-side operation
• No possibility to transparently “download” the data that isn’t available in the DTO graph from the application server
• 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.
Offline layer: ObjectSet as advanced DTO pattern implementation, part 1Offline layer: ObjectSet as advanced DTO pattern implementation, part 1
ObjectSet brings all advantages of regular DTO pattern:– It is serializable, as well as any type it contains– It’s a MarshalByValue object, as well as any of
types it can contain, so it can freely traverse AppDomain boundaries in serialized form
– “Online” types provide ToOffline(…) methods producing their “offline” analogues (export feature)
– ObjectSet.ApplyChanges method sends all changes to the Session object bound to it
Offline layer: ObjectSet as advanced DTO pattern implementation, part 2Offline layer: ObjectSet as advanced DTO pattern implementation, part 2
And in addition, ObjectSet resolved most annoying disadvantages of it:• FillDescriptors provide a way to explicitely 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 used to ensure the integrity of the ObjectSet after such operation. See LoadOptions enumeration for details.
• During the whole lifetime ObjectSet gathers information about all calls to methods of offline types 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
– Moreover, all changes made to online objects during ApplyChanges operation are transparently propagated to the offline entities on its completion!
Offline layer: ObjectSet as advanced DTO pattern implementation, part 3Offline layer: ObjectSet as advanced DTO pattern implementation, part 3
Other ObjectSet features:• Excellent Savepoint support
– ObjectSet supports multiple Savepoints– Savepoint objects internally keeps their 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 those purpose is very similar to TransactionController in
the “online” layer – all base operations “require” 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 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 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 interface– DataObjectCollection type supports ITypedList and IListSource– IBindingList support and support of ITypedList \ IListSource in other types (such as
Offline.QueryResult) is currently in development.
Offline layer: important notes, part 1Offline layer: important notes, part 1
• ObjectSet isn’t an ordered collection similar to QueryResult. Its purpose is to maintain and provide access to a set of Offline.DataObject instances by nearly the same fashion as Session provides access to their online analogues. So ObjectSet is much closer to Session, then to a QueryResult.
• Offline.QueryResult object is analogue of “online” QueryResult – use it if you need to pass an ordered collection of Offline.DataObjects. By the way, you can use even array in this case.
• ObjectSet is very similar to DataSet by its serialization behavior – i.e. when you searialize any object from the ObjectSet, the whole ObjectSet is serialized (since any ObjectSetBoundObject contains a reference to ObjectSet, but ObjectSet has references to all Offline.DataObject instances stored in it). So any ObjectSetBoundObject traverses AppDomain boundaries with its ObjectSet.
Offline layer: important notes, part 2Offline layer: important notes, part 2
• You should properly configure ClientSideCache object to successfully utilize ObjectSets on it - see Demo_DisconnectedSets for examples
• ClientSideCache object is used to cache ObjectModel and proxy assembly with offline type proxies on the client – both these parts can be very large (several megabytes in size), thus they’re delivered from the server just once, and further their locally cached snapshots are used
BindingManager componentBindingManager component
• DataObjects.NET-ASP.NET\WindowsForms controls gateway• VisualStudio.NET integration• Fill-Update pattern• Property-PropertyEditor bindings (in contrast to common Property-
ControlProperty bindings)• Brings two-way bindings to ASP.NET• Binds controls not to only DataObjects.NET types, but to any object
graphs• Supports DataObjects.NET-specific features – [Translatable]
properties, automatic transactions• DataBinder.Eval-like paths• Advanced error reporting via IErrorInfoControls:
– IPropertyErrorInfoControls show property-related errors on updates– IFormErrorInfoControls show combined error reports for the whole update
• BindingManager extenders (IBindingExtenders):– Common property-control binding behavior provided DefaultExtender– VersionCode validation via VersionCodeValidator– Custom (third-party) extenders
BindingManager component: implementation stepsBindingManager component: implementation steps
1. Add BindingManager to Windows or ASP.NET form2. Fill new properties (BindingExpression, BindingProperties) for each
control you want to bind with property of an object. Use DataBinder.Eval-like paths in BindingExpression.
3. Use BindingManager.Fill method to fill the controls with property values
4. Use BindingManager.Update method to fill object properties with values stored in controls
5. Extend\customize BindingManager functionality:– Register extenders for extending BindingManager behavior:
• Built-in: BindingManager.RegisterExtender(new VersionCodeValidator())• Create your own extenders by implementing IBindingExtender
– Implement IBindableControl to add BindingManager support to your custom control (the same can be achieved by implementing IBindingExtender)
– Implement IPropertyErrorInfoControl and IFormErrorInfoControl, and put implementers of these interfaces to the form to show error messages in a custom fashion
BindingManager component: correct updateBindingManager component: correct update
BindingManager component: BLL check failedBindingManager component: BLL check failed
BindingManager component: VersionCode check failedBindingManager component: VersionCode check failed
BindingManager component: .aspx code example
<uc1:PropertyEditorControl id="peName" runat="server" BindingExpression="p.Name"></uc1:PropertyEditorControl><BR>Surname =<asp:textbox id="tbSurname" runat="server" BindingExpression="p.Surname"></asp:textbox><uc1:ErrorInfoControl id=eSurname runat="server"></uc1:ErrorInfoControl><br><br><asp:button id="bSave" runat="server" Text="Save"></asp:button><asp:button id="bReload" runat="server" Text="Reload"></asp:button><br><br><asp:label id="lName" runat="server" Width="160px" Height="20px">Name</asp:label><asp:label id="lSurname" runat="server" Width="152px"
Height="20px">Surname</asp:label><br><hr><asp:Repeater id="repeater" runat="server"> <ItemTemplate> <asp:textbox id="tbFriendName" runat="server" BindingExpression="Container.DataItem.Name"></asp:textbox> <uc1:ErrorInfoControl id="eFriendName" runat="server" BindingExpression="Container.DataItem.Name" DESIGNTIMEDRAGDROP="17"></uc1:ErrorInfoControl> <asp:textbox id="tbFriendSurname" runat="server" BindingExpression="Container.DataItem.Surname"></asp:textbox> <uc1:ErrorInfoControl id="eFriendSurname" runat="server" BindingExpression="Container.DataItem.Surname" DESIGNTIMEDRAGDROP="17"></uc1:ErrorInfoControl><br> </ItemTemplate></asp:Repeater>
BindingManager component: .aspx.cs code example, part 1
private void Page_Load(object sender, System.EventArgs e){ DataObjects.NET.Session s = DataContext.Session; long personId = (long)Application["PersonID"]; p = (Person)s[personId]; if (!IsPostBack) { repeater.DataSource = p.Friends; repeater.DataBind(); bm.Fill(this); }}
private void bReload_Click(object sender, System.EventArgs e) { DataObjects.NET.Session s = DataContext.Session; long personId = (long)Application["PersonID"]; p = (Person)s[personId]; repeater.DataSource = p.Friends; repeater.DataBind(); bm.ClearErrors(this); bm.Fill(this);}
BindingManager component: .aspx.cs code example, part 2
private void bSave_Click(object sender, System.EventArgs e) { DataObjects.NET.Session s = DataContext.Session; long personId = (long)Application["PersonID"]; p = (Person) s[personId]; repeater.DataSource = p.Friends; // NOTE: do not call repeater.DataBind here as it would recreate // child controls and so reading of data would be impossible bm.ClearErrors(this); TransactionController tc = s.CreateTransactionController( DataObjects.NET.TransactionMode.NewTransactionRequired); try { bm.Update(this); if (!errors.HasErrors) { tc.Commit(); repeater.DataSource = p.Friends; repeater.DataBind(); bm.Fill(this); } else tc.Rollback(); } catch { tc.Rollback(); }}
BindingManager component: correct updateBindingManager component: correct update
BindingManager component: BLL check failedBindingManager component: BLL check failed
BindingManager component: VersionCode check failedBindingManager component: VersionCode check failed
BindingManager component: BLL check failedBindingManager component: BLL check failed
This screenshot from our upcoming product shows how BindingManager is used to detect and show errors thrown by BLL code on attempt to assign a new property value. Undelying .ascx control (“Summary” region) has no fill \ update \ error detection code at all – all these tasks are handled by BindingManager that is automatically provided for all regions (by parent .aspx page). Errors are automatically shown by special (usually - invisible) control implementing IXxxErrorInfo interfaces.
BindingManager component: VersionCode check failedBindingManager component: VersionCode check failed
This screenshot shows how BindingManager is used to detect concurrent document update made by other user (via DataObject.VersionCode\CheckVersionCode methods). Undelying .ascx control (“Summary” region) has no fill \ update \ concurrent update detection code at all – all these tasks are handled by BindingManager that is automatically provided for all regions (by parent .aspx page). Errors are automatically shown by special (usually - invisible) control implementing IXxxErrorInfo interfaces.
Thank you for watching!You’re ready to see DataObjects.NET Samples, Manual and .HxS\.Chm Help now
Thank you for watching!You’re ready to see DataObjects.NET Samples, Manual and .HxS\.Chm Help now
Product web site: http://www.x-tensive.com/Products/DataObjects.NET/DataObjects.NET Support Forum: http://www.x-tensive.com/Forum/
Product web site: http://www.x-tensive.com/Products/DataObjects.NET/DataObjects.NET Support Forum: http://www.x-tensive.com/Forum/