WXML starting up

by Alexey Shirshov September 21, 2009 21:03

Today I published new project on codeplex - WXML. In short it is a new xml format to define object models and their mapping to various sources (database, xml, etc). It is similar to edmx or dbml but has some differences

  • XInclude (any part of file can be brought out to external file)
  • Strong types
  • Open and flexible API
  • Differential updates

Feature list:

  • Generating from database
  • Generating SQL scripts from model
  • Generating LINQ to SQL context
  • Generating Worm classes

It combines simplicity (from dbml) and power (from edmx).

Lets compare three files which are define the same model.

WXML

edmx

dbml

And the files

As you can see edmx is very complex and bulky. Dbml is much simplier but has many annoying limitations (lack of xml documentation, m2m relations). WXML has all the features of edmx and brief as dbml.

Expressions has no IdentityEquality operator

by Alexey Shirshov September 19, 2009 19:17

Vb guys always wonder why c# has no identity equality operator like Is and IsNot. In many cases equality operator == make the job in c#. For instance

object d = "hello!";
bool isString = typeof(string) == d.GetType();

Here is the code in vb.net

Dim d As Object = "hello!"
Dim isString As Boolean = GetType(stringIs d.GetType()
'below is regular language construction
Dim isType As Boolean = GetType(stringIs d

The last statement will not work in c#. The only chance is to use ReferenceEquals method

object d = "hello!";
bool isType = ReferenceEquals(typeof(string), d);

So far so good. The bad news is expressions lack of identity equality operator. There is only one enumeration element in ExpressionType enum represents equality - ExpressionType.Equal. Even in CodeDom there are three: IdentityInequality, IdentityEquality and ValueEquality. So if you want to transform Expressions tree to CodeDom tree like I do, you have to think a lot.

In Expressions to CodeDom I create spesial CodeIdentityEqualityExpression class and CodeDom.Is method to simplify usage.

<TestMethod()> Public Sub TestIsOperator()
   Dim c = New CodeDomGenerator()
   c.AddNamespace("Samples").AddClass("cls") _
      .AddMethod(MemberAttributes.Public Or MemberAttributes.Static, Function() "foo", _
         Emit.declare("d1", Function() CodeDom.Is(String.Empty, GetType(String))), _
         Emit.declare("d2", Function() CodeDom.IsNot(String.Empty, GetType(String))) _
   )
   
   Console.WriteLine(c.GenerateCode(CodeDomGenerator.Language.CSharp))
   Console.WriteLine(c.GenerateCode(CodeDomGenerator.Language.VB))

   Dim ass = c.Compile()
   Assert.IsNotNull(ass)
   
   Dim TestClass As Type = ass.GetType("Samples.cls")
   Assert.IsNotNull(TestClass)
End Sub

Worm examples

by Alexey Shirshov June 28, 2009 10:55
All examples I used to write entries under Quickstart category can be downloaded in single solution. It contains VB.NET project and scripts to create AdventureWorks model.

Tags:

Examples

Expressions to CodeDOM

by Alexey Shirshov May 16, 2009 13:05

Over a last month I was involved in Expressions to CodeDOM project. This blog I plan to use to explain some general aspects, usage scenarios and internal mechanisms of the library.

The library consists of five abstractions

  • Define static class
  • Emit static class
  • CodeDom static class
  • Microsoft CodeDOM extesion methods
  • CodeDomGenerator class

Define class using for class, method, property, event, etc declarations. It helps to create CodeTypeMember classes (CodeTypeDeclaration, CodeMemberMethod, etc).

Emit class helps to create various statements as return, if, for, etc. All methods of the class return instances of CodeStatement derived classes.

CodeDom helps to create some statements which cannot be presented in pure lambda functions. It helps to create CodeExpressions too.

Microsoft CodeDOM extesion methods simplify work with CodeDOM.

CodeDomGenerator is a root class, main entry point of the library. Although you can create CodeDOM tree without it, it very helps to store root namespaces, process CodeDOM tree and generate the code.

Creating the model

by Alexey Shirshov May 14, 2009 10:48

Worm has three ways to create object model

  1. Use code generator
  2. Write mapping schema for object manually
  3. Use attribute-based mapping

If you already have object model you can use POCO functionality to map the classes.

By this time Worm has no full-functional VS designer and the only way to automatically generate model is to use codegen utilitis. There is two

To generate xml from AdventureWorks run the following

Worm.CodeGen.XmlGenerator.exe -S=(local)\sqlexpress -E -D=AdventureWorks

The program produce AdventureWorks.xml (143.19 kb)

Now, you can generate code from the xml via code generator or custom tool. VS custom tool is code generator that can be used with Visual Studio. To use it you should

  1. register custom tool in VS
  2. add xml to solution
  3. assign custom tool to xml file

Here is how to register custom tool in Visual Studio

regasm /codebase <path to custom tool>\Worm.CodeGen.VSTool.dll

Now you can add xml file to solution and set Custom tool property of the AdventureWorks.xml file to WormEntityClassGenerator.

POCO goes away

by Alexey Shirshov May 13, 2009 12:51

Almoust all recent posts apply to POCO. Worm has more powerful mechanism to map database entities. It based on predefined base classes. They are implement the following interfaces: IEntity, ICachedEntity and IKeyEntity. We'll talk about the interfaces later. So, what purposes do they serve? It's

  • Additional functionality
  • Performance
  • Persistence in local memory (caching)
  • Partial loading
  • Lazy loading
  • and more

You can see class diagram.

Let's see how base class can affect application performance. We gonna add CachedEntity base class to SalesPerson.

    [Entity("Sales", "SalesPerson", "1")]
    public class SalesPerson : CachedEntity
    {
        [EntityProperty("SalesPersonID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public decimal? SalesQuota { get; set; } 

        [EntityProperty("TerritoryID")]
        public SalesTerritory SalesTerritory { get; set; } 

        public decimal Bonus { get; set; } 

        public decimal CommissionPct { get; set; } 

        public decimal SalesYTD { get; set; } 

        public decimal SalesLastYear { get; set; } 

        public DateTime ModifiedDate { get; set; } 

        [EntityProperty("rowguid", Field2DbRelations.RowVersion)]
        public Guid Timestamp { get; protected set; } 

        public static QueryCmd Query
        {
            get
            {
                return new QueryCmd(exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(SalesPerson))
                    .SelectEntity(typeof(SalesPerson));
            }
        }
    }

As you can see no other changes were made. The program to measure the time

        static void Main(string[] args)
        {
            int iterCount = 1000;
            DateTime start = DateTime.Now;
            for (int i = 0; i < iterCount; i++)
            {
                foreach (Sales.SalesPerson s in Sales.SalesPerson.Query
                    .ToPOCOList<Sales.SalesPerson>())
                {
                    string str = string.Format("Store id: {0}, bonus: {1}, territory name: {2}",
                        s.ID, s.Bonus, s.SalesTerritory != null ? s.SalesTerritory.Name : "no territory");
                    //Console.WriteLine(str);
                }
            }
            Console.WriteLine("POCO time {0}", DateTime.Now - start); 

            start = DateTime.Now;
            for (int i = 0; i < iterCount; i++)
            {
                foreach (Entity.SalesPerson s in Entity.SalesPerson.Query
                    .ToList<Entity.SalesPerson>())
                {
                    string str = string.Format("Store id: {0}, bonus: {1}, territory name: {2}",
                        s.ID, s.Bonus, s.SalesTerritory != null ? s.SalesTerritory.Name : "no territory");
                    //Console.WriteLine(str);
                }
            }
            Console.WriteLine("Entity time {0}", DateTime.Now - start);
        }

Here is the time:

POCO time 00:00:18.2705521
Entity time 00:00:10.9729305

We have more than 40% performance gain!

Tags:

Insert/Delete POCO

by Alexey Shirshov May 07, 2009 15:26

So what about creating new instances of POCO in DBMS or deleting it? No problem. You need to create and save it.

        static void Main(string[] args)
        {
            //create new instance
            Sales.SalesTerritory t = new exam1sharp.Sales.SalesTerritory()
            {
                Name = "China",
                CountryRegionCode = "CH",
                Group = "Asia",
                SalesYTD = 100,
                SalesLastYear = 100,
                ModifiedDate = DateTime.Now
            };

            //save new instance
            using (ModificationsTracker mt = new ModificationsTracker(exam1sharp.Properties.Settings.Default.connString))
            {
                mt.Add(t);
                mt.AcceptModifications();
            }

            Console.WriteLine("New SalesTerritory id = {0}", t.ID);

            //delete created instance
            using (ModificationsTracker mt = new ModificationsTracker(exam1sharp.Properties.Settings.Default.connString))
            {
                mt.Delete(t);
                mt.AcceptModifications();
            }
        }

Tags:

Multitable object update

by Alexey Shirshov May 06, 2009 13:22

SalesOrder class has two tables as a source. Can we update properties from those two tables and save changes simultaneously? Yes, we can. Suppose we want to change OrderQty and OrderDate property.

        static void Main(string[] args)
        {
            var o = SalesOrder.Query
                .Where(Ctor.prop(typeof(SalesOrder), "SalesOrderDetailID").eq(1))
                .Single() as SalesOrder; 

            o.OrderQty += 10;
            o.OrderDate = new DateTime(2001, 07, 5); 

            using (ModificationsTracker mt = new ModificationsTracker(exam1sharp.Properties.Settings.Default.connString))
            {
                mt.Add(o);
                mt.AcceptModifications();
            }
        }
 

No additional actions required to make it work. You just change object and save it. But OrderQty property stored in SalesOrderDetail table and OrderDate stored in SalesOrderHeader, and so Worm have to create script with two update statements. Furthermore don't forget about calculated fields. The task become complex. Here is the script generated by Worm.


declare @p1 SmallInt;set @p1 = 11
declare @p2 Int;set @p2 = 1
declare @p3 DateTime;set @p3 = 07/05/2001 00:00:00
declare @p4 Int;set @p4 = 1
declare @p5 Int;set @p5 = 1
declare @lastErr int
update t1 set t1.OrderQty = @p1 from Sales.SalesOrderDetail t1 where t1.SalesOrderDetailID = @p2
declare @SalesOrderDetail_rownum int
select @SalesOrderDetail_rownum = @@rowcount, @lastErr = @@error
if @lastErr = 0 update t2 set t2.OrderDate = @p3 from Sales.SalesOrderHeader t2 join Sales.SalesOrderDetail t1 on t2.SalesOrderID = t1.SalesOrderID where t1.SalesOrderDetailID = @p4
if @SalesOrderDetail_rownum > 0 select t2.LineTotal from Sales.SalesOrderHeader t1 join Sales.SalesOrderDetail t2 on t1.SalesOrderID = t2.SalesOrderID where t2.SalesOrderDetailID = @p5

Of course the whole batch executed in single transaction and the error in any line will rollback it.

Tags:

POCO persistency: calculated fields

by Alexey Shirshov May 05, 2009 10:01

In previous post, we modify and save POCO. Very good, but we have a small problem. There is a field LineTotal in SalesOrderDetails table. The field is calculated and depends on OrderQty field. When we modify OrderQty the value of LineTotal is changing in DBMS implicity. By default Worm doesn't synchronize fields during update, you have to specify additional attributes to point out it. So, we gonna change a single line of code in GetFieldColumnMap method

columns.Add(new MapField2Column("LineTotal", "LineTotal", _tables[1], 
    Field2DbRelations.SyncUpdate | Field2DbRelations.ReadOnly));

We sad that the field is read only and have to be synchronized during updates. Now LineTotal will be changed automatically.

        static void Main(string[] args)
        {
            var o = SalesOrder.Query
                .Where(Ctor.prop(typeof(SalesOrder), "SalesOrderDetailID").eq(1))
                .Single() as SalesOrder; 

            o.OrderQty += 10; 

            Console.WriteLine("LineTotal={0}", o.LineTotal); 

            using (ModificationsTracker mt = new ModificationsTracker(exam1sharp.Properties.Settings.Default.connString))
            {
                mt.Add(o);
                mt.AcceptModifications();
            } 

            Console.WriteLine("LineTotal={0}", o.LineTotal);
        }

And here is SQL statement


declare @p1 SmallInt;set @p1 = 11
declare @p2 Int;set @p2 = 1
declare @p3 Int;set @p3 = 1
declare @lastErr int
update t1 set t1.OrderQty = @p1 from Sales.SalesOrderDetail t1 where t1.SalesOrderDetailID = @p2
declare @SalesOrderDetail_rownum int
select @SalesOrderDetail_rownum = @@rowcount
if @SalesOrderDetail_rownum > 0 select t2.LineTotal from Sales.SalesOrderHeader t1 join Sales.SalesOrderDetail t2 on t1.SalesOrderID = t2.SalesOrderID where t2.SalesOrderDetailID = @p3

Tags:

POCO persistency

by Alexey Shirshov April 24, 2009 19:52

So we have plain objects and can query them from database. We can use caching to improve query performance. But what about modification of objects? How can we save changes made in program to database. As easy as pie!

        static void Main(string[] args)
        {
            var o = SalesOrder.Query
                .Where(Ctor.prop(typeof(SalesOrder), "SalesOrderDetailID").eq(1))
                .Single() as SalesOrder; 

            o.OrderQty += 10; 

            using(ModificationsTracker mt = new ModificationsTracker(exam1sharp.Properties.Settings.Default.connString))
            {
                mt.Add(o);
                mt.AcceptModifications();
            }
        }

You don't have to change anything! You get the object, modify it, add to ModificationTracker and call AcceptModifications.

Tags:

AdventureWorks caching

by Alexey Shirshov April 21, 2009 09:57

Worm is thread safety framework therefore you can use cahing mechanisms to improve performance of your application.

Let's slightly modify the program from the previous post.

        static void Main(string[] args)
        {
            DateTime start = DateTime.Now;
            for (int i = 0; i < 100; i++)
            {
                foreach (SalesOrder s in SalesOrder.Query
                    .Where(Ctor
                        .prop(typeof(SalesOrder), "OrderDate").eq("2003-08-01")
                        .and(typeof(SalesOrder), "LineTotal").less_than(10))
                    .ToList())
                {
                    string str = String.Format("Date: {0}, LineTotal: {1}, sales territory: {2}",
                        s.OrderDate,
                        s.LineTotal,
                        s.Territory.Name);
                    //Console.WriteLine(str);
                }
            }
            Console.WriteLine("Elapsed {0}", DateTime.Now - start);
        }

We added outer loop and calculated the total time. It is 10.7186000 sec on my machine.

Now I'm gonna create cache and use it for database queries.

        static void Main(string[] args)
        {
            ReadonlyCache cache = new ReadonlyCache(); 

            DateTime start = DateTime.Now;
            for (int i = 0; i < 100; i++)
            {
                foreach (SalesOrder s in new QueryCmd(cache, exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(SalesOrder))
                    .Select(typeof(SalesOrder))
                    .Where(Ctor
                        .prop(typeof(SalesOrder), "OrderDate").eq("2003-08-01")
                        .and(typeof(SalesOrder), "LineTotal").less_than(10))
                    .ToList())
                {
                    string str = String.Format("Date: {0}, LineTotal: {1}, sales territory: {2}",
                        s.OrderDate,
                        s.LineTotal,
                        s.Territory.Name);
                    //Console.WriteLine(str);
                }
            }
            Console.WriteLine("Elapsed {0}", DateTime.Now - start);
        }

As you see, I created ReadonlyCache object and passed it to QueryCmd.

The time is 06.2186704. So we've got 33% performance boost just by changing a couple lines of code. Moreover we've unloaded DBMS from annoying short queries which is very positive fact.

Tags: ,

Trace SQL statements

by Alexey Shirshov April 20, 2009 09:58

Worm uses standard TraceSource and TraceListener classes to trace necessary information. If you want to catch trace statements in some trace listener you should add you listener to StmtSource trace source. By default StmtSource has one standard listener - DefaultTraceListener. It writes trace information into debug output.

In the example below we add Console.Out to statement listeners.

            Worm.Database.OrmReadOnlyDBManager.StmtSource.Listeners.Add(
                new System.Diagnostics.TextWriterTraceListener(Console.Out)
            );

But StmtSorce is not the only TraceSource in Worm. Since Worm supports caching not all queries will address DBMS and produce SQL statements. There is another trace source what shows information about obtained data - ExecSource.

            Worm.OrmManager.ExecSource.Listeners.Add(
                new System.Diagnostics.TextWriterTraceListener(Console.Out)
            );

Here is Console output from the previous post.


declare @p1 VarChar(10);set @p1 = '2003-08-01'
declare @p2 Int;set @p2 = 10
select t2.LineTotal, t1.OrderDate, t2.OrderQty, t1.SalesPersonID, t2.SalesOrderDetailID, t1.TerritoryID, t3.Bonus, t3.CommissionPct, t3.SalesPersonID, t3.ModifiedDate, t3.SalesLastYear, t3.SalesQuota, t3.TerritoryID, t3.SalesYTD, t3.rowguid, t4.CostLastYear, t4.CostYTD, t4.CountryRegionCode, t4.[Group], t4.TerritoryID, t4.ModifiedDate, t4.Name, t4.SalesLastYear, t4.SalesYTD, t4.rowguid, t5.CostLastYear, t5.CostYTD, t5.CountryRegionCode, t5.[Group], t5.TerritoryID, t5.ModifiedDate, t5.Name, t5.SalesLastYear, t5.SalesYTD, t5.rowguid from Sales.SalesOrderHeader t1
 join Sales.SalesOrderDetail t2 on t1.SalesOrderID = t2.SalesOrderID join Sales.SalesPerson t3 on t3.SalesPersonID = t1.SalesPersonID join Sales.SalesTerritory t4 on t4.TerritoryID = t1.TerritoryID join Sales.SalesTerritory t5 on t5.TerritoryID = t3.TerritoryID where (t1.OrderDate = @p1 and t2.LineTotal < @p2)

Resultset count: 55Execution time: 00:00:00Fetch time: 00:00:00Cache hit: FalseCount of loaded objects in resultset: 55

Tags: ,

AdventureWorks mapping: multitable entity

by Alexey Shirshov April 17, 2009 11:21

Almoust all we have done in previous posts can be implemented in LINQ to SQL. Now I'm gonna show you how to create multitable entity, which is imposible in LINQ to SQL.

Suppose we have the following two tables.

We want unite those table into a single entity called SalesOrder. This is a complex mapping, so we have to use certain class to define the mapping. I called this class entity schema class. Since we have two tables, the entity schema class should implement IMultiTableObjectSchema interface. But first let's start from SalesOrder entity.

    [Entity(typeof(EntitySchema), "1")]
    public class SalesOrder
    {
        public int SalesOrderDetailID { get; set; }
        public short OrderQty { get; set; }
        public decimal LineTotal { get; set; } 

        //SalesOrderHeader fields
        public DateTime OrderDate { get; set; }
        public exam1sharp.Sales.SalesTerritory Territory { get; set; }
        public exam1sharp.Sales.SalesPerson Person { get; set; } 

        public static QueryCmd Query
        {
            get
            {
                return new QueryCmd(exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(SalesOrder))
                    .Select(typeof(SalesOrder));
            }
        }
    }

As you can see none of the property has any attribute so this is complete POCO. All mapping data aggregated in entity schema.

SalesOrderHeader table has references to SalesTerritory and SalesPerson, so we add them in class. They are from Sales namespace.

    public class EntitySchema : IMultiTableObjectSchema
    {
        //define tables
        private SourceFragment[] _tables = new SourceFragment[]{
                new SourceFragment("Sales","SalesOrderHeader"),
                new SourceFragment("Sales","SalesOrderDetail")
            }; 

        //define join
        public Worm.Criteria.Joins.QueryJoin GetJoins(SourceFragment left, SourceFragment right)
        {
            return JCtor.join(right).on(left, "SalesOrderID").eq(right, "SalesOrderID");
        } 

        //define mapping
        public Worm.Collections.IndexedCollection<string, MapField2Column> GetFieldColumnMap()
        {
            OrmObjectIndex columns = new OrmObjectIndex();
            columns.Add(new MapField2Column("SalesOrderDetailID", "SalesOrderDetailID", _tables[1], Field2DbRelations.PK));
            columns.Add(new MapField2Column("OrderQty", "OrderQty", _tables[1]));
            columns.Add(new MapField2Column("LineTotal", "LineTotal", _tables[1]));
            columns.Add(new MapField2Column("OrderDate", "OrderDate", _tables[0]));
            columns.Add(new MapField2Column("Territory", "TerritoryID", _tables[0]));
            columns.Add(new MapField2Column("Person", "SalesPersonID", _tables[0]));
            return columns;
        } 

        public SourceFragment[] GetTables()
        {
            return _tables;
        } 

        public SourceFragment Table
        {
            get { return _tables[0]; }
        }
    }

Now you can use the SalesOrder and don't think about in what table the property you requsted is.

The program prints SalesOrder with a LineTotal less than 10 and OrderDate equals to 2003-08-01.

        static void Main(string[] args)
        {
            foreach (SalesOrder s in SalesOrder.Query
                .Where(Ctor
                    .prop(typeof(SalesOrder), "OrderDate").eq("2003-08-01")
                    .and(typeof(SalesOrder), "LineTotal").less_than(10))
                .ToList())
            {
                Console.WriteLine("Date: {0}, LineTotal: {1}, sales territory: {2}", 
                    s.OrderDate, 
                    s.LineTotal, 
                    s.Territory.Name);
            }
        }

Here is generated statement.


declare @p1 VarChar(10);set @p1 = '2003-08-01'
declare @p2 Int;set @p2 = 10
select
--SalesOrder columns
   t2.LineTotal, t1.OrderDate, t2.OrderQty, t1.SalesPersonID, t2.SalesOrderDetailID, t1.TerritoryID,
--SalesPerson columns
   t3.Bonus, t3.CommissionPct, t3.SalesPersonID, t3.ModifiedDate, t3.SalesLastYear, t3.SalesQuota, t3.TerritoryID, t3.SalesYTD, t3.rowguid,
--Order SalesTerritory columns
   t4.CostLastYear, t4.CostYTD, t4.CountryRegionCode, t4.[Group], t4.TerritoryID, t4.ModifiedDate, t4.Name, t4.SalesLastYear, t4.SalesYTD, t4.rowguid,
--Person SalesTerritory columns
   t5.CostLastYear, t5.CostYTD, t5.CountryRegionCode, t5.[Group], t5.TerritoryID, t5.ModifiedDate, t5.Name, t5.SalesLastYear, t5.SalesYTD, t5.rowguid
from Sales.SalesOrderHeader t1
 join Sales.SalesOrderDetail t2 on t1.SalesOrderID = t2.SalesOrderID
 join Sales.SalesPerson t3 on t3.SalesPersonID = t1.SalesPersonID
 join Sales.SalesTerritory t4 on t4.TerritoryID = t1.TerritoryID
 join Sales.SalesTerritory t5 on t5.TerritoryID = t3.TerritoryID
where (t1.OrderDate = @p1 and t2.LineTotal < @p2)

Sandcastle and permanent links in html documentation

by Alexey Shirshov April 16, 2009 10:04

Yesterday I open discussion in Sandcastle Help file Builder forum with a question: How can I reference html documentation pages in the way they include link to content page. Since html documentation generates frames based pages it's not trivial as you may think.

After couple of hours I contrive the solution. So what have been done

  1. During navigation through pages address bar is being changed to reflect current page.
  2. Address bar url always contain information to navigate to specific page.

For instance, my documentation http://wise-orm.com/reference/. Content frame main page url - http://wise-orm.com/reference/html/R_Project.htm. When I open main page address bar is being changed to http://wise-orm.com/reference/#html/R_Project.htm. This is a permanent link. When I copy it and open again I navigate to main page. Now implementation.

I create a little js file, named url.js

var firstUrl = window.location.href; 

function addLoadEvent(func, o) {
    if (o == undefined)
        o = window;
    var oldonload = o.onload;
    if (typeof o.onload != 'function') {
        o.onload = func;
    } else {
        o.onload = function() {
            oldonload(); func();
        }
    }
} 

function ParseUrl() {
    var idx = firstUrl.indexOf("#html");
    if (idx > 0) {
        var p = firstUrl.substr(idx).replace("#", "/");
        idx = firstUrl.lastIndexOf("/", idx);
        var root = firstUrl.substr(0, idx);
        setTimeout(function() { window.frames["TopicContent"].location = root + p; }, 0);
    }
}

Second, I modify Index.aspx and Index.html pages.

  1. I remove onload handler
  2. Include url.js script
  3. Subscribe Initialize and ParseUrl to onload event

<head>
<title>Table of Content</title>
<link rel="stylesheet" href="TOC.css">
<script type="text/javascript" src="TOC.js"></script>
<script type="text/javascript" src="url.js"></script>
<script type="text/javascript">
addLoadEvent(Initialize);
addLoadEvent(ParseUrl);
</script>
</head> 

<body onresize="javascript: ResizeTree();">

Ok, now when I open url with hash I navigate to appropriate page. But when content frame is changed the address bar remain the same. We have to attach to onload event in content frame and change address bar. The conner-cutting way is to add some code into script_manifold.js file which is loaded into every page. It lays in scripts folder. So I add to it the following function

function BringOutUrl() {
    var url = window.location.href;
    var idx = url.indexOf("/html/");
    if (idx > 0) {
        var rel = url.substr(idx + 1);
        idx = url.lastIndexOf("/", idx);
        var root = url.substr(0, idx);
        window.parent.location.hash = rel;
    }
}

And the final step - change first line in script_mainfold.js file.

window.onload = function() { LoadPage(); BringOutUrl(); };

Tags:

Relation mapping

by Alexey Shirshov April 15, 2009 09:59

In the last post I wrote that I'm finish with AdventureWorks mapping. I was wrong. :) There are still a lot of things I'm gonna show you.

Ok, let's talk about relations. In the Store class there is a property SalesPerson which means one to many relation between Store and SalesPerson. One SalesPerson has multiple Stores and one Store has single SalesPerson.

So in our model there isn't property in SalesPerson class that returns collection on Stores. Let's add it.

        public QueryCmd Stores
        {
            get
            {
                return Store.Query
                    .Where(Ctor.prop(typeof(Store), "SalesPerson").eq(this));
            }
        }

As you can see we add Stores property which returns QueryCmd. We have seen QueryCmd in previous examples. It's not a collection, it is query command which can be used to get collection by executing it. In the following example we invoke ToList method to execute command and get collection of Store class instances.

            foreach (exam1sharp.Sales.Store s in p.Stores.ToList())
            {
                Console.WriteLine("Store id: {0}, name: {1}, sales territory: {2}",
                    s.ID,
                    s.Name,
                    s.SalesPerson.SalesTerritory.Name);
            }

where p is SalesPerson variable. It can be obtained in the following manner

            exam1sharp.Sales.SalesPerson p = exam1sharp.Sales.SalesPerson.Query
                .Where(Ctor.prop(typeof(exam1sharp.Sales.SalesPerson),"ID").eq(280))
                .Single() as exam1sharp.Sales.SalesPerson;

Here we select SalesPerson with ID property equals to 280.

Tags:

Quickstart

AdventureWorks mapping: comlpete implementation of three entities

by Alexey Shirshov April 13, 2009 12:01

Ok. Let's finish our trip to AdventureWorks mapping. The following steps need to be done:

  1. Create base class containing ModifiedDate and Timestamp properties. Those properties present in all entities.
  2. Fulfil SalesPerson and SalesTerritory classes with appropriate properties
  3. Create Query static property in SalesPerson class.

So, here is complete classes code (single file).

 

using System;
using System.Collections.Generic;
using System.Text;
using Worm.Entities.Meta;
using Worm.Query;
using System.Xml; 

namespace exam1sharp.Sales
{
    public class SalesBase
    {
        public DateTime ModifiedDate { get; set; } 

        [EntityProperty("rowguid", Field2DbRelations.RowVersion)]
        public Guid Timestamp { get; protected set; }
    } 

    [Entity("Sales", "Store", "1")]
    public class Store : SalesBase
    {
        [EntityProperty("CustomerID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public string Name { get; set; } 

        public XmlDocument Demographics { get; set; } 

        [EntityProperty("SalesPersonID")]
        public SalesPerson SalesPerson { get; set; } 

        public static QueryCmd Query
        {
            get
            {
                return new QueryCmd(exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(Store))
                    .Select(typeof(Store));
            }
        }
    } 

    [Entity("Sales", "SalesPerson", "1")]
    public class SalesPerson : SalesBase
    {
        [EntityProperty("SalesPersonID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public decimal? SalesQuota { get; set; } 

        [EntityProperty("TerritoryID")]
        public SalesTerritory SalesTerritory { get; set; } 

        public decimal Bonus { get; set; } 

        public decimal CommissionPct { get; set; } 

        public decimal SalesYTD { get; set; } 

        public decimal SalesLastYear { get; set; } 

        public static QueryCmd Query
        {
            get
            {
                return new QueryCmd(exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(SalesPerson))
                    .Select(typeof(SalesPerson));
            }
        }
    } 

    [Entity("Sales", "SalesTerritory", "1")]
    public class SalesTerritory : SalesBase
    {
        [EntityProperty("TerritoryID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public string Name { get; set; } 

        public string CountryRegionCode { get; set; } 

        [EntityProperty("[Group]")]
        public string Group { get; set; } 

        public decimal SalesYTD { get; set; } 

        public decimal SalesLastYear { get; set; } 

        public decimal CostYTD { get; set; } 

        public decimal CostLastYear { get; set; }
    }
} 

Program almoust not changed.

        static void Main(string[] args)
        {
            foreach (exam1sharp.Sales.Store s in exam1sharp.Sales.Store.Query
                .Where(Ctor.prop(typeof(exam1sharp.Sales.Store), "Name").like("A%"))
                .ToList())
            {
                Console.WriteLine("Store id: {0}, name: {1}, sales territory: {2}", 
                   s.ID, 
                   s.Name, 
                   s.SalesPerson.SalesTerritory.Name);
            }
        }

And the most interesting piece of code for the real docs.


declare @p1 VarChar(2);set @p1 = 'A%'
select

   t1.Demographics, t1.CustomerID, t1.ModifiedDate, t1.Name, t1.SalesPersonID, t1.rowguid,
   t2.Bonus, t2.CommissionPct, t2.SalesPersonID, t2.ModifiedDate, t2.SalesLastYear, t2.SalesQuota, t2.TerritoryID, t2.SalesYTD, t2.rowguid,
   t3.CostLastYear, t3.CostYTD, t3.CountryRegionCode, t3.[Group], t3.TerritoryID, t3.ModifiedDate, t3.Name, t3.SalesLastYear, t3.SalesYTD, t3.rowguid
from Sales.Store t1
join Sales.SalesPerson t2 on t2.SalesPersonID = t1.SalesPersonID
join Sales.SalesTerritory t3 on t3.TerritoryID = t2.TerritoryID
where t1.Name like @p1

As you can see, the SQL code is simple, readable and solid. It's very similar to human coding. Nothing superfluous.

AdventureWorks mapping: Entity references

by Alexey Shirshov April 13, 2009 09:48

In the previous example, we create Store entity which has SalesPersonID property type of int. This is a database reference to SalesPerson object. Let's implement this reference in our model.

Obviously, we have to create SalesPerson entity.

    [Entity("Sales", "SalesPerson", "1")]
    public class SalesPerson
    {
        [EntityProperty("SalesPersonID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public decimal SalesQuota { get; set; }
    }

The next and the last thing we have to do - change type and name of the corresponding property in Store class.

        [EntityProperty("SalesPersonID")]
        public SalesPerson SalesPerson { get; set; }

That is all. Here is the code of all classes.

 

    [Entity("Sales", "Store", "1")]
    public class Store4
    {
        [EntityProperty("CustomerID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public string Name { get; set; } 

        public DateTime ModifiedDate { get; set; } 

        public XmlDocument Demographics { get; set; } 

        [EntityProperty("rowguid", Field2DbRelations.RowVersion)]
        public Guid Timestamp { get; protected set; } 

        [EntityProperty("SalesPersonID")]
        public SalesPerson SalesPerson { get; set; } 

        public static QueryCmd Query
        {
            get
            {
                return new QueryCmd(exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(Store4))
                    .Select(typeof(Store4));
            }
        }
    } 

    [Entity("Sales", "SalesPerson", "1")]
    public class SalesPerson
    {
        [EntityProperty("SalesPersonID", Field2DbRelations.PK)]
        public int ID { get; set; } 

        public decimal SalesQuota { get; set; }
    } 

And the program prints sales person quota information.

        static void Main(string[] args)
        {
            foreach (Store4 s in Store4.Query
                .Where(Ctor.prop(typeof(Store4), "Name").like("A%"))
                .ToList())
            {
                Console.WriteLine("Store id: {0}, name: {1}, sales person quota: {2}", s.ID, s.Name, s.SalesPerson.SalesQuota);
            }
        }

AdventureWorks mapping: almost full Sales.Store implementation

by Alexey Shirshov April 08, 2009 10:21

It's time to complicate an example.

First of all I'm going to assign database table to our entity. In the previous examples, we have to specify table in From method. To avoid this we should mark entity class with EntityAttribute attribute.

[Entity("Sales", "Store", "1")]

Here we specify Sales database schema and Store table name. 1 - entity schema version (forget itSmile).

The next improvment - implement static Query property returns QueryCmd instance.

    [Entity("Sales", "Store", "1")]
    public class Store3
    {
        [EntityProperty("CustomerID", Field2DbRelations.PK)]
        public int ID { get; set; }

        public string Name { get; set; }
        
        public DateTime ModifiedDate { get; set; }
        
        public XmlDocument Demographics { get; set; }        
        
        [EntityProperty("rowguid", Field2DbRelations.RowVersion)]
        public Guid Timestamp { get; protected set; }

        public int SalesPersonID { get; set; }

        public static QueryCmd Query
        {
            get
            {
                return new QueryCmd(exam1sharp.Properties.Settings.Default.connString)
                    .From(typeof(Store3))
                    .Select(typeof(Store3));
            }
        }
    }

The program prints stores whos name starts with A.

        static void Main5(string[] args)
        {
            foreach (Store3 s in Store3.Query
                .Where(Ctor.prop(typeof(Store3), "Name").like("A%"))
                .ToList())
            {
                Console.WriteLine("Store id: {0}, name: {1}, timestamp: {2}", s.ID, s.Name, s.Timestamp);
            }
        }

Tags:

Quickstart

Continue AdventureWorks mapping

by Alexey Shirshov April 07, 2009 16:01

In the previous post we have seen how simple we can map database table to class. Let's extend the mapping.

Suppose we don't want to use CustomerID. Instead, we want ID property. Also we want to add ModifiedDate column. Here is the code

    public class Store2
    {
        [EntityProperty("CustomerID")]
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime ModifiedDate { get; set; }
    }

The program might look like previous version with Store class.

        static void Main(string[] args)
        {
            var query = new QueryCmd(exam1sharp.Properties.Settings.Default.connString);

            foreach (Store2 s in query
                .From(new SourceFragment("Sales", "Store"))
                .ToPODList())
            {
                Console.WriteLine("Store id: {0}, name: {1}", s.ID, s.Name);
            }
        }

Tags:

Quickstart

Open and build solution

by Alexey Shirshov December 23, 2008 16:29

If you download code from Source control after opening solution you will be prompted to remove Source control bindings. Choose Yes, 'cause you don't have an account in codeplex or at least you are not member of worm team. If you download source code from release package, this step will not appear.

After that you will be asked about VSTool project what needs VS SDK to be installed. Press ok to unload the project.

Ok, now the solution is loaded and could be built. But additional actions could be required to run test.

First of all, almost all test requires SQL database, so you have to have SQL Express or higher edition of SQL Server. By default connection string starts with "Server=.\sqlexpress". You should replace this part according your server location. All changes should be made in UnitTesting project.

Next, you should define or undefine UseUserInstance custom constant in Advanced compiler setting window at compile tab.

Now, all tests should work fine.

Tags:

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen | Modified by Mooglegiant

The Author

My name is Alexey Shirshov. I'm a professional developer with wide specialization. I prefer VB.NET to C#, I hate ASP.NET but there is no better than it. You can contact me by this page.