Frequently Asked Questions

 

This document contains answers to most frequently asked questions. Please visit our support forum while searching for necessary information:

Support forum: http://www.x-tensive.com/Forum/
There is a "DataObjects.NET FAQ" section also: http://www.x-tensive.com/Forum/viewforum.php?f=10

Forum search page: http://www.x-tensive.com/Forum/search.php

 

Conceptual and common questions. 3

Why did you decide to develop one more ORM tool? 3

What are the main differences between DataObjects.NET and other ORM (Object-Relation Mapping) tools? 3

I don't like the idea of making all of my business objects abstract. This is an unusual approach (for me anyways) and might lead to confusion for my developers.  Is this a hard and fast requirement or is it just a recommendation? Why did you decided to do things  this way? 3

DataObjects.NET usage questions. 5

I have a problem, and it seems DataObjects.NET Manual and CHM Help doesn't help to solve it. What can I do? 5

Why Database Setup Wizard creates empty databases? 5

I can't run Demo_WebTransfers (or DoPetShop). What can be wrong? 5

I recieve InvalidConnectionUrlException on attempt to connect to this URL .... 6

How can I obtain SQL code for domain update rather than have DataObjects perform the update? 6

How can I ensure that only one Domain instance upgrades the database at one time (especially with ASP.NET applications)? 6

When should I apply [Transactional] attribute ([TransactionalAttribute])? 6

Can you give an example of some proxy class? 6

I just discovered this product and was wondering if it's also usable with an existing database. If not, what are the steps to fill the new database with the old info? 8

I want to use IDbConnection/IDbTransaction instances associated with a Session. How I can get these objects? How I can use them safely (to not affect DataObjects.NET's operations)? 9

It seems DataObjects.NET doesn't allow me to create the required database schema (e.g., my schema should include an index on the first N characters of the varchar column). Is this  possible to modify the database schema manually? 9

How can I determine if a specific change (e.g. table creation) in the underlying database has occurred during a call to the Domain.Build method? Is it possible to perform an additional action (e.g. SQL execution) on such an event? 9

Is it possible to update DataObjects.NET-created database using an external tool? 10

Does DataObjects.NET support aggregate queries? 10

How can I build a report with DataObjects.NET? 11

Can I use DataObjects.NET with Microsoft Reporting Services? 11

I want to bind a result of Query.ExecuteXXX(...) to the DataGrid (or some other ASP.NET control supporting data binding). What should I do? 11

Can I add my own constructor to a persistent type (DataObject descendant)? 12

Should I specify a primary key for each persistent type? 12

How can I specify additional parameter (m,n) for a Decimal type? 12

Can I change properties of persistent instances returned by Query.ExecuteXXX(...) method? 12

Updating a record directly, bypassing DO, does not update the FastDataLoad field. DataObjects.NET still returns the old data from the FastDataLoad record. 13

Why DataObjects.NET doesn't like the [Indexed] attribute on a collection property? 13

Why persistent types could be not registered in Domain.Build? No error was generated. 13

What is purpose of persistent interfaces? How can I use this feature in my own application? 13

Why it's necessary to call Session.Persist in DoPetShop before invoking HttpContext.Current.Response.Redirect? 14

How to remove inaccessible objects from QueryResult? I wish to remove all objects active user can't access (hasn't ReadPermission on). 14

Updating DataObjects.NET to later versions makes impossible to load assemblies using an older version unless the older version is kept intact in the same directory. 15


 

Conceptual and common questions

Why did you decide to develop one more ORM tool?

We decided to develop of DataObjects.NET when we began to implement solutions on ASP.NET. After rather short period it became obvious we need very extendable core part that could be used in a vast majority of our projects. The most important requirements for us were:

 

All applications should be based on a set of extendable persistent entities and services operating on them. Their combination should completely form business logic layer (BLL) of the application

DAL should rely on one of well-known RDBMS; it would even be better if several RDBMS could be supported. Since we need full transactional support (durability, isolation, atomicity), this is actually the only good option. Moreover, there are lots of other benefits of using an existing RDBMS, e.g. maintenance cost.

Type information (classes, properties, methods, attributes, etc.) should be the only information source for our persistence framework (ORM). No additional XML files or mapping studio should be required. We don't want to waste our time on maintaining the mappings ourselves.

So our DAL should be capable of automatically evolving the database schema

We should fully support key OOP concepts in DAL, such as inheritance and polymorphism. Few levels in persistent entity's inheritance hierarchy is not what we should dream about.

It would be very convenient if all persistence tasks were handled automatically and transparently. The less Save\Load-like calls we should write in BLL - the better. Complete transparency is ideal solution.

