Static mapping
Tip
Before read this topic be sure to already readied Glossary and Common mapping rules contains base information to better understanding this.
Static mapping is defined using attributes of mapped class and it properties.
Static mapping sample
Here is example of data object static mapping. Of course, real mapping is much simpler then this, but as example of XData capabilities it's really representative...
SQL representation of following sample:
with recursive T (catalogue_id, code, name, parent_id)
as ((SELECT R.catalogue_id AS R_catalogue_id,
R.code AS R_code,
R.name AS R_name,
R.parent_id AS R_parent_id
FROM T_CATALOGUE R
WHERE ((@FilterByRoot is not null
and R.catalogue_id = @FilterByRoot)
OR (@FilterByRoot is null
and R.parent_id is null))) UNION (SELECT S.catalogue_id AS S_catalogue_id,
S.code AS S_code,
S.name AS S_name,
S.parent_id AS S_parent_id
FROM T_CATALOGUE S, T T
WHERE S.parent_id = (T.catalogue_id)))
SELECT CF.CUSTOMER_ID AS CF_CUSTOMER_ID,
CF.DOC_CUST_ID AS CF_DOC_CUST_ID,
CF.DOC_CUST_TYPE_ID AS CF_DOC_CUST_TYPE_ID,
CF.DOC_ID AS CF_DOC_ID,
CT.CUSTOMER_ID AS CT_CUSTOMER_ID,
CT.DOC_CUST_ID AS CT_DOC_CUST_ID,
CT.DOC_CUST_TYPE_ID AS CT_DOC_CUST_TYPE_ID,
CT.DOC_ID AS CT_DOC_ID,
CAST((SELECT Sum(A.amount) AS A_amount
FROM T_DOC_SPEC S
JOIN T_DOC_SPEC_AMOUNT A ON A.DOC_SPEC_ID = S.DOC_SPEC_ID
WHERE S.doc_id = S.doc_id) AS NUMERIC(17, 5)) AS doc_amount /* DocAmount */,
D.doc_date AS D_doc_date /* DocDate */,
D.doc_id AS D_doc_id /* DocId */,
D.DOC_TYPE_ID AS D_DOC_TYPE_ID,
D.generation AS D_generation /* Generation */,
D.changed AS D_changed /* Changed */,
D.author AS D_author /* Author */,
DD.delivery_date AS DD_delivery_date /* DeliveryDate */,
DD.delivery_type AS DD_delivery_type /* DeliveryType */,
DD.DOC_ID AS DD_DOC_ID,
DS.DOC_DOC_STATE_ID AS DS_DOC_DOC_STATE_ID,
DS.DOC_ID AS DS_DOC_ID,
DS.DOC_STATE_ID AS DS_DOC_STATE_ID,
F.CODE AS F_CODE,
F.CUSTOMER_ID AS F_CUSTOMER_ID,
F.name AS F_name /* CustomerFrom */,
H.H_doc_id AS H_H_doc_id,
H.H_history_date AS H_H_history_date /* DocLastChange */,
N.DOC_ID AS N_DOC_ID,
N.numb AS N_numb /* DocNumb */,
S.code AS S_code /* DocStateCode */,
S.DOC_STATE_ID AS S_DOC_STATE_ID,
S.name AS S_name /* DocState */,
SC.DOC_ID AS SC_DOC_ID,
SC.scan AS SC_scan /* Scan */,
SR.DOC_ID AS SR_DOC_ID,
SR.source AS SR_source /* Source */,
ST.S_doc_id AS ST_S_doc_id,
T.CODE AS T_CODE,
T.CUSTOMER_ID AS T_CUSTOMER_ID,
T.name AS T_name /* CustomerTo */
FROM T_DOC D
JOIN T_DOC_DOC_STATE DS ON DS.doc_id = D.doc_id
JOIN T_DOC_STATE S ON S.DOC_STATE_ID = DS.DOC_STATE_ID
JOIN T_DOC_NUMBER N ON N.doc_id = D.doc_id
JOIN T_DOC_CUST CF ON CF.doc_id = D.doc_id
JOIN T_DOC_CATALOGUE DC ON DC.doc_id = D.doc_id
JOIN T_CATALOGUE U ON DC.catalogue_id = U.catalogue_id
JOIN T_CUSTOMER F ON F.CUSTOMER_ID = CF.CUSTOMER_ID
JOIN T_DOC_CUST CT ON CT.doc_id = D.doc_id
JOIN T_CUSTOMER T ON T.CUSTOMER_ID = CF.CUSTOMER_ID
LEFT OUTER JOIN T_DOC_SCAN SC ON SC.doc_id = D.doc_id
LEFT OUTER JOIN T_DOC_SOURCE SR ON SR.doc_id = D.doc_id
LEFT OUTER JOIN T_DOC_DELIVERY DD ON DD.doc_id = D.doc_id
LEFT OUTER JOIN (SELECT H.doc_id AS H_doc_id,
H.history_date AS H_history_date
FROM T_DOC_HISTORY H) H ON D.doc_id = H.H_doc_id
JOIN (SELECT S.doc_id AS S_doc_id
FROM T_DOC_SPEC_TYPE ST
JOIN T_DOC_SPEC S ON S.DOC_SPEC_TYPE_ID = ST.DOC_SPEC_TYPE_ID
WHERE ST.code = @FilterBySpecTypeCode) ST ON D.doc_id = ST.S_doc_id
WHERE D.DOC_TYPE_ID = (select doc_type_id
from T_DOC_TYPE
where code='INVOICE')
AND CF.DOC_CUST_TYPE_ID = (select doc_cust_type_id
from T_DOC_CUST_TYPE
where code='FROM')
AND CF.DOC_CUST_TYPE_ID = (select doc_cust_type_id
from T_DOC_CUST_TYPE
where code='TO')
AND D.doc_id = @FilterByDocId
AND DC.catalogue_id in (SELECT T.catalogue_id AS T_catalogue_id FROM T T)
Mapping sample:
[DataObject("D", LogicAssemblyName = "XDataObjectTest"),
DataTable("T_DOC", "D"),
DictionaryFilter("T_DOC_TYPE", "doc_type_id", "code", "INVOICE", "D"),
DataTable("T_DOC_DOC_STATE", "DS", Parent = "D"),
Link("D", "doc_id", "DS"),
DataTable("T_DOC_STATE", "S"),
Link("DS", "doc_state_id", "S"),
DataTable("T_DOC_NUMBER", "N", Parent = "D"),
Link("D", "doc_id", "N"),
DataTable("T_DOC_CUST", "CF", Parent = "D"),
Link("D", "doc_id", "CF"),
DataTable("T_CUSTOMER", "F"),
Link("CF", "customer_id", "F"),
DictionaryFilter("T_DOC_CUST_TYPE", "doc_cust_type_id", "code", "FROM", "CF"),
DataTable("T_DOC_CUST", "CT", Parent = "D"),
Link("D", "doc_id", "CT"),
DataTable("T_CUSTOMER", "T"),
Link("CT", "customer_id", "T"),
DictionaryFilter("T_DOC_CUST_TYPE", "doc_cust_type_id", "code", "TO", "CT"),
DataTable("T_DOC_SCAN", "SC", Parent = "D"),
Link("D", "doc_id", "SC", Operation = FilterOperation.OuterJoin),
DataTable("T_DOC_SOURCE", "SR", Parent = "D"),
Link("D", "doc_id", "SR", Operation = FilterOperation.OuterJoin),
DataTable("T_DOC_DELIVERY", "DD", Parent = "D"),
Link("D", "doc_id", "DD", Operation = FilterOperation.OuterJoin),
DataTable("T_DOC_CATALOGUE", "DC", Parent = "D"),
Link("D", "doc_id", "DC"),
DataTable("T_CATALOGUE", "U"),
Link("U", "catalogue_id", "DC"),
Subquery("A", typeof(DocSpecAmounts), "Amount", Grouping = DataGrouping.Sum),
SubqueryLink("A", "DocId"),
InnerView("H", typeof(DocLastChange)),
SubqueryLink("H", "DocId", Operation = FilterOperation.OuterJoin),
ExternalLink(typeof(InvoiceSpec), "DocId"),
InnerView("ST", typeof(DocBySpecType)),
SubqueryLink("ST", "DocId"),
Subquery("UT", typeof(CatalogueTree), "CatalogueId"),
SubqueryFilter("UT", "DC", "catalogue_id", Operation = FilterOperation.In),
Column("DocId", typeof(long?), "D", Flags = DataPropertyFlag.Id),
Column("DocStateId", typeof(long), "S"),
Column("FromCustomerId", typeof(long), "F"),
Column("ToCustomerId", typeof(long), "T"),
Column("CatalogueId", typeof(long), "U"),
ColumnDefault("DocId", DefaultType.AutoIncrement)]
public class Invoice: IDataObject
{
[Property("S", "code")]
public string DocStateCode { get { return this.GetProperty(x => x.DocStateCode); } }
[Property("S", "name"),
LinkProperty("Name"),
LinkProperty(Property = "DocStateId"),
LinkProperty("Code", "DocStateCode")]
public Link<string, DocState> DocState { get; set; }
[Property("U", "name"),
LinkProperty(Property = "CatalogueId"),
LinkProperty("Name")]
public Link<string, Catalogue> DocCatalog { get; set; }
[Property("N", "numb")]
public string DocNumb { get; set; }
[Property("D"),
PropertyDefault(DefaultType.CurrentDate)]
public DateTime? DocDate { get; set; }
[Property("D", Flags = DataPropertyFlag.ConcurrencyToken),
PropertyDefault(DefaultType.AutoIncrement)]
public long Generation { get { return this.GetProperty(x => x.Generation); } }
[Property("D"),
PropertyDefault(DefaultType.CurrentDateTime, DefaultFeature = DefaultFeature.UseOnUpdate)]
public DateTime Changed { get { return this.GetProperty(x => x.Changed); } }
[Property("D"),
PropertyDefault(DefaultType.UserName, DefaultFeature = DefaultFeature.UseOnUpdate)]
public string Author { get { return this.GetProperty(x => x.Author); } }
[Property,
PropertyExpression("A", DataExpressionType.SubQuery, DbType.Decimal,
ExprSize = 17, ExprScale = 5)]
public decimal? DocAmount { get { return this.GetProperty(x => x.DocAmount); } }
[Reference("H", "HistoryDate")]
public DateTime? DocLastChange {
get { return this.GetProperty(x => x.DocLastChange); }
}
[Property("SC", Flags = DataPropertyFlag.OuterFlag)]
public Lob Scan { get; set; }
[Property("SR", Flags = DataPropertyFlag.OuterFlag)]
public Xml Source { get; set; }
[Property("F", "name"),
LinkProperty("CustomerId", "FromCustomerId"),
LinkProperty("Name")]
public Link<string, Customer> CustomerFrom { get; set; }
[Property("T", "name"),
LinkProperty("CustomerId", "ToCustomerId"),
LinkProperty("Name")]
public Link<string, Customer> CustomerTo { get; set; }
[Property("DD")]
public DeliveryTypeEnum DeliveryType { get; set; }
[Property("DD"),
PropertyDefault(DefaultType.CurrentDate)]
public DateTime? DeliveryDate { get; set; }
public IRepository<InvoiceSpec> Spec {
get { return this.GetRepository().GetChild<InvoiceSpec>(); }
}
}
Mapping parameters
All statically mapped classes must be marked with DataObjectAttribute. Parameters of DataObjectAttribute: BaseTable - alias of base table (optional, default is virtual table with empty alias), Context - database context (optional, context will be specified at runtime), and Flags - data source flags (optional, default is None).
Example:
[DataObject("D" /* base table alias */,
Context: "Main", Flags: DataObjectFlags.ReadOnly)]
The DataObjectAttribute attribute can also be used to inherit the mapping description of the data object. To do this, an alternative constructor is used that allows you to limit BaseMappingType - the base object in the inheritance hierarchy of the mapping description. The BaseMappingType class must implement the ISqlObject interface but does not have to be associated with the current class. The depth of the inheritance hierarchy of the mapping description is not limited.
[DataObject(typeof(ParentDescription))]
Warning
When inheriting mapping descriptions, a mixture of the static and dynamic description methods within the a single mapping inheritance hierarchy is not allowed!
Tables
All tables in repository query must to be specified using DataTableAttribute. Parameters of DataTableAttribute: Name - name of table and Alias - table alias in query. Tables in updatable tables hierarchy, but not base table must to specify Parent parameter of this attribute - alias of parent table in updatable tables hierarchy.
[DataTable("P" /* table alias */,
"patient" /* table name */,
"D" /* parent table alias */)]
Same way we can define mapping to view, but all fields of view must to be explicitly defined as read-only properties or columns.
Subqueries
XData allows use subqueries as part of mapping. This feature can be used for get property values from subquery, for subquery filters, and inner view definitions.
Subquery recursive deep is not limited.
Class used subquery in mapping definition must to marked with attribute SubqueryAttribute or InnerViewAttribute (when it used as inner view definition). Both of them has a parameter Alias - subquery alias in resulting query. InnerViewAttribute additionally has parameter InnerViewType - type mapped to subquery. SubqueryAttribute additionally has parameters: SubqueryType - type mapped to subquery, PropertyName - subquery returned property and Grouping - aggregation type of resulting property.
[Subquery("A" /* subquery alias */,
typeof(DocSpecAmounts) /* subquery mapped type */,
"Amount" /* subquery result property name */,
Grouping = DataGrouping.Sum /* result property aggregation type */)]
...
[InnerView("H" /* inner view alias */,
typeof(DocLastChange) /* inner view mapped type */)]
Filters
Filters can be defined as:
- Constant filters
- SQL expression filters
- Dictionary filters
- Range filters
- Subquery filters
- Inner links
Each of filter type is defined by own special attribute of data object class. Each of attributes has parameters: Source - data source alias of filtered field, FieldName - filtered field name, Operation - filter operation (optional, default is FilterOperation.Equal), Combination - filter group name (optional, default is root filter group). Above this every filter attribute has their own specific parameters described below.
Filter operation is defined by enum FilterOperation:
- Equal - equal
- NotEqual - not equal
- LessThan - less then
- GreaterThan - greater then
- LessThanOrEqual - less or equal
- GreaterThanOrEqual - greater or equal
- In - field value in range or subquery
- NotIn - field value is not in range or subquery
- Exists - subquery returns one or more rows
- NotExists - subquery returns no rows
- Like - field value correlated with find expression
- NotLike - field value not correlated with find expression
- OuterJoin - left outer join link between query sources (tables or inner views)
- Contains - field value correlated with full-text search
- NotContains - field value not correlated with full-text search
Filter group
Filter group can be defined by FilterCombinationAttribute with parameters: Name - filter group unique name, Combination - logical operation between filters and subgroups inside group (optional, default is FilterCombination.And), Parent - parent filter group name (optional, can be omitted for filter groups with parent - root filter group).
Logic operation inside filter group is defined by enum Combination:
- And - and
- Or - or
[FilterCombination("OrGroup" /* group filter name */,
Combination: Combination.OR /* logical operation */)]
Constant filters
Constant filters can restrict query by constant value. This filter is defined by ConstantFilterAttribute, with specific (over described above) parameters: Name - filter name (will be transformed to query parameter name), ConstantType - constant type (see below) and ConstantValue - constant value depends on ConstantType.
Constant types is defined by enum FilterConstantType:
- Const - value of ConstantValue.
- Variable - value of variable with name equals ConstantValue.
- CurrentDate - current date.
- CurrentDateTime - current date/time.
- CurrentDateTimeUTC - current date/time (UTC).
[ConstantFilter("FilterByZero" /* filter name */,
"P" /* table alias */,
"discount" /* DB field name */,
0m /* decimal constant value */,
Operation: FilterOperation.Equal /* filter operation */,
ConstantType: FilterConstantType.Const /* constant type */)]
SQL expression filters
To define filter over SQL expression used ExpressionFilterAttribute with specific (over described above) parameter: ExpressionText - depends on value can be:
- subquery alias,
[ExpressionFilter("P" /* table alias */,
"discount" /* field name */,
"D" /* ExpressionText: subquery alias */,
Operation: FilterOperation.In /* filter operation */)]
- private static field name contains dynamic definition of SQL expression,
[ExpressionFilter("P" /* table alias */,
"discount" /* field name */,
"AllowedDiscount" /* ExpressionText: SQL expression description field name */,
Operation: FilterOperation.NotEqual /* filter operation */)]
...
//Private static field of mapped class marked with SqlExpression attribute
[SqlExpression]
private static Calculate<int> AllowedDiscount = x => x.Case<Product, int>(
z => z.Field<bool>("is_vip"), z => 0, 1.SetExpression(z => 10));
- plain SQL expression text.
[ExpressionFilter("P" /* table alias */,
"discount" /* field name */,
"case P.is_vip when 1 then 10 else 0 end" /* ExpressionText: plain SQL expression */,
Operation: FilterOperation.NotEqual /* filter operation */)]
Important
If possible do not use plain SQL expressions. It possibly use specific SQL dialect features not supported by other DBMS or providers.
Range filters
To define filter checks field value over array of values used RangeFilterAttribute with specific (over described above) parameter: Range - array of values to filter on.
[RangeFilter("P" /* table alias */,
"discount" /* field name */,
new[] {0,5,10} /* range of values */,
Operation: FilterOperation.In /* filter operation */)]
Dictionary filters
Dictionaries surrogate keys may varied over one instance to another, and names can be changed by users. Sometimes business logic need to operate with the concrete value of dictionary. Because of this it's recommended to add into dictionary DB structure a special unique text field - mnemonic code.
XData supports using mnemonic code when define filters on dictionary value - dictionary filters. They defined by DictionaryFilterAttribute with specific (over described above) parameters: DictionaryTable - dictionary table name, DictionaryId - dictionary id field name, DictionaryCode - dictionary mnemonic code field name, DictionaryValue - mnemonic code value to filter query on, ValueIsVariable - flag DictionaryValue contains not a value but name of variable with value.
Tip
In case filter field name is equals dictionary id field name, parameter FieldName can be omitted.
Important
This filter can be used when dictionary has simple (one field only) identity field and mnemonic code field. But it's a very common case.
[DictionaryFilter("t_doc_state" /* dictionary table name */,
"doc_state_id" /* dictionary id field name */,
"code" /* dictionary mnemonic code field name */,
"CREATED" /* mnemonic code value */,
"D" /* filtered table alias */)]
Inner links
To define links between data sources inside query used LinkAttribute with specific parameters: LinkedSourceAlias - linked table alias, LinkedFieldName - linked table field name (optional, can be omitted when equals with FieldName).
Tip
Inner links can be used to define optional filters. To specify role in optional filter chain need to set parameter PrimaryFilter to true.
[Link("D" /* table alias */,
"doc_id" /* field name */,
"DD" /* linked table alias */)]
Subquery filters
To define filters checks field value is correspond subquery results used SubqueryFilterAttribute with specific (over defined above) parameter: Subquery - subquery alias (see Subqueries). Inner view can not be used with subquery filters.
[SubqueryFilter("D" /* table alias */,
"doc_state_id" /* field name */,
"A" /* subquery alias */,
Operation: FilterOperation.In /* filter operation */)]
When flag PrimaryFilter is set to true optional part of query is defined by followed rules: when one of linked data sources is skipped then other one will skipped too. When base table of subquery is skipped then subquery is skipped completely.
Subquery links
Links with subqueries is used to link not table fields but properties of base class and subquery mapped class including hidden properties. To link with subquery or inner view used SubqueryLinkAttribute with parameters: Subquery - subquery alias, SubqueryProperty - linked property from subquery, PropertyName - optional, linked property from base class (when omit, base class property name is equal SubqueryProperty), also optional properties: Operation, Combination and PrimaryFilter is supported. They was described above in topics Filters and Inner links.
[SubqueryLink("H" /* subquery alias */,
"DocId" /* subquery property to link */)]
When flag PrimaryFilter is set to true optional part of query is defined by followed rules: when subquery is skipped, then data source linked to subquery by this filter will be skipped too, and skip data sources analysis will continue inside base class (see Inner links).
Properties
Static mapping is performed as each property with specific attributes mark. Main attribute to statically map property is PropertyAttribute with parameters: Source - data source alias (may be omitted for virtual table), FieldName - DB field name mapped to property (may be omitted when field and property correspond naming rule, for example DB field named as some_field_name and mapped to SomeFieldName property), Flags - optional, mask of property flags,NativeSqlType - optional, SQL type name in DB. Parameter NativeSqlType is used when type default mapping is conflicted with real SQL type name.
[Property("S" /* table alias */, "code" /* field name */)]
SQL expression property
Read only property can be mapped to SQL expression. Static mapping of SQL expression is defined by PropertyExpressionAttribute with parameters: ExprText - depends of ExprType value (see below) it can be: subquery alias / SQL expression / private static field name define SQL expression in LINQ style, ExprType - optional, SQL expression type, default is DataExpressionType.PlainSql, DbType - ADO ,Net provider data type (optional, default is DbType.String), ExprSize - optional, expression field size (if applicable), ExprScale - optional, expression field precision (if applicable). Possible to use SQL expressions of three types:
- subquery,
[PropertyExpression("A",
DataExpressionType.SubQuery,
DbType.Decimal,
ExprSize = 17,
ExprScale = 5)]
- LINQ expression,
[PropertyExpression("AllowedDiscount",
DataExpressionType.LinqExpression,
DbType.Decimal,
ExprSize = 17,
ExprScale = 5)]
...
//Private static field of mapped class marked with SqlExpression attribute
[SqlExpression]
private static Calculate<int> AllowedDiscount = x => x.Case<Product, int>(
z => z.Field<bool>("is_vip"), z => 0, 1.SetExpression(z => 10));
- plain text SQL expression.
[PropertyExpression("case P.is_vip when 1 then 10 else 0 end",
DataExpressionType.PlainSql,
DbType.Decimal,
ExprSize = 17,
ExprScale = 5)]
Tip
Attribute PropertyExpressionAttribute is not replaced PropertyAttribute but extend it.
Important
If possible do not use plain SQL expressions. It possibly use specific SQL dialect features not supported by other DBMS or providers.
Tip
It's recommended to use virtual table as SQL expression source omitting data source alias in PropertyAttribute.
Property default value
To set default value of property with static mapping, use PropertyDefaultAttribute with parameters: DefaultSource - default value type, DefaultValue optional, depends on DefaultSource, DefaultFeature - optional, default is DefaultFeature.UseOnInsert, default value assignment will be applied with some extended features.
[PropertyDefault(DefaultType.UserName, AlwaysUseDefault = true)]
Property grouping parameters
Property grouping parameters used when data object mapped to grouped query to specify property role in GROUP BY expression. PropertyGroupingAttribute has properties: Grouping - optional, property aggregation type, default is None, GroupOrder - optional, property grouping order when Grouping = DataGrouping.None.
[PropertyGrouping(DataGrouping.Sum)]
Hidden properties
Hidden properties statically defined using couple of specific attributes: ColumnAttribute, ColumnExpressionAttribute and ColumnDefaultAttribute as analog with similar attributes of properties.
ColumnAttribute has parameters: PropertyName - name of hidden property, PropertyType - type of hidden property, Source - data source alias, FieldName - optional, DB field name (can be omitted when correspond default mapping rule (example: some_field_name is mapped to SomeFieldName)), Flags - optional, bit mask of property flags, Grouping - optional, defined aggregation type, default is None, GroupOrder - optional, group order when Grouping = DataGrouping.None, Hidden - optional, mapped field omitted in SELECT expression of result query (when grouping limitations demand this), default is false, NativeSqlType - optional, native SQL type name. NativeSqlType is used when default data type mapping is conflicted with result set data type.
ColumnExpressionAttribute has parameters: PropertyName - name of hidden property (same as defined by ColumnAttribute), ExprText - depends on ExprType (see below): subquery alias / SQL expression text / field name with LINQ style expression definition, ExprType - optional, SQL expression type default is DataExpressionType.PlainSql, DbType - data type on ADO .Net provider level (optional, default is DbType.String), ExprSize - optional, expression field size (if applicable), ExprScale - optional, expression field precision (if applicable).
ColumnDefaultAttribute has parameters: PropertyName - hidden property name (same as defined by ColumnAttribute), DefaultSource - default value type, DefaultValue optional, depends on DefaultSource, DefaultFeature - optional, default is DefaultFeature.UseOnInsert, default value assignment will be applied with some extended features.
[Column("DocId", typeof(long?), "D", Flags = DataPropertyFlag.Id),
ColumnDefault("DocId", DefaultType.AutoIncrement)]
Inner view reference
To map property to field from inner view a ReferenceAttribute is used with parameters: Source - inner view alias, PropertyName - optional, DB field name (may be omitted when property name is equals with inner view mapped class property), Flags - optional, mask of property flags,NativeSqlType - optional, SQL type name in DB. Parameter NativeSqlType is used when type default mapping is conflicted with real SQL type name.
[Reference("H" /* inner view alias */,
"HistoryDate" /* property name inside inner view */)]
Links
To statically define link property pairs used LinkPropertyAttribute with parameters: DictSource - optional, source object property name, Property - optional, property name. When one or both of parameters is omitted, followed rules are used:
- When omit both properties - used property name of link property as target and equal named property from source object
- When DictSource is omitted - used Property as target property name and equal named property from source object
- When Property is omitted - used property name of link property as target and property with name equals DictSource from source object
[Property("S", "name"),
LinkProperty("Name") /* DocState.Name -> this.DocState */,
LinkProperty(Property = "DocStateId") /* column DocState.DocStateId -> column this.DocStateId */,
LinkProperty("Code", "DocStateCode") /* DocState.Code -> this.DocStateCode */]
public Link<string, DocState> DocState { get; set; }
Tree
To statically map tree organized data structure used special data source attributes With to describe tree root query and WithRecursive to describe tree childs. Both of them use subquery mappings (see example below) to set subquery structure for tree query construction.
Important
Data source flags of tree organized data sources must contain DataStructureFlag.ReadOnly and DataStructureFlag.Tree!
With attribute has properties: Alias - alias of tree data source, SubqueryType - type of root subquery mapping, Type - data source combination within tree query, Properties - tree properties order. WithRecursive attribute has properties: Alias - alias of recursive data source, SubqueryType - type of root subquery mapping, InitialAlias - alias of tree data source.
[DataObject("CAT", Flags = DataStructureFlag.ReadOnly | DataStructureFlag.Tree),
With("CAT", typeof(CatalogueTreeRoot), WithRecursiveType.RecursiveUnion,
"CatalogueId", "Name", "Code", "ParentId"),
WithRecursive("S", typeof(CatalogueTreeFolders), "CAT"),
SubqueryLink("S", "ParentId", "CatalogueId")]
public class CatalogueTree : IDataObject
{
public static readonly RuntimeFilter FilterByRoot
= new RuntimeFilter(null, "FilterByRoot");
public static readonly RuntimeFilter FilterByCathalogue
= new RuntimeFilter(null, "FilterByCathalogue");
[Property("T", Flags = DataPropertyFlag.Id)]
public long CatalogueId { get { return this.GetProperty(x => x.CatalogueId); } }
[Property("T")]
public string Code { get { return this.GetProperty(x => x.Code); } }
[Property("T")]
public string Name { get { return this.GetProperty(x => x.Name); } }
[Property("T")]
public long? ParentId { get { return this.GetProperty(x => x.ParentId); } }
}
[DataObject("R"),
DataTable("T_CATALOGUE", "R"),
Column("CatalogueId", typeof(long), "R"),
Column("Code", typeof(string), "R"),
Column("Name", typeof(string), "R"),
Column("ParentId", typeof(long?), "R"),
FilterCombination("root", Combination = Combination.Or),
ConstantFilter("FilterByCathalogue", "R", "catalogue_id", null,
Combination = "root", Nullable = FilterNullable.NullsNotAllowed),
ConstantFilter("FilterByRoot", "R", "parent_id", null,
Combination = "root", Nullable = FilterNullable.NullsCompared)]
public class CatalogueTreeRoot : ISqlObject {}
[DataObject("S"),
DataTable("T_CATALOGUE", "S"),
Column("CatalogueId", typeof(long), "S"),
Column("Code", typeof(string), "S"),
Column("Name", typeof(string), "S"),
Column("ParentId", typeof(long?), "S")]
public class CatalogueTreeFolders : ISqlObject {}
External link
External link can be statically defined using ExternalLinkAttribute with parameters: ChildType - detail repository mapped type, ChildProperty - link property in detail repository, Property - optional, link property in master repository, default used same name as ChildProperty, FilterName - optional, detail repository filter name, default used "FilterBy" + ChildProperty, MasterRefresh - optional, refresh master repository current row data on submit detail changes flag (useful when master has calculated properties over detail repository data), default is false, DirectLink - optional, see tree master-detail specifics for details, default is false Operation - optional, link filter operation (see Filters), default FilterOperation.Equal, Nullable - optional, filter null processing behavior, default is FilterNullable.Nullable, Combination - optional, filter group name in detail repository. External links can be used in optional filters description (see Optional filters and optional subqueries). To specify external link is optional filter part You can set primary filter flag PrimaryFilter.
[ExternalLink(typeof(InvoiceSpec), "DocId")]
Variables
The use of variables is described here.
Here we describe a way of setting variable values for reused mapping Subquery (and InnerView) mapping. To do this, use the SubqueryVariableAttribute attribute. Parameter Alias - subquery alias in resulting query. Name - the name of the variable. Value - the value of the variable.
[SubqueryVariable("A" /* subquery alias */,
"TestVar" /* variable name */,
1 /* variable value */)]
SQL procedures & functions
Common information about mapping to SQL procedures and functions has described in common mapping rules description and here we just represent static mapping rules for various procedure types.
Warning
SQLite not supported SQL procedures & functions
Warning
PostgreSql functions has restrictions:
- returning refcursor or set of refcursor requires transaction!
- parameters use lower case names ONLY!
- result set names (refcursor names) use lower case names ONLY!
SQL procedure as query
To define procedure as data source of mapping used ProcedureAttribute with parameters: Alias - procedure alias, Name - procedure or function name, ProcedureType - ProcedureType: ProcedureType.Procedure or ProcedureType.Function.
SQL procedure parameter
To define SQL procedure parameter used ParameterAttribute with parameters: Alias - procedure alias, Order - order number of parameter, Binding - name used inside application code (it's handy to use parameter with another name, not the name defined inside DB), Type - data type used inside application code, DbType - data type on ADO .Net provider level, Direction - optional, parameter direction, default is ParameterDirection.Input, Size - optional, parameter size(if applicable), Scale - optional, parameter precision (if applicable), Name - optional, parameter name, default is equal with Binding, DefaultType - default value type, DefaultValue optional, depends on DefaultType, NativeSqlType - optional, SQL type name (NativeSqlType is used when type default mapping is conflicted with real SQL type), UdtTypeName - optional, user defined type (UDT), UdtElementTypeName - optional, user defined type (UDT) of UdtTypeName element (in case where UdtTypeName - array or table), IsArray - optional, parameter is array of UdtElementTypeName (when UdtElementTypeName is omitted, array of DbType), default is false.
SQL procedure result set
To describe result set of SQL procedure used ResultSetAttribute with parameters: Alias - procedure alias, Name - result set name used inside application code, when result set is not mapped to procedure parameter set it to "" (empty string), Order - optional, order number of result set (required to procedures returns multiple result sets), ResultType - optional, mapped type of result set, default is attributed class itself, IsDefault - optional, result set is default - mapped to attributed class (used when procedure returns multiple result sets), default is false.
SQL procedure result set as query sample
- MS SQL Server
CREATE PROCEDURE [dbo].[TestProcedure]
(
@param1 int,
@param2 varchar(20)
)
AS
SELECT @param1 as Id, @param2 as Name
- Oracle
procedure TestProcedure(p_Id int, p_Name varchar2, p_Out out SYS_REFCURSOR)
as
begin
open p_Out for select p_Id as Id, p_Name as Name from dual;
end TestProcedure;
- PostgreSQL
CREATE OR REPLACE FUNCTION public.TestProcedure (p_id integer, p_name varchar, refcursor)
RETURNS refcursor AS
$$
begin
open $3 for select p_id as Id, p_name as Name;
return $3;
end
$$ LANGUAGE plpgsql;
- MySQL
create procedure TestProcedure(p_Id int, p_Name varchar(50))
begin
select p_Id as Id, p_Name as Name;
end
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestProcedure", ProcedureType.Procedure),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
ResultSet("T")]
public class TestProcedure : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id { get { return this.GetProperty(x => x.Id); } }
[Property("T", "Name")]
public string Name { get { return this.GetProperty(x => x.Name); } }
}
SQL procedure with multiple result sets sample
- MS SQL Server
CREATE PROCEDURE [dbo].[TestProcedure2]
(
@param1 int,
@param2 varchar(20),
@param3 int,
@param4 varchar(20)
)
AS
BEGIN
SELECT @param1 as Id, @param2 as Name
SELECT @param3 as Id, @param4 as Name
END
- Oracle
procedure TestProcedure2(p_Id int, p_Name varchar2, p_Out out SYS_REFCURSOR, p_Out2 out SYS_REFCURSOR)
as
begin
open p_Out for select p_Id as Id, p_Name as Name from dual;
open p_Out2 for select p_Id as Id, p_Name as Name from dual;
end TestProcedure2;
Tip
Use refcursor parameters names as ResultSet names!
- PostgreSQL
CREATE OR REPLACE FUNCTION public.TestProcedure2 (p_id integer, p_name varchar, refcursor, refcursor)
RETURNS SETOF refcursor AS
$$
begin
open $3 for select p_id as Id, p_name as Name;
return NEXT $3;
open $4 for select p_id as Id, p_name as Name;
return NEXT $4;
end
$$ LANGUAGE plpgsql;
Warning
Functions witch returns setof refcursor is not supported by NpgSql .Net Standard 2.0 Edition :( XData net4.0 version can call that type functions with the same mapping rules as described here...
Tip
If refcursor parameters have a names it must be used as ResultSet names!
- MySQL
create procedure TestProcedure2(p_Id int, p_Name varchar(50))
begin
select p_Id as Id, p_Name as Name;
select p_Id as Id, p_Name as Name;
end
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestProcedure2", ProcedureType.Procedure),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
Parameter("T", 3, "param3", typeof(int), DbType.Int32),
Parameter("T", 4, "param4", typeof(string), DbType.String),
ResultSet("T", IsDefault = true),
ResultSet("T", "SecondResult", ResultType = typeof(TestResult), Order = 2)]
public class TestProcedure2 : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id { get { return this.GetProperty(x => x.Id); } }
[Property("T", "Name")]
public string Name { get { return this.GetProperty(x => x.Name); } }
public IEnumerable<TestResult> SecondResult
{ get { return this.GetResultSet(x => x.SecondResult); } }
}
SQL procedure with out parameter sample
- MS SQL Server
CREATE PROCEDURE [dbo].[TestProcedure3]
(
@param1 int,
@param2 varchar(20),
@param3 varchar(20) OUTPUT
)
AS
SET @param3 = convert(varchar(20), @param1) + ' - ' + @param2
RETURN
- Oracle
procedure TestProcedure3(p_Id int, p_Name varchar2, p_Out out varchar2)
as
begin
select p_Id || ' - ' || p_Name into p_Out from dual;
end TestProcedure3;
- PostgreSQL
CREATE or REPLACE function TestProcedure3(p_id integer, p_name varchar, out p_out varchar) as $$
begin
p_Out := p_id || ' - ' || p_name;
end
$$ LANGUAGE plpgsql;
- MySQL
create procedure TestProcedure3(p_Id int, p_Name varchar(50), out p_Out varchar(100))
begin
set p_Out = CONCAT(p_Id, ' - ', p_Name);
end
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestProcedure3", ProcedureType.Procedure),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
Parameter("T", 3, "OutParameter", typeof(string),
DbType.String,
Size = 20,
Direction = ParameterDirection.Output,
Name = "param3")]
public class TestProcedure3 : IDataObject
{
public string OutParameter
{ get { return this.GetParameter(x => x.OutParameter); } }
}
SQL procedure with out parameter and result set sample
- MS SQL Server
CREATE PROCEDURE [dbo].[TestProcedure4]
(
@param1 int,
@param2 varchar(20)
)
AS
SELECT @param1 as Id, @param2 as Name
RETURN(@param1)
- Oracle
procedure TestProcedure4(p_Tab in classifier_tab, p_Out out SYS_REFCURSOR)
as
begin
open p_Out for select Id, Name from table(p_Tab);
end TestProcedure4;
Tip
Use refcursor parameters names as ResultSet names!
- PostgreSQL
CREATE OR REPLACE FUNCTION public.TestProcedure4 (p_tab public.classifier_rec [], refcursor)
RETURNS refcursor AS
$$
begin
open $2 for select Id, Name from unnest(p_tab);
return $2;
end
$$
LANGUAGE plpgsql;
Tip
If refcursor parameters have a names it must be used as ResultSet names!
- MySQL
Not supported
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestProcedure4", ProcedureType.Procedure),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
Parameter("T", 3, "OutParameter", typeof(string),
DbType.String,
Size = 20,
Direction = ParameterDirection.Output,
Name = "param3"),
ResultSet("T")]
public class TestProcedure4 : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id { get { return this.GetProperty(x => x.Id); } }
[Property("T", "Name")]
public string Name { get { return this.GetProperty(x => x.Name); } }
public string OutParameter
{ get { return this.GetParameter(x => x.OutParameter); } }
}
SQL procedure with return value sample
- MS SQL Server
CREATE PROCEDURE [dbo].[TestProcedure5]
(
@param1 int,
@param2 varchar(20)
)
AS
RETURN(@param1)
- Oracle
Not supported
- PostgreSQL
Not supported
- MySQL
Not supported
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestProcedure5", ProcedureType.Procedure),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
Parameter("T", 3, "ReturnValue", typeof(string),
DbType.String,
Size = 20,
Direction = ParameterDirection.ReturnValue,
Name = "result")]
public class TestProcedure5 : IDataObject
{
public string ReturnValue
{ get { return this.GetParameter(x => x.ReturnValue); } }
}
SQL procedure with return value and result set sample
MS SQL Server
MS SQL Server
CREATE PROCEDURE [dbo].[TestProcedure6]
(
@param1 int,
@param2 varchar(20)
)
AS
SELECT @param1 as Id, @param2 as Name
RETURN(@param1)
- Oracle
Not supported
- PostgreSQL
Not supported
- MySQL
Not supported
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestProcedure6", ProcedureType.Procedure),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
Parameter("T", 3, "ResultParameter", typeof(int),
DbType.Int32,
Direction = ParameterDirection.ReturnValue),
ResultSet("T")]
public class TestProcedure6 : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id { get { return this.GetProperty(x => x.Id); } }
[Property("T", "Name")]
public string Name { get { return this.GetProperty(x => x.Name); } }
public int ResultParameter
{ get { return this.GetParameter(x => x.ResultParameter); } }
}
SQL procedure with simple array parameter sample
- MS SQL Server
Not supported
- Oracle
Not supported
- PostgreSQL
CREATE or REPLACE function TestFunction6(p_arr INTEGER[]) returns integer as $$
begin
return sum(s) from unnest(p_arr) s;
end
$$ LANGUAGE plpgsql;
- MySQL
Not supported
- Mapping
[DataObject("T"),
Procedure("T", "TestFunction", ProcedureType.Function),
Parameter("T", 1, "param1", typeof(int[]), DbType.Int32, IsArray = true),
Parameter("T", 2, "Result", typeof(int), DbType.Int32,
Direction = ParameterDirection.ReturnValue)]
public class TestFunction : IDataObject
{
public int Result { get { return this.GetParameter(x => x.Result); } }
}
SQL procedure with UDT parameter sample
- MS SQL Server
Not supported
Tip
MS SQL Server does not support passing single UDT object as a parameter value, but support array of UDT
- Oracle
Not supported
Warning
Oracle ODP.Net provider does not support UDT
- PostgreSQL
CREATE TYPE public.classifier_rec AS ( Id integer, Name varchar(20) );
CREATE or REPLACE function TestFunction5(p_obj classifier_rec) returns varchar as $$
begin
return p_obj.Id || ' - ' || p_obj.Name;
end
$$ LANGUAGE plpgsql;
- MySQL
Not supported
- Mapping
/* Type mapped to UDT required to be Xml serializable */
[Serializable]
public class Classifier
{
[XmlAttribute]
public int Id { get; set; }
[XmlAttribute]
public string Name { get; set; }
}
...
[DataObject("T"),
Procedure("T", "TestFunction5", ProcedureType.Function),
Parameter("T", 1, "p_obj",
typeof(Classifier),
DbType.Object,
UdtTypeName = "classifier_rec" /* Full qualified name of UDT */ ),
Parameter("T", 2, "Result", typeof(string), DbType.String, Size = 20,
Direction = ParameterDirection.ReturnValue)]
public class TestFunction5 : IDataObject
{
public string Result => this.GetParameter(x => x.Result);
}
SQL procedure with UDT array parameter sample
- MS SQL Server
CREATE TYPE [dbo].[Classifier] AS TABLE(
[Id] [int] NULL,
[Name] [varchar](20) NULL
)
CREATE PROCEDURE [dbo].[TestProcedure7] (@param1 Classifier READONLY)
AS
SELECT * from @param1
- Oracle
Not supported
Warning
Oracle ODP.Net provider does not support UDT
- PostgreSQL
CREATE TYPE public.classifier_rec AS ( Id integer, Name varchar(20) );
CREATE or REPLACE function TestProcedure7(p_tab classifier_rec[]) returns setof classifier_rec as $$
select a.Id, a.Name from unnest(p_tab) a;
$$ LANGUAGE sql;
- MySQL
Not supported
- Mapping
/* Type mapped to UDT required to be Xml serializable */
[Serializable]
public class Classifier
{
[XmlAttribute]
public int Id { get; set; }
[XmlAttribute]
public string Name { get; set; }
}
...
[DataObject("T"),
Procedure("T", "dbo.TestProcedure7", ProcedureType.Procedure),
Parameter("T", 1, "param1",
typeof(Classifier[]) /* Parameter type must be an array of mapped type */,
DbType.Object,
UdtTypeName = "dbo.Classifier" /* Full qualified name of UDT */ ),
ResultSet("T")]
public class TestProcedure7 : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id => this.GetProperty(x => x.Id);
[Property("T", "Name")]
public string Name => this.GetProperty(x => x.Name);
}
SQL table valued function as query sample
- MS SQL Server
CREATE FUNCTION [dbo].[TestFunction]
(
@param1 int,
@param2 varchar(20)
)
RETURNS TABLE
AS
RETURN
(
SELECT @param1 as Id, @param2 as Name
)
- Oracle
CREATE OR REPLACE type classifier_rec is object (Id int, Name varchar2(20));
CREATE OR REPLACE type classifier_tab is table of classifier_rec;
function TestFunction(p_Id int, p_Name varchar2) return classifier_tab pipelined
is
begin
for curr in (select p_Id as Id, p_Name as Name from dual) loop
pipe row (classifier_rec(curr.Id, curr.Name));
end loop;
end TestFunction;
- PostgreSQL
CREATE or REPLACE function TestFunction(p_id integer, p_name varchar)
returns TABLE (
Id bigint,
Name varchar
) as $$
select p_id as Id, p_name as Name;
$$ LANGUAGE SQL;
... or with the same mapping syntax...
CREATE TYPE public.classifier_rec AS ( Id integer, Name varchar(20) );
CREATE or REPLACE function TestFunction(p_Id integer, p_Name varchar) returns setof classifier_rec as $$
select p_Id as Id, p_Name as Name;
$$ LANGUAGE SQL;
- MySQL
Not supported
- Mapping
[DataObject("T"),
Procedure("T", "dbo.TestFunction", ProcedureType.Function),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
ResultSet("T")]
public class TestFunction : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id { get { return this.GetProperty(x => x.Id); } }
[Property("T", "Name")]
public string Name { get { return this.GetProperty(x => x.Name); } }
}
- Mapping table function with joined table
[DataObject("D"),
Procedure("T", "dbo.TestFunction", ProcedureType.Function),
Parameter("T", 1, "param1", typeof(int), DbType.Int32),
Parameter("T", 2, "param2", typeof(string), DbType.String),
ResultSet("T"),
DataTable("T_DOC_TYPE", "D"),
Link("D", "doc_type_id", "T", "Id")]
public class TestFunctionJoin : IDataObject
{
[Property("T", "Id", Flags = DataPropertyFlag.Id)]
public int Id { get; set; }
[Property("T", "Name")]
public string Name { get; set; }
[Property("D", "Code")]
public string Code { get; set; }
}
SQL scalar function as property data source sample
- MS SQL Server
CREATE FUNCTION [dbo].[TestFunction2]
(
@param1 int,
@param2 varchar(20)
)
RETURNS varchar(20)
AS
BEGIN
DECLARE @res varchar(20)
SET @res = convert(varchar(20), @param1) + ' - ' + @param2
RETURN @res
END
- Oracle
function TestFunction2(p_Id int, p_Name varchar2) return varchar2
is
begin
return p_Id || ' - ' || p_Name;
end TestFunction2;
- PostgreSQL
CREATE or REPLACE function TestFunction2(p_id integer, p_name varchar) returns varchar as $$
DECLARE
ret varchar;
begin
select p_id || ' - ' || p_name into ret;
return ret;
end
$$ LANGUAGE plpgsql;
- MySQL
create function TestFunction2(p_Id int, p_Name varchar(20)) returns varchar(100)
begin
declare res varchar(100);
select CONCAT(p_Id, ' - ', p_Name) into res;
return(res);
end
- Mapping
[DataObject("T"),
DataTable("T_DOC_TYPE", "T"),
Column("DocTypeId", typeof(long), Flags = DataPropertyFlag.Id)]
public class TestFunction4 : IDataObject
{
[Property("T")]
public string Code { get; set; }
[Property("T")]
public string Name { get; set; }
[Property,
// Property expression attribute "name" is the name of private static field
// with type Calculate<string> and attributed by SqlExpressionAttribute
PropertyExpression("test", DataExpressionType.LinqExpression, ExprSize = 20)]
public string Test { get { return this.GetProperty(x => x.Test); } }
// Private static field with type Calculate<string> and attributed by
// SqlExpressionAttribute
[SqlExpression]
private static Calculate<string> test z => z.SqlFn<string>("dbo.TestFunction2",
y => y.Field<long>("T", "doc_type_id"), y => y.Field<string>("T", "name"))
}