For many years, I have been developing applications that require database development, and a data tier to perform the basic CRUD operations. As many of you who read my blog may also be doing the same thing. The .net developer is the master at data manipulation. We can do anything. We just like to spend all our time on the simple things that could be automated. That's where Plinqo comes in.
One thing that we do, that drive us nuts, is spending time on the same repetitive tasks. Yes, the CRUD operations usually are the same thing over and over. Another thing that drives us nuts is when there is a schema change. What do you do with your old data objects? Well, Plinqo will solve this issue for you.
Not to long ago, I was researching a replacement for the old school methods we were using. I used to create XSD datasets and then I had used designed a template CodeSmith to generate a business object that performed the basic CRUD operations against those datasets needed in my app. Although I thought this was an automated way of doing things, we coudl take it a step further. I still had to do everything by hand, and changes meant I had to regenerate many of the same classes I had been using. While researching an alternative, I was testing all the big ones like nHibernate, and even went back to some stuff like CSLA. In the end, the absolute fastest and most effective method of doing what I wanted to do was using plinqo.
This will warrant a demonstration of how quick and easy it is to get setup.
1.) Design your database. For this sample, I put together a very simple database for something I am currently testing.
2.) Loadup Plinqo in CodeSmith. CodeSmith is a must for .net developers.
3.) Configure all the properties in the CodeSmith developer. This QuickStart template will create everything from the Data Project, to a test application. I personally don't use those, as i already know they work.
4.) Press the Run button, and CodeSmith studio will create the plinqo template files needed to generate your data tier.
6.) Load up your Visual Studios by double clicking on the solution it created.
7.) Right click on the csp file that's in the linq project and click "Generate"
You will now notice a bunch of new files that it has generated. These are files that are from the database -> Code.
Technically at this point, you are ready to start using it.
8.) The next step is something I do. I think this has been resolved in the latest version of plinqo and I have submitted for this issue a while back and they said it was resolved now, but I still do it manually anyways.
Notice that it generated a .dbml file. This dbml file holds the schema to your entire database. Its very nice because you can make changes within it and the generator will keep your changes when you re-generate. Its also nice because if you change something your database, or add a new table, it will pick it up, and add it to your modified .dbml file for you, and even create the missing .cs (.vb if you still go that route - yuck).
The 2 things that I change are two auto-generated type names.
Open up the dbml file, and click on the white area and go to the properties of the dbml.
Change the Name to something nicer like: EventDataContext and save it.
Next right click on the csp file, and go to Manage Output. Select "Managers" and "Edit Output" - Change the DataManagerName to something a little more attractive instead of the default (it usually isnt something you woudl want to type in in your code). I usually change it to something like [Project]DataManager so in this case EventDataManager. Save and close.
Go and delete the files that it generated in the /Entities and /Managers, and re-run the Generate on the csp file. This is a safe thing to do, and it will refresh all your objects.
10.) You now have a very nice data tier for your database. Now, I have taken it a step further, and actually created a CodeSmith studio template that actually makes easier business objects out of the classes. But, if you don't have to use that to take advantage of this already ready to go app. I am a big fan of factory patterns, so the only thing a user can do to generate a new instance is the static methods available. Here is an example of its usage (output from one of my templates):
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Auto Generated by HJT PLinqo Extension class
// Version 1.0.6 - 7/08/2009
//
// DO NOT CODE WITHIN THIS FILE - Use Partial class to extend functionally
//
// Usage:
// Generate to work with your plinqo objects.
// Extend with partial classes to support additional functionally
//
// Revision History:
// 1.0.0 - Travis Whidden - 3/30/2009
// 1.0.1 - Travis Whidden - 4/06/2009
// - Added new static partial method for Initialize
// 1.0.2 - Travis Whidden - 4/07/2009
// - Return null on get methods that dont return anything.
// 1.0.3 - Travis Whidden - 4/24/2009
// - Added the Linq Data Manager - Was hard coded before when it shouldn't be.
// - Base Class Name Space and option to say which base class to use.
// - Object Constructors to use the base class DataContext instead of the Linq DataContext
// - GetByQueryExpression is requiring DataContext instead of the Linq DataContext class
// 1.0.4 - Added an option that will not allow null values into the database. It will insert the
// default value for a type if one is not specified. Helps against failures inserting into
// databases that dont allow null values.
// 1.0.5 - Deconstructor could possibly dispose of linq objects before they are done being used.
// Disabled in this version. GC will handle on its own.
// 1.0.6 - Travis Whidden - 7/8/2009
// - Fixed the Save with No nulls to use type codes instead of guessing the reflection type
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
namespace HJT.DataEvents.Linq
{
///
/// DataEvent Entity Container for managing the Linq object and life cycle.
///
[Serializable()]
public partial class DataEvent : HJTLinqBase, IDisposable
{
#region "Class Members"
///
/// Holds the reference to the Linq entity associated with this business object.
///
private DataEvents _dataEvents = null;
#endregion
#region "Constructors"
///
/// Default Constructor - Only use for serilization
///
internal DataEvent(){}
///
/// Used to create a new DataEvent object.
///
///
internal DataEvent(DataContext context)
{
_context = context;
_isNew = true;
_dataEvents = new DataEvents(); // Create a new DataEvents Object used to hold the user info
}
///
/// Used to manage an existing user session object.
///
///
///
internal DataEvent(DataContext context, DataEvents dataEvents)
{
_context = context; // Used throughtout the objects lifecycle
_dataEvents = dataEvents; // Sets the reference to the object we will use through out thsi object lifecycle.
}
///
/// Default Deconstructor. Disposes of anything that could potentially create a memory problem.
///
~DataEvent()
{
// Disabled for now - possible disposing of the object before its done being used
//this.Dispose();
}
#endregion
#region "Properties"
///
/// Holds reference to the existing DataEventsObject
///
public DataEvents DataEventsObject
{
get { return _dataEvents; }
}
#endregion
#region "Methods"
///
/// Delete this current entity.
///
public override void Delete()
{
DataEvent.Delete(this);
}
///
/// Saves this current entity
///
public override void Save()
{
// Enumerate over all the properties to ensure they are not null
var members = this.DataEventsObject.GetType().GetProperties();
foreach (var info in members)
{
if (info.GetValue(this.DataEventsObject, null) == null)
{
try
{
var typeCode = Type.GetTypeCode(info.PropertyType);
if (typeCode == TypeCode.String)
{
info.SetValue(this.DataEventsObject, "", null);
}
else if (typeCode == TypeCode.DateTime)
{
info.SetValue(this.DataEventsObject, DateTime.Parse("1/1/1970"), null);
}
else if (typeCode == TypeCode.Boolean)
{
info.SetValue(this.DataEventsObject, false, null);
}
else if (info.PropertyType == typeof(Guid)) { }
else
{
// Numeric?
if(info.PropertyType.IsValueType)
{
info.SetValue(this.DataEventsObject, 0, null);
}
}
}
catch (Exception ex)
{
//Debug.WriteLine("Null Type could not be changed to non null: " + info.GetType().Name +
// " in object " + this.GetType().Name);
}
}
}
DataEvent.Save(this);
_isNew = false;
}
// Disposes of the Data Context - this is only active within this object.
public void Dispose()
{
if (Context != null)
{
Context.Dispose();
_context = null;
}
}
#endregion
#region "Static Methods"
///
/// If the object needs to be initialized, it can be safely added the partial class for this partial method.
///
///
static partial void Initialize(string connectionString);
///
/// Create an instance of the DataEvent object.
///
/// Database Connection String
/// Returns a fresh, ready to go object.
public static DataEvent Create(string connectionString)
{
// Initialize anything this object may require to be initialized first
Initialize(connectionString);
var newObject = new DataEvent(new EventDataContext(connectionString));
// Set the primary key(s) for the user if needed
newObject.DataEventsObject.DataEventID = 0;
// Return the ready to go object.
return newObject;
}
///
/// Retreives the DataEvent object from the database
///
/// The database connection string where this request is being made.
/// The primary Key (dataEventID) that is being requested.
///
public static DataEvent Get(string connectionString, long dataEventID)
{
// Initialize anything this object may require to be initialized first
Initialize(connectionString);
// The data context that is used.
var context = new EventDataContext(connectionString);
// The Manager for retrieving the data
var manager = new EventDataManager(context);
// Gets the database object from the Database using the manager
var dbObject = manager.DataEvents.GetByKey(dataEventID);
// If the object is null, we want to return nothing
if(dbObject == null){
context.Dispose();
return null;
}
// Creates a new singular type with extentions
var dataEvent = new DataEvent(context, dbObject);
// return
return dataEvent;
}
///
/// Returns a list of the DataEvent Objects
///
///
///
public static IEnumerable GetAll(string connectionString)
{
// Initiatlize anything this object may require to be inialized first
Initialize(connectionString);
// Data Context Creation
var context = new EventDataContext(connectionString);
// Generic Container for calling
var results = new List();
// Enumerate throught the Linq Objects
foreach (var result in context.DataEvents.ToList())
{
// Create the new container object
results.Add(new DataEvent(context, result));
}
return results;
}
///
/// Return the base of a Linq object request so a query can be extended. This isn't public exposed, as we want
/// to have control in our business objects on what is permitted to be queried. Also, these objects once converted
/// into business objects must be wrapped in this business layer to have the functions that this layer creates.
///
///
internal static IQueryable GetByQueryExpression(DataContext context)
{
// Initialize anything this object may require to be initialized first
Initialize(context.Connection.ConnectionString);
// Becuse we dont know anything about the query at this point in time, we will need to depend on
// the calling object to create the data context for us. We dont wnat to hide the data context
// as the IQueryable at this point is just the base query that is needed for the rest of the query.
return ((EventDataContext) context).DataEvents;
}
///
/// Delete an instance of the DataEvent object
///
/// The DataEvent object to remove
public static bool Delete(DataEvent dataEvent)
{
if (dataEvent != null)
{
// Initialize anything this object may require to be initialized first
Initialize(dataEvent.Context.Connection.ConnectionString);
// Reqeusts the Data SQL from the Linq Expression
var result = from myRows in DataEvent.GetByQueryExpression(dataEvent.Context)
where
myRows.DataEventID == dataEvent.DataEventsObject.DataEventID
select myRows;
// Gets the single or default result (null is default)
var singleRow = result.SingleOrDefault();
// Dont try and delete something that isnt there.
if (singleRow != null)
{
// Adds the single row to the delete queue
((EventDataContext)dataEvent.Context).DataEvents.DeleteOnSubmit(singleRow);
// Commit the changes to the database.
dataEvent.Context.SubmitChanges();
return true;
}
}
// No data was deleted.
return false;
}
///
/// Saves the DataEvent to the database
///
/// the DataEvent object that is being saved
public static void Save(DataEvent dataEvent)
{
if (dataEvent != null)
{
// Initiatlize anything this object may require to be inialized first
Initialize(dataEvent.Context.Connection.ConnectionString);
// If the object is a new object (not in the database), we have to insert it first into the context.
if (dataEvent.IsNew)
{
((EventDataContext)dataEvent.Context).DataEvents.InsertOnSubmit(dataEvent.DataEventsObject);
}
// Submit changes
dataEvent.Context.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
}
#endregion
}
}
I have not really decided if I want to release my template to the general public. We used to work with people that have stolen our code and ideas and then created legal issues for us, so by releasing my templates I risk them doing it again. If you would like a copy of my template, please email me directly travis |atsign| hjtcentral.com and I will email you what I have. Its very nice, and it has a base class to go with it.
In conclusion. If you don't want to spend your whole month building the data tier, and are looking to code on the fly - plinqo is the way to go. If you are just starting to use it, you will understand how powerful it is very soon. Although there are other good tiers you can go with, after my testing I found this to be the fastest and most efficient method of doing it for what I was doing. I hope this helps jump start your plinqo experience