All BLL entities and services should be accessible from outside of the application (e.g. via .NET Remoting) to simplify integration with other products

 

Later we decided to add other requirements to this API:

Generated database should be normalized, "human-readable" and contain structures which are acceptable for use without our DAL (DataObjects.NET)

Automatic persistence should be implemented in the way that doesn't allow it to corrupt the database at any point in time (this means that inconsistent data can't be committed). This is extremely important if we want to give access to our data model to external applications (e.g. external data consumers or scripting module). This requirement can be satisfied supporting nested and automatic transactions.

Multilingual (translatable) properties and different sorting rules (collations) should be supported internally

It would be a good idea - to implement unified instance identification and versioning. As we found later, these features are absolutely necessary to provide the complete support of inheritance and global caching.

Performance should be comparable with standard database access methods. We can even increase it sometimes at the cost of additional memory (caching) or space in the database (FastLoadData). All unnecessary, but resource-intolerant features should be removed or made optional.

In the near future this framework should be extended with an Access Control System (currently this part is already implemented), since almost any complex application has similar feature.

 

There were several ORM tools on the market at the moment, but we couldn't find an API with these capabilities. Most of them (it is understandable) were simply a kind of adapters that actually "translate" DataRows to instances of custom types and provides methods to transfer the changes made to them back.

 

Note: This description was written ~ 2 years ago - now DataObjects.NET development went much further.

 

What are the main differences between DataObjects.NET and other ORM (Object-Relation Mapping) tools?

 

Please refer to the "Benefits" document - it starts from list of the most important and unique features of our product.

 

I don't like the idea of making all of my business objects abstract. This is an unusual approach (for me anyways) and might lead to confusion for my developers.  Is this a hard and fast requirement or is it just a recommendation? Why did you decided to do things this way?

 

DataObjects.NET requires all persistent objects and DataServices to be of abstract class type. Note that it's not required to make abstract all classes that work with persistent types (e.g. presentation or business tier classes).

 

So:

All DataObject and DataService descendants must be abstract;

All other classes can be abstract or not - this depends on your application.

 

We need this to implement the following requirements:

 

Every persistent object is missing the coding logic (persistence logic) that is needed to implement it. DataObjects.NET implements this logic by producing a descendant (DataObjects.NET proxy class) of each persistent type during runtime. Actually the descendants are simple - they mostly pass  control  to  private methods of the DataObject class.

 

Note, that all instances of a persistent class (actually instances of their proxy classes) are bound to Sessions, this is a strict requirement for the persistence layer. Currently DataObjects.NET does not allow the creation of "unbound" instances.

 

The .NET Framework provides another way to implement this behavior (called interception) by way of the ContextBoundObjects class. This solution has the following benefits:

 

It's not required to write abstract classes - developers can use non-abstract   classes   (a TransparentProxy object will be provided automatically by the .NET Framework during type instantiation).

It's possible to implement a set of constructors in this case.

 

The disadvantages of this approach are:

 

It's very slow. The speed of this approach is comparable to the speed of local .NET Remoting interaction (.NET framework uses the  same  interaction  scheme  in  this  case: TransparentProxy intercepts each method call, produces "message" packet, passes this packet to RealProxy, then RealProxy handles this message by calling actual method and/or doing something else, the result is returned by the same scheme). The speed of such call is incomparable (nearly 3000 calls (property reads/writes) per second max, on Intel Pentium IV 2.4GHz) while the speed of a virtual method call is more than 100,000,000 calls per second max).

There is no easy way to intercept a call to some method of the persistent object issued by the method of the same object in this approach e.g.  DataObjects.NET would automatically persist the instance if its property was changed during execution of the overridden method (e.g. event handler) of the persistent class.

There are some other less important issues.

  

Given these factors we decided to use the first scheme.

 


 

DataObjects.NET usage questions

I have a problem, and it seems DataObjects.NET Manual and CHM Help doesn't help to solve it. What can I do? 

Feel free to ask us to help:

Publish comprehensive description of your problem in our Support Forum: http://www.x-tensive.com/Forum/. We normally answer on most of posts within 24 hours.

Or send a comprehensive description of your problem to support@x-tensive.com. We normally answer all e-mails within 24 hours.

 

Other options:

Try to find a solution (by keywords) of your problem in the .HxS/.Chm Help.

Try to find a solution by studying the samples shipped with DataObjects.NET - they cover most common tasks, so it's very possible that your solution (or at least a temporary solution) can be found within them.

