ORM N-Tier Domain Model Forward Development

8 posts, 1 answers
  1. Brian
    Brian avatar
    40 posts
    Member since:
    Oct 2008

    Posted 21 Sep 2010 Link to this post

    Requirements

    RadControls version

    ORM 2010.2.804.5, WPF4 2010.2.0812
    .NET version

    4
    Visual Studio version

    2010
    programming language

    C#
    browser support N/A since WPF

    PROJECT DESCRIPTION
    I have invested a significant amount of development time learning the ORM product this year and wanted to make a 'best practice' that included what I've learned.  This project is designed to highlight what you need to be successful under the new Domain Model paradigm that came out with the ORM July 2010 release.  There are several challenges I encountered, and some issues I have yet to overcome, which I will highlight below.  My project is setup as an N-Tier effort, to reflect how I tend to work since my ORM libraries tend to get used by multiple clients (i.e. WPF, WCF, ASP.NET).  The layers are:

    • MyModel -- this is the ORM DataModel that talks directly to the database (i.e. where the .rlinq file lives)
    • MyLogic -- this is the business logic layer that handles logic to pull data in and send it out from MyModel so the clients do not need to know the details.  It also handles forming associations between entities.
    • MyClient -- this is a WPF application that creates an instance of a MyLogic class, which creates the database and adds default data to it, then reads and displays it.  It also consumes events that come up from exceptions or informational data from MyModel and MyLogic.

    A picture of my simple data schema can be found in the MyModel.bmp file in the attached MyORM.zip file, which is designed to represent an Organization (Org) that can have multiple people and exist ant multiple locations.

    MyModel design practice highlights:

    • Most classes inherit from MyBase, so that I can have a creation date and last updated date field to know when data was added or changed.
    • Notice that in MyModel, I created a Generated directory where MyContext.rlinq sends its generated class files.  In a directory called Extensions, I put my partial class extensions to those same files.  This allows me to do handy things like create parameterized constructors, create Find and Persist functions, and overload key operators to test for equality.
    • One of the HUGE things that has helped me is the creation of NullEquals type functions.  These are nice because it lets me treat null == null as true for database values.  You will see several overloaded versions of this.  This is used with a NullTrim function which will force a whitespace only or empty string to be explicitly null, which makes comparisons easier later in Find calls.
    • Added an AssignIfNotEqual function for IList comparison when you want to completely replace a set of linked references.  For example, if you want to replace Orc.Loc, which is an IList<Loc> with a new set of associates in one line.
    • Overriding ToString is also handy, to make it more human readable.
    • I did not bother to make the WPF radgridview show the ToString version of Loc and Org, but you can with a little XAML.
    • The Persist function in each entity class provides a consistent way to add or update data.  In debugging my ORM code, I found most issues had to do with inconsistent implementation of Find calls, case issues, whitespace or null issues, and not clearly specifying what the search key is.  In the Persist function, it needs to check for changes in all fields that are NOT the key.
    • Created a MyContextManager which provides handy functions to initialize the database (create it, update the schema, add default data).  It also provides some metadata to return Tables, Views, and StoredProcedures for the MyModel context (useful for dropdowns in the GUI).
    • When you want to create a 1:n relationship, I figured out (the hard way) that you first need to add OrgType_ID:Int32 to your model, then create an OrgType_ID field in your database table of type Int32, then associate the two in your Model Details view, then drag the association from the header of Org to OrgType, then associate OrgType.ID with OrgType_ID.  Telerik, this is NOT clear from the examples until one of the latest videos.  I would expect future versions of ORM to automatically create the model, table, and such fields when you create the relationship.  It is confusing to have two entries in the same entity for the same thing.

    MyLogic design practice highlights:

    • Created InsertCount, UpdateCount, NoChangeCount, and TotalCount for showing the status of record changes in the GUI.  I'm not fully using this for this example project, but it saves a lot of time testing because you can explicitly see how many updates or inserts there were.  A good test is to present the same data set twice, and you should only see NoChange count increase.
    • I also setup some GuiStatus an LogEvent handlers for sending notice to the GUI that something changed or that an error needs to be displayed.  Way better than just letting the exception go all the way out without capturing it (i.e. let the GUI decide how to handle it).
    • Notice in Company.AddDefaultPeople, I'm using the FindAdd function to quickly check if a given record exists already in the database, and if not, add it.  I also return a reference to the record from the database so that the .ID field is set properly.  Vital to avoid runtime error later when you try to use it to create links between entities.

    MyClient design practice highlights:

    • Created an App.config to copy the connection string from MyModel (see this thread for details).  This lets a new MyContext() call work for the client correctly.
    • Note that the WriteStat method periodically clears the log RichTextBox when it gets full - very handy to avoid memory and super slowdown issues when badness happens.
    • Note that company_GuiStatus does not fire an expensive UpdateCompany call every time the event fires, as this kills performance on the GUI.  This way, you can let the lower level stuff fire that event as much as they want for status updates that get overwritten with newer values anyway.
    • I created simple hierarchical grids to show the child data instead of the default view which shows the type name for an IList (which you can see in the child view when you expand them).

    Problems I have yet to overcome (i.e. help Telerik master coders!):

    • In MyContextManager.Initlialize, it will create the MyORM database when it is not there, but the ddl_script string stays null or empty in either case.  I was able to get this working with the Database class in pre-July 2010 ORM code, but I can't here.  I created the schema by right clicking the model and telling it to update the database from the model.  Help Telerik on this one! :)
    • In Company.AddDefaultPeople, if I change new Person(dt, "Tom", "Joe", "Jones", loc2) to new Person(dt, "Tom", null, "Jones", loc2), I get an unfriendly run-time error having to do with null.  I made sure that the SQL data allows null and that the ORM model does too for the middle name.  Not sure how to solve this.
    private static bool _First_Initialize = true; // Only need to do this once, and if parallel, avoids exception on multiple instances
    /// <summary>
    /// Initialize ORM Database schema
    /// </summary>
    /// <returns>True if database exists</returns>
    public static bool Initialize()
    {
        bool db_exists = false;
        if (_First_Initialize)
        {
            _First_Initialize = false;
            using (MyContext context = new MyContext())
            {
                ISchemaHandler schema = context.GetSchemaHandler();
      
                string ddl_script;
                if (schema.DatabaseExists())
                {
                    ddl_script = schema.CreateUpdateDDLScript(null);
                }
                else
                {
                    schema.CreateDatabase();
                    ddl_script = schema.CreateDDLScript();
                }
                if (!string.IsNullOrWhiteSpace(ddl_script))
                {
                    schema.ExecuteDDLScript(ddl_script);
                }
                db_exists = schema.DatabaseExists();
            }
            using (MyContext context = new MyContext())
            {
                // Update or Add default data to the database to pre-populate required entities before general use
                OrgType.AddDefaultData(context);
            }
        }
        else db_exists = true;
        return db_exists;
    }
  2. Answer
    Zoran
    Admin
    Zoran avatar
    534 posts

    Posted 22 Sep 2010 Link to this post

    Hello Brian Womack,

     First thanks a lot for the useful code library example. Regarding issue 1) - I have to admit that it is a missing feature that you are not able to configure the designer created objects so that they update the database through code calls for the schema update API. This is something we will definitely address by Q3 2010 release. For this version, the users of this code library will have to open the .rlinq file in the Visual Designer and then run the "Update Schema From Model" wizard in order to create their database schema. 
    Regarding the second issue you faced, that is already fixed in the example which we modified a bit in order for it to run correctly on customer machines out-of-the-box. The reason for the exception was the way you had constructed your Linq query:

    (from d in context.People
                        where d.First.Equals(fi_first, StringComparison.InvariantCultureIgnoreCase) &&
                        ((d.Middle == null && fi_middle == null) || d.Middle.Equals(fi_middle, StringComparison.InvariantCultureIgnoreCase)) &&
                        d.Last.Equals(fi_last, StringComparison.InvariantCultureIgnoreCase)
                        select d).SingleOrDefault();

    The query would run correctly and return the desired results if you modify it by omitting the null check for the MiddleName property as that is internally handled by OpenAccess. Here is our modified version of the query:
    (from d in context.People
                        where d.First.Equals(fi_first, StringComparison.InvariantCultureIgnoreCase) &&
                        (d.Middle.Equals(fi_middle, StringComparison.InvariantCultureIgnoreCase)) &&
                        d.Last.Equals(fi_last, StringComparison.InvariantCultureIgnoreCase)
                        select d).SingleOrDefault();

    Hopefully we will have the first issue resolved as soon as possible so that no obstacles remain in your application.
    Once again, thanks a lot for your contribution.


    All the best,
    Zoran
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  3. Brian
    Brian avatar
    40 posts
    Member since:
    Oct 2008

    Posted 22 Sep 2010 Link to this post

    Hmm, I want to treat null.Equals(null) as true.  Does your code work that way?  It usually returns false, hence all of my NullEquals functions.  Maybe I have it backwards as to how the rest of .NET handles .Equals.

    As to programmatic model creation, it would be awesome if you, at a minimum, made it work when the database is empty.  Then I can just delete the database and the code and recreate it the same way the designer does to save a bunch of manual steps.  I also noticed the designer does not create update scripts successfully either yet.

    By the way, I spent about two months of solid effort since the release of Q2 to figure all this stuff out, mainly due to the sketchy nature of the documentation, examples, and videos.  It would be *REALLY* nice if I got a large chunk of telerik points for this effort, since I'm due to renew really soon.  Any luck there?  (hint hint) *SMILE*

    I do have another project on a bunch of WPF resource and styling stuff too that I'll upload soon as well.
  4. Zoran
    Admin
    Zoran avatar
    534 posts

    Posted 23 Sep 2010 Link to this post

    Hello Brian Womack,

     You are right, we will surely address all of the issues regarding the database schema migration API. I understand your struggle with documentation as it has been in development phase while you have been doing your work. However I believe that at the moment the documentation covers about all of the OpenAccess ORM aspects and we are preparing much nicer examples for Q3 too. 
    Please let me apologize for not giving you the Telerik points, you have earned them for sure, it is just a glitch on our side.

    Greetings,
    Zoran
    the Telerik team
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
  5. richard
    richard avatar
    9 posts
    Member since:
    Jul 2012

    Posted 30 Oct 2010 Link to this post

    Thanks for giving back. Very rarley do see such a truly elegant example.
  6. Brent
    Brent avatar
    33 posts
    Member since:
    Jun 2010

    Posted 07 Dec 2010 Link to this post

    I have downloaded the latest Q3 build 2010.3.1125.1 and the schema.CreateDDLScript still does not appear to be working.  Any update?

    Brent
  7. Rick Hubka
    Rick Hubka avatar
    33 posts
    Member since:
    Apr 2006

    Posted 09 Dec 2010 Link to this post

    Very rarley do see such a truly elegant example
    Very rarely do I see ANY example for ORM that works at all..
    Do you people have anybody testing your tutorials and examples?
    I am simply going to throw up my hands and use another ORM tool.
    Go ahead and follow your latest help file on the ORM sample SofiaCarRental for Web.  There is so many mistakes.  Neither the tutorial nor the downloaded C# sample work as written.  I think these samples are CutNPasted together from various sources and never even tried by any Telerik staff.

    Signed....  Frustrated
  8. Zoran
    Admin
    Zoran avatar
    534 posts

    Posted 09 Dec 2010 Link to this post

    Hello Rick Hubka,

     We appreciate your feedback. I guess that you are referring to the version of the documentation which is available online. Indeed we have some inconsistencies there due to some problems in our help uploading process. Our team is working on that and the help should be updated at some point this week. Some minor inconsistencies were actually spotted after a review of the content I must admit and thank you for that, it is a fact that  if someone copies and pastes the exact same content presented in the help he/she might end up with non-compiling code. 

    However, OpenAccess made a significant step towards improving the experience of its customers with regard of the examples and integration with different technologies (Asp.Net, WPF, Silverlight..). With that I would like to introduce you to the OpenAccess SDK. I really believe that it should provide answers and better experience with our product for you. You can download it from the product download page

    We are looking forward for your feedback.

    Best wishes,
    Zoran
    the Telerik team
    Accelerate your learning with industry's first Telerik OpenAccess ORM SDK. Download today.
Back to Top