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.