Check the Revision History (by keywords.

 

Why Database Setup Wizard creates empty databases?

...It seems is creates two databases (DataObjectsDotNetDemos and DoPetShop) without any tables...

 

Don't worry - it's a normal situation. Any sample builds\upgrades all necessary structures on its startup.

 

I can't run Demo_WebTransfers (or DoPetShop). What can be wrong?

If you're using Microsoft SQL Server, ensure that:

ASPNET (and IIS_WPG group on Windows Server 2003) account have permission to read Demo_WebTransfers (DoPetShop\Web) folder and all contained files;

Demo_WebTransfers (DoPetShop\Web) folder is mapped to corresponding IIS virtual folder;

Microsoft SQL Server service is started locally;

DataObjectsDotNetDemos\DoPetShop database exist on Microsoft SQL Server;

Microsoft SQL Server is configured to allow full access to this database for ASPNET account (IIS_WPG group on Windows Server 2003) via trusted connection;

Web.config contains the following tag (it should not be commented out):
<add key="ConnectionURL" value="mssql://localhost/DataObjectsDotNetDemos" />

                      

And if you are using Oracle, ensure that:

Oracle Client software is installed;

ASPNET (and IIS_WPG group on Windows Server 2003) account have permission to read Demo_WebTransfers (DoPetShop\Web) folder and all contained files;

Demo_WebTransfers (DoPetShop\Web) folder is mapped to corresponding IIS virtual folder;

DEMOS (DOPETSHP) database instance is created, properly configured (read Running DataObjects.NET Samples section) and running;

For Demo_WebTransfers: Web.config contains the following tag (it shouldn't be commented out):
<add key="ConnectionURL" value="oracle://admin:admin@localhost/Demos" />

For DoPetShop: Web.config contains the following tag (it shouldn't be commented out):
<add key="ConnectionURL" value="oracle://admin:admin@localhost/DoPetShp" />

 

Note that you should restart Demo_WebTransfers (DoPetShop) manually if it fails to start or if you see that it is constantly producing pages with this error message: InvalidOperationException("Domain is inaccessible."). To restart the application, you can perform one of the following actions:

Change (by simply opening and saving it) its Web.config file - ASP.NET will automatically restart application on such event;

Restart IIS by typing in command prompt:
net stop "World Wide Web Publishing" (net stop "World Wide Web Publishing Service" on Windows Server 2003)
net start "World Wide Web Publishing" (net start "World Wide Web Publishing Service" on Windows Server 2003)

 

I recieve InvalidConnectionUrlException on attempt to connect to this URL ...

InvalidConnectionUrlException is thrown on wrong URL format. Consider the following fact: you should use URL encoding to inject a character like "-" ("/",":","@", etc...) into Domain.ConnectionUrl. For example, if you wish to connect to "mssql://my-server/myDatabase", you should use the following ways of doing this:

 

Specifying this URL: "mssql://my%2Dserver/myDatabase"

Modifying Domain.ConnectionInfo property after Domain creation:
Domain d = new Domain("mssql://notImportantPart/myDatabase");
d.ConnectionInfo.Host = "my-server";

 

Remark: URL encoding means that DataObjects.NET substitutes %xx (or %xxxx) sequences in each part of URL to characters with xx (or xxxx) hexadecimal Unicode code.

 

How can I obtain SQL code for domain update rather than have DataObjects perform the update?

Use DomainUpdateMode.Skip or DomainUpdateMode.Block to skip/block the actual update of the database (specify it as argument of the Domain.Build method). Then you can use the following code to get the schema update SQL code:

 

using (StreamWriter sw = new StreamWriter("SchemaUpdateSQL.txt")) {

  sw.WriteLine("Database Schema Update SQL:");

  foreach (string s in domain.UpdateActions.GetSqlCommands(domain))

    sw.WriteLine(s+"\n");

}

 

How can I ensure that only one Domain instance upgrades the database at one time (especially with ASP.NET applications)?

Look at the "DataObjects.NET PetShop" or "Demo_WebTransfers" samples for a solution of this problem (Global.asax.cs, Application_Start method).

 

When should I apply [Transactional] attribute ([TransactionalAttribute])?

1) You shouldn't use this attribute with [Persistent] attribute and you shouldn't apply it on persistent fields. They act as if they are marked by [Transactional(TransactionMode.TransactionRequired)].

2) By default any other public methods or properties (including non-virtual), that haven't been marked by this attribute acts as if they are marked by [Transactional(TransactionMode.TransactionRequired)].  Because of this you can receive an exception during domain building if the model contains some public non-virtual method. We decided to use this behavior even with non-virtual methods to ensure that such methods really don't require a transaction. You should apply [Transactional(TransactionMode.Disabled)] to specify this. The same behavior is applicable to the DataService descendants.

3) Consider that all other methods (including non-DataObject methods, e.g. Query methods) that seems to require some type of automatic transaction already contains required code.

 

Can you give an example of some proxy class?

Yes. Note: this proxy code can vary with new DataObjects.NET versions.

 

public class Account_DataObjectsDotNetProxy : Demo_Transfers.Model.Account {

 

  public Account_DataObjectsDotNetProxy() :

      base() {

  }

 

  public override string Title {

    get {

      return ((string)(this.GetProperty("Title", null)));

    }

    set {

      this.SetProperty("Title", null, value);

    }

  }

 

  public override long Balance {

    get {

      return ((long)(this.GetProperty("Balance", null)));

    }

    set {

      this.SetProperty("Balance", null, value);

    }

  }

 

  public override void TransferTo(Demo_Transfers.Model.Account a1, long a2) {

    DataObjects.NET.TransactionController transactionController =

      new DataObjects.NET.TransactionController(this.Session,

          ((DataObjects.NET.TransactionMode)(1)),

          ((System.Data.IsolationLevel)(-1)));

  Reprocess:

    try {

      base.TransferTo(a1, a2);

      transactionController.Commit();

    }

    catch (System.Exception e) {

      if (transactionController.Rollback(e, true)) {

        goto Reprocess;

      }

      throw e;

    }

  }

 

...

 

}

 

Example code for [Nullable] property:

 

public class Person_DataObjectsDotNetProxy : Tests.Core.PersistentModel.Person {

 

  ...

 

  public override System.DateTime DOB {

    get {

      object o = this.GetProperty("DOB", null);

      if ((o == null)) {

        return ((System.DateTime)(this.Type.Fields[8].CreateNullSubstitution()));

      }

      else {

        return ((System.DateTime)(o));

      }

    }

    set {

      this.SetProperty("DOB", null, value);

    }

}

 

...

 

}

 

You can get the source code of an assembly with proxy classes by inspecting the Domain.ProxyBuilderResults.Source property after Domain.Build call or by setting         Domain.DebugInfoOutputFolder property before Domain.Build call;

 

I just discovered this product and was wondering if it's also usable with an existing database. If not, what are the steps to fill the new database with the old info?

DataObjects.NET doesn't generally allow making it work with an existing database (exceptions are: existing, but DataObjects.NET-built database or a database with very similar structure). There are several serious reasons preventing us from support of this feature. Most important of them are:

 

Complete inheritance support: DataObjects.NET has excellent support of inheritance; you can find additional information on this subject in the "Benefits" document (http://www.x-tensive.com/Products/DataObjects.NET/Benefits/). This feature requires a unified structure of tables storing objects and relations between them + unified instance identification. DataObjects.NET uses 64-bit object identifiers (primary keys) unique in the storage scope. Refer to "Instance Identification" section in the http://www.x-tensive.com/Products/DataObjects.NET/Overview/ - there is an explanation of why this is necessary.

DataObjects.NET stores additional fields for each object (VersionID, TypeID, Permissions and FastLoadData) to execute quieries faster and utilize object caching. First 2 of these fields are vital for DataObjects.NET, security permissions can be unused, and FastLoadData is automatically maintained (updated or rebuilt if necessary) by DataObjects.NET. Necessity of these fields is one more factor made us to use unified database schema.

 

This expresses one of our intensions: to provide as reach support of object-oriented development and set of other features (for example, security system) for persistent objects, as it's possible, even when some inevitable restrictions (inability to work with custom database schemas) should be added to implement this.

 

And the answer to the second question:

 

The easiest way to implement a data import tool is to implement it using DataObjects.NET (this allows easily persist the results of conversion). Let's think you've already implemented all persistent types required for your application, and you're able to start a "clean" DataObjects.NET Domain utilizing these types. So your task is to fetch a data from some existing database and convert it to appropriate persistent instances.

 

Practically you have two ways of implementing this task:

 

a) Recursive upload process: you should write a method ("import method") for each of your persistent type allowing to create an instance of this type by its original relational data (e.g. by a primary key - in this case this method should be able to fetch all necessary data; or by DataRow \ set of DataRows - in this case this data should be fetched prior to its invocation).

 

There is one problem we should solve: this method should be able to establish relations between created object and other objects, but these "other" objects may not being created (imported) yet. So you should create an additional globally available method allowing to get a persistent object that corresponds to some foreign key: DataObject GetDataObjectByTableAndForeignKey(string tableName, string fkValue)) (just an example). This method should return an existing DataObject instance, if it was already created, and execute appropriate "import method" otherwise. Because of that this process is called recursive.

 

So the complexity of this task is nearly proportional to the number of your persistent types.

 

b) Two phase import process: first phase implies importing of all instances without establishing any references between them; second phase implies establishing these references only. Primary difference here is that this process doesn't require any recursion. You should write two methods per each object in this case: "import data" and "import relations" methods (arguments of these methods can be the same).

 

Common idea: we recommend you to implement an import tool as special DataService (for example, ImportService).

 

And the second option: implementing a data import tool without use of DataObjects.NET. Let's think you've already implemented all persistent types required for your application, and already started a "clean" DataObjects.NET Domain utilizing these types. So you already know a structure of destination database.

 

This way seems more difficult for us, since DataObjects.NET provides excellent way of modifying its database (by simply modifying properties of persistent objects). But nevertheless you can decide to go by this way.

 

You can use Microsoft DTS in this case. Note that:

You should choose object identifiers (IDs) unique in the storage scope;

You can assign any VersionID to your objects during this process, but of course none of DataObjects.NET-based applications should use this database during import;

You should set up correct TypeID values (their relations to class names are stored in the doSysTypes table);

Permissions and FastLoadData should be set to NULL. DataObjects.NET rebuilds FastLoadData automatically; Permissions equal to NULL means that security permissions are inherited from the SecurityParent object (i.e. finally from the SecurityRoot object).

 

I want to use IDbConnection/IDbTransaction instances associated with a Session. How I can get these objects? How I can use them safely (to not affect DataObjects.NET's operations)?

1) You should read Session.RealConnection/Transaction.RealTransaction properties to get the required interfaces. Ensure that Session.SecurityOptions allows this, or temporary disable the security system (see SessionBoundObject.DisableSecurity) to perform this (this is possible only for the DataObject\DataService methods).

2) To use them safely, you should always take into account the following rules:

Use Session/Transaction/Savepoint types to begin/commit/rollback transactions and work with Savepoints. Do not execute "Begin tran" - like code in IDbConnection or use IDbConnection.BeginTransaction. DataObjects.NET should control all operations related to beginning and finishing transactions or rollbacks to savepoints.

Do not try to open or close connections manually (do not use IDbConnection.Open or IDbConnection.Close). You should begin transaction (e.g. use Session.BeginTransaction or by using automatic transaction), to ensure that a connection is opened.

Close all of your IDataReaders at the end of an operation (don't forget to use try-finally or using statements to ensure that their instances will be closed).

Generally, it's a bad idea to modify DataObjects.NET tables this way. We have designed DataObjects.NET to perform this task better anyway. But it may be a good idea to use this approach to collect some aggregate data (e.g. for reporting purposes).

 

It seems DataObjects.NET doesn't allow me to create the required database schema (e.g., my schema should include an index on the first N characters of the varchar column). Is this  possible to modify the database schema manually?

Yes. You can modify the database schema manually (e.g. add index on the part of the column - such index definitions are currently unsupported by DataObjects.NET) and use DomainUpdateMode.Skip to skip schema update.

 

How can I determine if a specific change (e.g. table creation) in the underlying database has occurred during a call to the Domain.Build method? Is it possible to perform an additional action (e.g. SQL execution) on such an event?

Yes. You should traverse Domain.UpdateActions collection after your call to the Domain.Build method to find this information. After that you can create a Session, read the Session.RealConnection property and perform the necessary additional operation over it. "DataObjects.NET PetShop" sample uses this approach to detect when it should upload the data to the database.

 

Is it possible to update DataObjects.NET-created database using an external tool?

Yes, but you should take into account the following:

A data model should be valid on the commit of each transaction (it's recommended to use IsolationLevel.RepeatableRead). This means that it should not contain references to instances that do not exist, referenced instances must have valid type and so on.

Don't forget that every instance is split into several tables, one per each ancestor type excluding System.Object and MarshalByRefObject. Each instance also has a part in the table related to DataObject type.

Set FastLoadData column (in DataObject-related table) value to NULL or empty byte sequence in the row of updated/inserted instance. DataObjects.NET maintains serialized version of instance fields (excluding [LoadOnDemand]-marked fields) in this column to avoid additional queries during instantiation of the query result (result can include descendants of queried type, so additional queries are normally required to fetch their fields). So DataObjects.NET loads every instance by one of two ways:
a) using
FastLoadData, if it is non-empty and valid; in this case no additional queries are performed.
b) without use of
FastLoadData (or when it is empty or NULL); in this case DataObjects.NET can perform one additional query to fetch all required instance properties.
Note, that
FastLoadData is automatically maintained by DataObjects.NET. DataObjects.NET updates the value of this property on any persisted operation or even on the instance fetch (if FastLoadData of this instance is empty or NULL, with 10% probability).

Increment VersionID column (in DataObject-related table) value in the row of updated/inserted instance.

If the updated instance is FtObject descendant, reset its FtRecordIsUpToData column value in the FtObject - related table.

If the updated instance isn't an FtObject descendant but implements IFtObject interface, reset its FtRecordIsUpToData column value in the table related to the first type in its inheritance branch implementing IFtObject interface.

Generally we do not recommend updating the database manually unless it is absolutely necessary (this recommendation shouldn't be extended to read-only database access).

 

Does DataObjects.NET support aggregate queries?

DataObjects.NET does not provide internal support for aggregate queries (except counting of instances and using aggregate functions in subqueries in where clause).

 

There is one important reason preventing us from even allowing such queries: it's impossible to check security restrictions on any aggregate query, since it runs completely on the database server side. So it's possible to gain access to a protected data by running correctly designed aggregate query, and moreover, almost any protected data can be retrieved by this way. May be you have noticed that DataObjects.NET also doesn't allow to retrieve fields of objects from queries - you're always getting DataObject instances, and reasons for doing this are the same.

 

Practically this means you shouldn't provide unmanaged access to any aggregate information for anyone. This can be achieved by providing access to any aggregate data via methods\properties of your persistent model that perform all necessary security checks. Of course some tools (e.g. report builders) may access your database directly to get this information, and in this case you should protect the results of their work (reports). For example, Microsoft Reporting Services supports such scenarios directly.

 

And finally - how to run an aggregate query directly from the transactional method of your persistent model? Let's look on example:

 

[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();

    }

  }

}

 

You can implement a set of similar members in the persistent model to unify access to the database - usually such methods should be marked as [Transactional].

 

How can I build a report with DataObjects.NET?

You can use any standard tool to build your reports, such as Microsoft Reporting Services.

 

Can I use DataObjects.NET with Microsoft Reporting Services?

Yes - simply because DataObjects.NET uses regular relational databases (e.g. Microsoft SQL Server) to store persistent entities. So you can access the information stored in it by any standard way - e.g. a lot of reports for Reporting Services are based on a single SQL query, and you can write this query in case with DataObjects.NET also.

 

The only difference with standard database application in this case is that DataObjects.NET builds\maintains the schema of your database and changes the information stored in it "itself", rather then you perform these tasks manually. But of course you can query this database as usual (i.e. by SQL queries) - and it's enough easy to write such queries in case with DataObjects.NET-maintained database.

 

Recommendations:

Use views rather then tables in your reporting queries - each view consolidates the data for a single type (or collection) spreaded between different tables;

Use Enterprise Manager\Query Analyzer to study the structure of your database. Don't worry - DataObjects.NET uses a limited set of rules while building its schema, so generally you should spend nearly 1 hour to completely understand the schema of your database.

 

I want to bind a result of Query.ExecuteXXX(...) to the DataGrid (or some other ASP.NET control supporting data binding). What should I do?

Try to study List.aspx.cs and List.aspx files from Demo_WebTransfers for example. Most important parts of this code:

 

List.aspx.cs:

 

public class ListForm: System.Web.UI.Page

{

  ...

  protected QueryResult accounts;

   

  void Refresh()

  {

    accounts = DataContext.Session.CreateQuery(

      "Select Account instances order by {Title}, {ID}").Execute();

    ...

    this.DataBind();

    ...

  }

 

  protected override void OnPreRender(EventArgs e)

  {

    base.OnPreRender(e);

    Refresh();

  }

}

 

Note: it's not required to override the OnPreRender(...) method, you can do the same within e.g. OnLoad(...).

 

List.aspx.cs:

 

<asp:DataGrid id=cAccounts runat="server" DataKeyField="ID" DataSource="<%# accounts %>" Width="90%" AutoGenerateColumns="False" CssClass="Std" EnableViewState="False">

  <SelectedItemStyle BackColor="PaleGoldenrod"></SelectedItemStyle>

  <HeaderStyle CssClass="Head"></HeaderStyle>

  <Columns>

    <asp:BoundColumn DataField="Title" HeaderText="Title">

      <HeaderStyle HorizontalAlign="Center" Width="60%"></HeaderStyle>

      <ItemStyle Font-Bold="True"></ItemStyle>

    </asp:BoundColumn>

    <asp:BoundColumn DataField="Balance" HeaderText="Balance" DataFormatString="${0:N2}">

      <HeaderStyle HorizontalAlign="Center" Width="20%"></HeaderStyle>

      <ItemStyle HorizontalAlign="Right"></ItemStyle>

    </asp:BoundColumn>

  </Columns>

  <PagerStyle BackColor="Beige" Mode="NumericPages"></PagerStyle>

</asp:DataGrid>

 

Can I add my own constructor to a persistent type (DataObject descendant)?

Normally... no. Constructors are not allowed for DataObject or DataService descendants. But you can implement new or override existing OnCreate method to implement desired behavior. DataObjects.NET invokes the best matching method during Session.CreateObject(...) call.

 

Should I specify a primary key for each persistent type?

No. All persistent types already have a primary key (ID property). Moreover, the value of this property is unique within the database scope.

 

How can I specify additional parameter (m,n) for a Decimal type?

Currently this is not supported. We supported this in the earlier versions of DataObjects.NET, but removed this feature to simplify the development of additional drivers for Oracle and SAP DB. Decimals are always represented by dec(28,10).

 

Can I change properties of persistent instances returned by Query.ExecuteXXX(...) method?

Yes - and these changes will be persisted. You can be sure that if you've got a reference to some persistent instance by some way, you can use it by any possible way - e.g. you can change its properties, add it to some collection of another persistent instance, call its Remove() method and so on. Also note, that DataObjects.NET guarantees that only one instance with particular ID exists in each Session. So if you have a reference to the instance with ID=2212, you can be sure that any other reference to instance with ID=2212 will be the same (in the same Session).

 

Updating a record directly, bypassing DO, does not update the FastDataLoad field. DataObjects.NET still returns the old data from the FastDataLoad record.

Yes, an application that performs direct record updates (bypassing DataObjects.NET) normally can't update the FastLoadData properly. The solution in this case is to set FastLoadData to NULL manually by the updating application. NULL, empty byte sequence or invalid value in the FastLoadData property makes DataObjects.NET fetching all required data by an additional query. In this case there is a 50% probability (it can be changed - see Session.InconsistentDataUpdateProbability property) that DO.NET will update empty FastLoadData property by the correct value. Additionally you can do the same operation manually for the particular DataObject instance by invoking DataObject.UpdateInconsistentData() method.

 

We decided not to use the triggers that reset this column on record updates primarily because this will reduce the performance of the DataObjects.NET-based clients (because they update this column correctly).

 

Why DataObjects.NET doesn't like the [Indexed] attribute on a collection property?

The following property declaration generates a build error...

 

[Indexed]

[ItemType(typeof(Cat))]

public abstract DataObjectCollection Children {get;}

 

The error occurs because collections are indexed automatically.

 

Typically a collection is represented in a database as a separate table having ID-1 column for objects' IDs in which the collection is declared and ID-2 column for objects referenced from this collection. Two indexes are created automatically for such a table. The first is built on {ID-1, ID-2} columns; the second - on {ID-2} column. No other indexes are needed for selections by ID-1, or by ID-2, or by both them.

 

There is an exception from this rule, when a collection is marked as paired to a single property. Then actually no separate table is created, the information stored in that single property is enough for such association. In this case additional indexes are NOT created. A single reference property must be indexed manually.

 

Why persistent types could be not registered in Domain.Build? No error was generated.

The possible reason is that your assembly with persistent types could be not loaded by that moment. You can load it manually before the type registration:

 

  // Load the My.Model assembly.

  AppDomain currentDomain = AppDomain.CurrentDomain;

  currentDomain.Load("My.Model");

  

  domain.RegisterTypes("My.Model");

  domain.RegisterServices("My.Model");

 

You can use this code to study what assemblies are currently loaded:

 

  System.Reflection.Assembly[] assems = currentDomain.GetAssemblies();

  Console.WriteLine("List of assemblies loaded in current appdomain:");

    foreach (System.Reflection.Assembly assem in assems)

      Console.WriteLine(assem.ToString());

 

What is purpose of persistent interfaces? How can I use this feature in my own application?

Persistent interfaces allow to execute queries against objects that supports them. It's better to explain this on example.

 

Let's think we have IHasCreateModifyDates persistent interface. It declares two persistent properties: CreateDate and ModifyDate. These properties can be automatically maintained by your persistent objects (e.g. you can even put a code that automatically updates these properties in some of your base types - such code should check if current object supports IHasCreateModifyDates and perform appropriate updates of CreateDate\ModofyDate properties in OnCreate\OnPropertyChanged\OnPropertyContentChanged events).

 

So you should simply declare that some of your types supports IHasCreateModifyDates (+ add 2 corresponding persistent properties of this interface) to get a complete support of desired behavior!

 

Moreover, you can execute a Query like "Select IHasCreateModifyDate objects where {CreateDate}>@MinCreateDate" - it will return all objects that supports this interface and satisfy specified criteria.

 

Another useful example: you can implement IHasOwner interface + provide its automatic support in some base class to allow some of your objects to have an Owner.

 

We use set of such interfaces in our current project, e.g. INode, IItem, IFolder, IHasName, IHasSystemName, INotRelocatable, IHasTitle, IHasDescription, etc... All support code required to automatically update persistent properties of these interfaces is located in Node class (base class of our inheritance hierarchy). So generally we should just declare that some type supports any of these interfaces to get a complete automatic support of its behavior!

 

Why it's necessary to call Session.Persist in DoPetShop before invoking HttpContext.Current.Response.Redirect?

You can find a code like this in DoPetShop, for example, in EditAccount.aspx.cs:

 

// This is necessary, because new thread will be started on

// further redirect, and possibly it will try to access some

// objects modified here - we should ensure that it can do

// this only after this thread and transaction will be finished.

DataContext.Session.Persist();

HttpContext.Current.Response.Redirect(URL_ACCOUNTUPDATE, true);

 

It's necessary to call DataContext.Session.Persist() before any redirect because this makes DataObjects.NET to flush all delayed updates cached in current Session and thus lock all necessary objects (rows\tables\index ranges) on the database server exclusively, so underlying objects will be available for other transactions only when the current transaction will be committed or rolled back.

 

Otherwise it's possible that these objects will be locked (in share mode) by the transaction created for the redirect-related request first, since ASP.NET executes all requests concurrently. In this case this request will not notice the changes made by the previous request (that initiated it) - usually this isn't the situation we want to have.

 

How to remove inaccessible objects from QueryResult? I wish to remove all objects active user can't access (hasn't ReadPermission on).

ReadPermission is checked only on attempt to read any property (except ID, TypeID, VersionID). You can find this code in the DataObject.cs file (see Available Source Code Package). So you should apply a filter (we'd prefere to implement it as the method of special DataService) to your QueryResult to remove all objects where current user doesn't have a ReadPermission.

 

ObjectListFilter.FilterByPermission in example below copies (adds to) all objects with allowed requiredPermission from source QueryResult to target QueryResult.

 

  [ServiceType(DataServiceType.Shared)]

  public abstract class ObjectListFilter: DataService

  {

    public virtual void FilterByPermission(QueryResult source,

      QueryResult target, IPermission requiredPermission)

    {

      int cnt = source.Count;

      for (int i = 0; i<cnt; i++) {

        DataObject o = source[i];

        if (o.IsAllowed(requiredPermission))

          target.Add(o);

      }

    }

  }

 

Updating DataObjects.NET to later versions makes impossible to load assemblies using an older version unless the older version is kept intact in the same directory.

It's usual case with .NET assemblies. We increase assembly version with each release, but dependent assemblies contain a reference to old version of DataObjects.NET assembly in manifest. You have two options to solve this problem:

 

Recompile dependent assembly with new version of DataObjects.NET. In this case its manifest will be updated.

You can add <bindingRedirect> element to the application or machine configuration file to achieve the same without recompilation. Application configuration file has "appName.exe.config" name (where appName is the name of application) for executable-hosted application, and Web.config for ASP.NET application.

 

Example of configuration file for full version of DataObjects.NET (its assembly has public token key "fe8e3137cfa4dc63"):

 

<?xml version="1.0"?>

<configuration>

  ...

  <runtime>

     ...

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

      <dependentAssembly>

        <assemblyIdentity name="DataObjects.NET" publicKeyToken="fe8e3137cfa4dc63" culture="neutral"/>

        <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="2.0.7.1"/>

      </dependentAssembly>

    </assemblyBinding>

  </runtime>

  ...

</configuration>

 

Remarks:

Trial version of DataObjects.NET has public token key "eb7392384bd0f0a6";

"sn -T assemblyName.exe" prints public token key of assembly with name assemblyName.exe;

oldVersion attribute can contain exact version number (2.0.6.1) or a range of versions (2.* or 2.0.6.1-2.0.10.1);

You can declare several <bindingRedirect> elements in a single <dependentAssembly> element;

You can find additional information (for example, how .NET Framework locates required assembly in this case) in MSDN.