A simple Business Object wrapper for LINQ to SQL
I've been talking about how I'm using a small business object wrapper around LINQ to SQL to provide better abstraction of the LINQ to SQL functionality and I suppose the time's come to be bit more specific. This is going to be a long post with a fair bit of code and a brief discussion of the whys and hows. So I want to share my ideas of what I'm thinking here and an overview of what I've built so far. It's not complete, but enough to work with to get a feel for it and I'm using it successfully with a sample app I'm building for my ASP.NET Connections session. Granted it's not a complicated app (a Time Trakker) but I have to admit - especially given my original misgivings and skepticism towards LINQ to SQL - it's working out pretty damn well.
I'm throwing this out now to get some thoughts and feedback and I'll post my current code so any so inclined can play around with this, but keep in mind this is rougher than stuff I usually put out publicly so it may take some twiddling.
So... where to start?
LINQ to SQL is essentially an OR/M tool that requires modeling your data model to an object model. But while you are using objects, in essence LINQ to SQL still is little more than a fancy Data Access Layer. It does not make a business object layer or middle tier.
It seems these days the concept of business objects is getting buried somewhat under the veil of technology. I was reminded of that the other day while reading a post on Rocky's blog regarding CSLA in comparison Enterprise Framework. We are so absorbed by the discussion of the technological choices that the business and logistical aspects and separation of responsibility often just seems to get lost. It's rare these days to hear people talking about business objects anymore - the focus is all on the front end and the data access technology itself which is kinda sad, because in my mind at least business objects really provide the biggest productivity and stability boost in app development.
My goal with what I'm building is not to build an end all framework - I've never been a fan of big frameworks, but ever since the dawn of time it seems I've built myself a small set of business object wrappers that facilitate the process of building data centric applications. And this is just one more iteration of it. This is nowhere as complete as CSlA or Mere Mortals or some other framework would be but it is also smaller and easier to work with. In time, I'm sure bigger frameworks will integrate with LINQ to SQL or Entity Framework too, but right now if you want to use this technology you have to roll your own. Again, this is my vision for my needs and I'm simply sharing it here - I'm not trying to get into a holy war on which approach is better <s>.
So, Business Objects in my mind serve several main purposes (at a minimum):
Logical Abstraction
They abstract the object model in such a way that all code related to accessing the object model (in this case LINQ to SQL Entities) as well as any other incidental data access in one place. While an object model may map database tables and relationships 1 - 1 , the business layer can combine these objects in any combination necessary to provide the logical high level abstraction. The typical example here is an invoice object that controls Invoice header, line items and customers for example. The invoice knows how to access all of these components and its the business object that accesses the model not the front end code. Front end code typically interacts with the business object and receives result data (queries or data objects) or in the case of CRUD operations entity objects to work with in the UI.
Code Abstraction
Just as important as the logical abstraction is the code abstraction in that all code related to a business object ends up in one place. This may be a single class or a set of classes, but there's a distinct place where are 'business logic' is addressed - always in one place. Never does the front end code talk to the data directly (although with LINQ there's some blurring of this line because it gets a little fuzzy of what 'data' means when you're dealing with LINQ expressions) it only sees the results which are put together by the business object. One huge advantage with this scenario is that you can easily reuse business object code in different applications or even different parts of your application. So if you build an ASP.NET page that access the business object, you use the same business object you might use a in a Web Service or even a Windows Forms application. This sort of code reuse simply cannot happen if you stick any sort of data access code into the UI using DataSource controls <g>.
CommonBusiness Object and Data Functionality
LINQ to SQL provides a pretty easy data access model through the LINQ to SQL entity classes and it's pretty straight forward. A business layer built ontop of LINQ to SQL can certainly take advantage of this functionality and reduce the amount of code that needs to get written to provide the basic DAL functionality (which in non-OR/M environments must be built separately). With LINQ to SQL a business layer can leverage that functionality directly and even provide much additional value for data retrieval operations by returning results as LINQ queries as I mentioned yesterday.
But there are also many things missing that a business layer needs. Validation for example, is not really something that you can or should handle on the entity model itself. LINQ to SQL does provide OnValidate methods for each mapped property it creates, but this sort of mechanism is very difficult to consolidate in a unified validation strategy. So at minmium there should be a ValidationErrors facility that can be checked and used to ask a business object to validation itself with a simple Validate() method perhaps.
Loading and Saving too should be simpler for front end code than what LINQ to SQL provides. Realistically UI code should have to know very little about the data it needs to access and certainly for simple tasks like retrieving an entity instance you should just be able to provide a key or potentially call specialized bus object methods that return an entity. This translates into standard Load(pk) and Save() methods that know what to do without any further setup.
Given that LINQ to SQL has some 'issues' with disconnected operation that model should also be abstracted. In my simple framework I have an option flag on the business object that allows either connected or disconnected operation. By default the same connected mode that LINQ to SQL uses is used where a DataContext instance jacked to the business object holds the context's change state. Any Save Operation then simply calls submit changes. In disconnected mode the business object creates a new context for each operation - to feed entities out and to save them back. The connected mode is more efficient, but if you need to disconnect it's easily done or can be overridden for the default behavior altogether when the object is created.
Finally LINQ to SQL provides essentially DAL functionality - but it doesn't provide much in the way of core ADO.NET operations which in some situations might be handy. There's no direct support for executing a SQL command and returning a DataReader or DataTable for example. There's not a lot of need for this functionality, but I've found that at times it's damn handy to be able to return data in otherways. So there's an extension of the DataContext that provides the abillity to easily create commands, parameters, and run database queries and commands more easily.
There's also a Converter class on the business object that can take a query (generated through this same business object's DataContext) and convert it into a resultset other than an EntityList. This it turns out is pretty important in that data binding to good old ADO.NET types is much more efficient than binding to entity lists which require Reflection to bind each data item.
A simple Business Framework for LINQ to SQL
So without much further ado here's what I've built so far. Keep in mind that this is just a start although I'm finding that what I have so far is quite adequate to work with.
Here's a a class diagram (with the hacked markup since VS can't deal with the generic associations) that shows what the object model looks like:
From a usability point of view compared to LINQ to SQL there's basically just one extra level of indirection: The business object. So to use this model, you create a new business object that inherits from wwBusinessObject<TEntity, TContext> and you provide the generated DataContext and EntityType from your LINQ to SQL model as generic parameters.
For example:
public class busEntry : wwBusinessObject<EntryEntity, TimeTrakkerContext>
A DataContext is created for each business object and it manages its own context. So two business object instances don't share the same context state.
The TEntity generic parameter is used for providing a link to the 'main' Entity that is associated with the business object. The business object may deal with more entities internally but there's usually going to be a primary entity that drives the BO. The primary entity is what is updated for the built in CRUD methods, so when you call Load() you get an instance of the provided entity type.
Note that the business object also has an internal Entity member which is loaded by each of the load operations. I've always found this very convenient because it makes it easy to pass around entity data in the context of a page without having to create separate instances. Load() and NewEntity() will automatically set the internal entity member, but also return the entity as return value. The .Entity property is merely a convenience.
For CRUD operations the business object doesn't require any setup or configuration code. Simply the above definition is enough to use code like the following:
this.Entry = new busEntry();
if (this.Entry.Load(10) == null) // load by pk
return false;
this.Entry.Entity.Title = "My Entry";
if (!this.Entry.Save())
{this.SetError(this.Entry.ErrorMessage);
return;}
this.Entry.NewEntity();this.Entity.Title = "New Entity";
this.Entity.Save();// *** Still pointing at new entitythis.Entry.Entity.Title = "Something else";
// *** Delete the entityif (this.Entry.Delete())
this.SetError("Couldn't delete: " + this.Entry.ErrorMessage);
This may not seem like a big improvement over LINQ to SQL, but it's actually a lot less code than you'd have to write even with plain LINQ to SQL code and it handles a fair amount of details behind the scenes such as error handling.
Here the client code is not talking directly to the data model to retrieve or save data - all it does is interact with the entity and then call the business object 'to deal with it'. No LINQ syntax for any of this which is as it should be IMHO. The UI doesn't talk to the data or even the model directly, only to the entities.
Query methods in the business object (such as GetRecentEntries() for example) generally are implemented as methods that return Query objects. Preferrably you'd want to return strongly typed entities like this bus object method:
/// <summary>
/// Get open entries for a given user
/// </summary>
/// <param name="userPk"></param>
/// <returns></returns>
public IQueryable<EntryEntity> GetOpenEntries(int userPk)
{IQueryable<EntryEntity> q =
from e in this.Context.EntryEntities
where !e.PunchedOut orderby e.TimeIn select e; // *** Add filter for User Pk - otherwise all open entries are returned if (userPk > 0)q = q.Where(e => e.UserPk == userPk);
return q;}
So that the front end code can optionally further filter the query. When a strongly typed query is returned there are a lot of options to deal with the data as shown in the following snippet:
protected override void OnInit(EventArgs e)
{ base.OnInit(e); // *** Set up the base queryIQueryable<EntryEntity> entries = this.entry.GetOpenEntries(this.TimeTrakkerMaster.UserPk);
int count = entries.Count(); if (count == 1) { int? Pk = entries.Select(en => en.Pk).FirstOrDefault();if (Pk == null)
Response.Redirect("~/Default.aspx"); Response.Redirect("~/punchout.aspx?id=" + Pk.ToString());}
// *** Assign the data source - note we can filter the data here!this.lstEntries.DataSource = this.entry.Converter.ToDataReader(
entries.Select(en => new { en.Pk, en.Title, en.TimeIn }));
this.lstEntries.DataBind();}
All the queries that actually hit the database are shown in bold. Notice that there are 3 different queries that are run from the original query returned from the business object, giving the front end code a ton of control of how to present the data in the UI.
Notice also the entry.Converter.ToDataReader() call for databinding. This isn't strictly necessary - you could directly bind the result of the query. However, databinding to a DataReader() is 3-4 times faster than binding to an entity list as Entity list binding requires use of Reflection for each data item. The DataConverter provides an easy way to convert to DataReader, DataTable (useful for paging, editing and still way faster binding than Entities) and List. Another advantage of the Converter is that it fires any errors into the business object so errors can be trapped more effectively.
Ok... so the CRUD code above is in connected mode. If you want to run CRUD operations in disconnected this should work:
// *** You can also work on the entities disconnectedthis.Entry.Options.TrackingMode = Westwind.BusinessFramework.TrackingModes.Disconnected;
EntryEntity entry = this.Entry.Load(10);
entry.Title = "Updated Title";this.Entry = null; // kill business object
// *** Create a new onethis.Entry = new busEntry();
this.Entry.Options.TrackingMode = Westwind.BusinessFramework.TrackingModes.Disconnected;
this.Entry.Save(entry); // update disconnected entity
To give you an idea of what this looks like in a Web page here's some code handles displaying and then saving entity data for a new time entry:
protected override void OnInit(EventArgs e)
{ base.OnInit(e);this.Proxy.TargetCallbackType = typeof(Callbacks); // wwMethodCallback Ajax Callbacks handler
if (this.Proxy.IsCallback)
return; object projectQuery = Project.GetOpenProjects(); this.lstProjects.DataSource = Project.Converter.ToDataReader(projectQuery);this.lstProjects.DataValueField = "Pk";
this.lstProjects.DataTextField = "ProjectName";
this.lstProjects.DataBind(); object customerQuery = Customer.GetCustomerList(); this.lstCustomers.DataSource = Customer.Converter.ToDataReader(customerQuery); this.lstCustomers.DataTextField = "Company";
this.lstCustomers.DataValueField = "Pk";
this.lstCustomers.DataBind();}
protected override void OnLoad(EventArgs e)
{ base.OnLoad(e);if (this.Proxy.IsCallback)
return;this.TimeTrakkerMaster.SubTitle = "Punch In New Entry";
if (this.Entry.NewEntity() == null)
{this.ErrorDisplay.ShowError("Unable to load new Entry:<br/>" + this.Entry.ErrorMessage);
return;}
if (!this.IsPostBack)
{ // *** Get the User's last settings
// we need to load user here no association yetbusUser user = TimeTrakkerFactory.GetUser();
if (user.Load(this.TimeTrakkerMaster.UserPk) != null)
{ if ( user.Entity.LastCustomer > 0 ) this.Entry.Entity.CustomerPk = user.Entity.LastCustomer; if (user.Entity.LastProject > 0) this.Entry.Entity.ProjectPk = user.Entity.LastProject;}
// *** Now bind it this.DataBinder.DataBind();}
}
protected void btnPunchIn_Click(object sender, EventArgs e)
{ // *** Start by unbinding the data from controls into Entity this.DataBinder.Unbind(); // *** Manual fixup for the split date fieldDateTime PunchinTime = Entry.GetTimeFromStringValues(this.txtDateIn.Text, this.txtTimeIn.Text);
if (PunchinTime <= App.MIN_DATE_VALUE)
{this.DataBinder.BindingErrors.Add( new Westwind.Web.Controls.BindingError("Invalid date or time value", this.txtDateIn.ClientID));
Entry.ValidationErrors.Add("Invalid date or time value", this.txtTimeIn.ClientID);
}
Entry.Entity.TimeIn = PunchinTime;
// *** Validate for binding errors - and error out if we have anyif (this.DataBinder.BindingErrors.Count > 0)
{this.ErrorDisplay.ShowError(this.DataBinder.BindingErrors.ToHtml(), "Please correct the following:");
return;}
// *** Have to make sure we associate a user with this entry Entry.Entity.UserPk = this.TimeTrakkerMaster.UserPk; // *** Validate business rulesif (!this.Entry.Validate())
{foreach (ValidationError error in this.Entry.ValidationErrors)
{ this.DataBinder.AddBindingError(error.Message,error.ControlID);}
this.ErrorDisplay.ShowError(this.DataBinder.BindingErrors.ToHtml(), "Please correct the following:");
return;}
// *** Finally save the entityif (!this.Entry.Save())
this.ErrorDisplay.ShowError("Couldn't save entry:<br/>" +
this.Entry.ErrorMessage); else {this.ErrorDisplay.ShowMessage("Entry saved.");
Response.AppendHeader("Refresh", "2;default.aspx");
// *** Remember last settings for Project and Customer for the user // *** NOTE: Entry.Entity.User is not available here because it's a NEW record // so we explicitly load and save settingsbusUser User = TimeTrakkerFactory.GetUser();
User.SaveUserPreferences(Entry.Entity.UserPk, Entry.Entity.CustomerPk, Entry.Entity.ProjectPk);
}
}
This is one of the simpler examples that deals mostly with a single business object but it should give you an idea of how things work. I mentioned the query functionality yesterday.
The business object's typical implementation will likely provide:
Query Result Methods
Methods that return data that is used in the front end. These are methods like GetOpenProjects() or GetCustomerList() that generally take input parameters and then create Queries that get returned as a result. Remember queries are not executed until enumerated so effectively no SQL access occurs until databinding happens.
Overridden CRUD operations
This is actually quite common: You'll want to set default values or perform other operations on new entities, set default values (say an Updated column) on or before saving. This can also mean speciaty methods that basically overload CRUD operations. For example in Time Trakker I have things like PunchIn and PunchOut that are essentially overloads of the Save() method that assign specific values first. For my user object I have AuthenticateAndLoad(string username, string password) which is essentially an overloaded Load() method.
Convenience Methods
Often you also have convenience methods that format data a certain way or perform special taks in batch on the entity object for example. Or you may have special operations that run updates against the database. Maybe a 'UpdateTimeTotals()' that ensures that all data in the tables are properly calculated (which incidentally would be a non-LINQ operation using just a command object).
Again to give you an idea of what this looks like here's my business object for the Entry object:
/// <summary>
/// Business object related a time entry.
/// </summary>
public class busEntry : wwBusinessObject<EntryEntity, TimeTrakkerContext>
{/// <summary>
/// Get open entries for a given user
/// </summary>
/// <param name="userPk"></param>
/// <returns></returns>
public IQueryable<EntryEntity> GetOpenEntries(int userPk)
{IQueryable<EntryEntity> q =
from e in this.Context.EntryEntities
where !e.PunchedOut orderby e.TimeIn select e; // *** Add filter for User Pk - otherwise all open entries are returned if (userPk > 0)q = q.Where(e => e.UserPk == userPk);
return q;}
/// <summary>
/// Get all open entries
/// </summary>
/// <returns></returns>
public IQueryable<EntryEntity> GetOpenEntries()
{return this.GetOpenEntries(-1);
}
/// <summary>
/// Gets a list of recent entries
/// </summary>
/// <param name="userPk"></param>
/// <param name="Count"></param>
/// <returns></returns>
public IQueryable<EntryEntity> GetEntries(int userPk)
{IQueryable<EntryEntity> q =
from e in this.Context.EntryEntities
orderby e.TimeIn descending
select e; return q;}
#region overridden CRUD operation/// <summary>
/// Sets default time value
/// </summary>
/// <returns></returns>
public override EntryEntity NewEntity()
{EntryEntity entry = base.NewEntity();
if (entry == null)
return null;
entry.TimeIn = TimeUtilities.RoundDateToMinuteInterval(DateTime.Now,
App.Configuration.MinimumMinuteInterval, RoundingDirection.RoundUp); entry.TimeOut = App.MIN_DATE_VALUE; return entry;}
/// <summary>
/// Fixes up times for Universal Time to the database
/// </summary>
/// <returns></returns>
public override bool Save()
{ //if (this.Entity.TimeIn != null) // this.Entity.TimeIn = this.Entity.TimeIn.ToUniversalTime(); //if (this.Entity.TimeOut != null) // this.Entity.TimeOut = this.Entity.TimeOut.ToUniversalTime();return base.Save();
}
/// <summary>
/// Fixes up times for local time from the database
/// </summary>
/// <param name="pk"></param>
/// <returns></returns>
public override EntryEntity Load(object pk)
{if (base.Load(pk) == null)
return null;
//if (this.Entity.TimeIn != null) // this.Entity.TimeIn = Entity.TimeIn.Value.ToLocalTime(); //if (this.Entity.TimeOut != null) // this.Entity.TimeOut = Entity.TimeOut.Value.ToLocalTime();return this.Entity;
}
/// <summary>
/// Checks for empty title and time in values and associations for user, customer and project
/// </summary>
/// <returns></returns>
public override bool Validate()
{ base.Validate();if (string.IsNullOrEmpty(this.Entity.Title))
this.ValidationErrors.Add("The title is required","txtTitle");
if (this.Entity.TimeIn <= App.MIN_DATE_VALUE)
this.ValidationErrors.Add("Time and/or date value is invalid","txtTimeIn");
if (this.Entity.CustomerPk < 1)
this.ValidationErrors.Add("A customer must be associated with this entry", "txtCustomerpk");
if (this.Entity.ProjectPk < 1)
this.ValidationErrors.Add("A project must be associated with this entry", "txtProjectPk");
if (this.Entity.UserPk < 1)
this.ValidationErrors.Add("A user must be associated with this entry", "txtUserPk");
if (ValidationErrors.Count > 0)return false;
return true;
}
/// <summary>
/// punches out an individual entry and saves it
/// </summary>
/// <returns></returns>
public bool PunchOut()
{this.Entity.PunchedOut = true;
if (this.Entity.TimeOut == null || this.Entity.TimeOut < this.Entity.TimeIn)
this.Entity.TimeOut = DateTime.Now;
return this.Save();
}
/// <summary>
/// Punches in a new entry by setting punch in time
/// </summary>
/// <returns></returns>
public bool PunchIn()
{return this.PunchIn(null);
}
/// <summary>
/// Punches in a new entry by setting punch in time
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public bool PunchIn(EntryEntity entity)
{this.Entity.PunchedOut = false;
if ( this.Entity.TimeIn <= App.MIN_DATE_VALUE )
this.Entity.TimeIn = DateTime.Now;
if (Entity == null)
return this.Save();
elsereturn this.Save(entity);
}
/// <summary>
/// Punches out an individual entry and saves it
/// </summary>
/// <param name="entry"></param>
/// <returns></returns>
public bool PunchOut(EntryEntity entry)
{ entry.PunchedOut = true;if (entry.TimeOut == null || entry.TimeOut < entry.TimeIn)
entry.TimeOut = DateTime.Now; this.CalculateItemTotals();return this.Save(entry);
}
#endregion/// <summary>
/// Utility function that converts a date time entry value from
/// a date and time string to a DateTime value
/// </summary>
/// <param name="Date"></param>
/// <param name="Time"></param>
/// <returns></returns>
public DateTime GetTimeFromStringValues(string Date, string Time)
{DateTime val = App.MIN_DATE_VALUE;
DateTime.TryParse(Date + " " + Time,out val);
return val;}
/// <summary>
/// Calculates Item and Rate totals and sets it on the passed entry object
/// </summary>
public void CalculateItemTotals(EntryEntity Entry)
{if (Entry == null)
Entry = this.Entity; if (Entry.TimeIn == null ||
Entry.TimeOut== null || Entry.TimeOut < Entry.TimeIn)
Entry.TotalHours = 0.00M;
else if ( Entry.TimeOut > App.MIN_DATE_VALUE &&
Entry.TimeIn > App.MIN_DATE_VALUE) Entry.TotalHours = (decimal)Entry.TimeOut.Subtract(Entry.TimeIn).TotalHours;Entry.ItemTotal = Entry.TotalHours * Entry.Rate;
}
/// <summary>
/// Calculates Item and Rate totals. This version works off the internal Entity object
/// </summary>
public void CalculateItemTotals()
{this.CalculateItemTotals(null);
}
/// <summary>
/// Adjusts the time values for rounding conditions
/// </summary>
public void RoundTimeValues()
{if (this.Entity.TimeIn > App.MIN_DATE_VALUE)
this.Entity.TimeIn = TimeUtilities.RoundDateToMinuteInterval(this.Entity.TimeIn,
App.Configuration.MinimumMinuteInterval, RoundingDirection.RoundUp);if (this.Entity.TimeOut > App.MIN_DATE_VALUE)
this.Entity.TimeOut = TimeUtilities.RoundDateToMinuteInterval(this.Entity.TimeOut,
App.Configuration.MinimumMinuteInterval, RoundingDirection.RoundUp);}
}
Most of the methods are pretty minimalistic - as you would expect in a simple application like this. But it should give a pretty clear view of what typically happens in the business object. The code essentially deals with the core data manipulation, pre and post processing and of course generation of queries. The code tends to break down into very clearly defined responsibilities that are easy to test against. Further given the structure of the business object you have a sort of template that you follow with most business objects. You implement Validate() and probably one more Load() related methods and possibly Save() if there's some special save syntax.
The business object uses LINQ to SQL for data access and passes out either LINQ queries (IQueryable<T> for 'adjustable' results or IQueryable for fixed non-mutable results) or returns individual entities for the CRUD methods. In simple applications like the time tracking app I'm building nearly 90% of the code deals with CRUD and convenience methods and just a few simple queries which is quite common for transactional applications.
Most operations require relatively little amounts of code because of the high level of abstraction that LINQ offers. From a pure code usability perspective LINQ to SQL is making code cleaner and resulting in quite a bit less of it. So far I haven't run into any of my own 'warning' points - mainly because I haven't gotten there yet. I know I will have issues when I get to the Web Service portion of things and already had to dodge one issue when using AJAX serialization. But other than that so far so good.
So there you have it <g>. I'd be interested to hear thoughts.
[ 2/5/2008 code updated for .NET 3.5 RTM ]
Other Posts you might also like
The Voices of Reason
# re: A simple Business Object wrapper for LINQ to SQL
IAC, provider independence is over-rated <s>. How often do you build applications that change providers. It happens but it's rare. If you're an MS shop chances are you're using SQL Server or SQL Express and certainly for the small to mid level scenario that LINQ to SQL addresses this is even more likely.
While you could use the generated entities in the model, generating your own data for them and doing the change management would all be required in somehting custom if you want to continue to use the LINQ syntax. If it comes to that you're going to be better off with some other non LINQ to SQL tool like nHibernate, LLBGen, Subsonic, WilsonOR etc.
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
OK, sorry, I had mostly read the introduction of the post, not so much the code.
I see the UI in the demo makes direct calls to Linq to SQL (The DAL), so a provider agnostic business layer would not work in this case.
Doesn't this go against the principles stated in the post?
Logical Abstraction
They abstract the object model in such a way that all code related to accessing the object model (in this case LINQ to SQL Entities) as well as any other incidental data access in one place...
# re: A simple Business Object wrapper for LINQ to SQL
1) By giving each entity its own data context, how would you insert, for example, an Entry and a User in transaction?
2) Are you seeing any connections leaking?
# re: A simple Business Object wrapper for LINQ to SQL
The alternative is that you never pass out queries out of the BO layer but only concrete instances with ToList() or whatever. But I think that would be denying one of the most compelling features of LINQ IMHO.
# re: A simple Business Object wrapper for LINQ to SQL
For clarfication - I'm not Disposing() the DataContext explicitly and I'm relying on the object going out of scope.
# re: A simple Business Object wrapper for LINQ to SQL
The idea is that a business itself is mostly self contained in its use of business logic even if it spans multiple entities which it uses internally. You can also assign the data context explicitly with one of the constructors which then results in a shared DataContext but that's something that has to be explicit.
It might be interesting to think of a way to have a more global DataContext store, but the question in this scneario always becomes where do you hold this context then? If you need to share on a page now you could create a context on the page, then explicitly use the constructor with the context for each business object loaded.
# re: A simple Business Object wrapper for LINQ to SQL
I like the idea of the concrete instances with ToList() for a provider agnostic BO as it also makes sure the Query is not executed twice. You could then query the list if needed with Linq to objects I guess.
On the validation, is there a way you can get automatic client side validation too using one the client side libraries like it is done in Monorail? This may be hard with web forms, but easier in the upcoming MVC framework.
Note on this comment box:
When I post a comment originating from a get request, the code is always rejected, so I always have to post it twice.
# re: A simple Business Object wrapper for LINQ to SQL
I guess if you did not have a DAL, it would suffice for that requirement. Still, accept for the ability to filter collections into subsets, I can't see the benefit of Linq vs. DTO and DAOs. Of, course it is newer, and thus perceived as sexier...So, what I am missing? Do I need more marketing effort from MS and Guthrie to buy into this?
</p>
BTW - been reading your articles and blog entries since 2001 and love your "say it like it is" approach. Keep up the good work!
# re: A simple Business Object wrapper for LINQ to SQL
As I mentioned previously I've been using a custom hybrid entity business object framework I built for my own long ago. It does a good chunk what the above does without LINQ to SQL. But LINQ to SQL adds:
* Much better entity management/relationship mapping
* completely wrapped DAL for CRUD (I could throw out all the DAL code)
* LINQ Queries for passing around
The first and last points are enough to make this worthwhile for me. The latter approach is saving a lot of coding I'm finding as I have an easy way to pass base queries around both the middle tier and front end.
I guess by building this sort of abstraction layer I'm trying to take some of the complexities and inconsistency out of the raw LINQ to SQL interface. You pretty much need to do that anyway with just about any data access strategy tool you use either by yourself (ie. homegrown) or using some library ontop of it, except that LINQ to SQL can stand on its own a lot easier than say using typed datasets and table adapters for example.
I think the biggest detriment AGAINST using LINQ to SQL (or EF for that matter) is the whole modelling aspect and keeping database and model in synch. The reason LINQ to SQL is interesting IMHO is because the model designer is relatively easy to work with and it's easy to manage the model even if you need to redrop tables relationships etc. But it's easy in my situation where I have full control over both code and database, but in situations where the database is under DBA control and changing frequently I can only imagine the headaches that come from trying to keep things in sync. Once you're not manually tracking what's in the database how in the heck do you sync back up short of regenerating everything?
The DevTeach discussion between Ted Newart and Ayenne comes to mind in that regard and it raises a lot of these 'mindset' issues that anybody who's looking into using any kind of OR/M solution should definitiely consider very carefully. OR/M definitely couples database and object model much more closely.
# re: A simple Business Object wrapper for LINQ to SQL
You are correct - no leaks. Sorry for the confusion - I was misunderstanding what I saw in Reflector in how the connections were managed and had a problem in my code.
# re: A simple Business Object wrapper for LINQ to SQL
With Linq, do you Know how could I post two or more queries on an only trip to server?
Thanks
# re: A simple Business Object wrapper for LINQ to SQL
You can however, use Execute Query to create string based queries to execute and you can potentially return more than one resultset with that and assign with Translate.
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
I really like your blog, thank you very much!
# re: A simple Business Object wrapper for LINQ to SQL
I try to convert it to vb and add it on my projet. I got no compiler error, but when I try to create a new Business Entity i got a error in the Initialize function of the BusinessObject. I got a error on this line:
Protected Overridable Sub Initialize() ' *** Create a default context If Me.Context Is Nothing Then If Not String.IsNullOrEmpty(Me.Options.ConnectionString) Then '***** HERRE Me.CreateContext(Me.Options.ConnectionString) Else Me.Context = Me.CreateContext() End If End If ' *** Initialize Table Info Me.TableInfo = New TableInfo(Context, GetType(TEntity)) End Sub
when I try to watch the options value I got this error:
{ " The generic type ' BusinessFramework.wwBusinessObject`2 ' was used with an incorrect number of generic arguments in the assembly ' BusinessFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null '. ": " BusinessFramework.wwBusinessObject`2 "}
any suggestion? Someone have aleready translate this framework in vb?
Thanks a lot
# re: A simple Business Object wrapper for LINQ to SQL
In you sample application, you demonstrate how to create new, update and save entity. What about collection of entity like all the project binded to a grid. Do your conver the result in datatable or dataset? How do you keep traking of witch row are added, removed or changed by the grid and then persiste change to DB?
Thanks
# re: A simple Business Object wrapper for LINQ to SQL
for example I bind to my grid like this:
RadGridSegments.DataSource = oSegment.Converter.ToDataTable(oSegment.GetSegments, "Segments")
on the grid event I gat the changed row like this:
ctype(RadGridSegments.DataSource,DataTable).GetChanges()
Do I need to do a for each row in ctype(RadGridSegments.DataSource,DataTable).GetChanges()
and load with oSegement.load(pk) each modified line change the content and submitchange ? Same thing for deleted and added row? Do you have a more efficent way to do this?
# re: A simple Business Object wrapper for LINQ to SQL
Your code is very great! I start using it in my new project. Thanks to your framework!
For fun I try you disconnect mode, all work good for new entity, but if I try to load a entity with a PK, change something and call the .Save() function I got this error:
ex = {"An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."}
If I trace the code, effectively a new datacontext are created at each request. This is normal in your code, any suggestion to remove this error? This is my simple test path:
Dim oSeg2 As New busSegment oSeg2.Options.TrackingMode = TrackingModes.Disconnected oSeg2.Options.ConflictResolutionMode = ConflictResolutionModes.WriteNonConflictChanges oSeg2.Load(1) oSeg2.Entity.Nom = "Hello" oSeg2.Validate() oSeg2.Save()
# Unresolved compile error
I have been studying the LINQ landscape for a long time and your solution to the need for a clear separation of DAL and BLL looks to be the right answer for LINQ as it stands at this time.
I grabbed the code but have a couple of unresolved errors and I wonder if I might pick your brain.
In the BusinessFramework table does not support either add or remove.
I am using VS2008 and Framework 3.5.
Perhaps there have been some late breaking changes in LINQ or perhaps I am simply overlooking something.
Thanks Rick
353 table.Add(entity);
523 table.Remove(entity);
# re: A simple Business Object wrapper for LINQ to SQL
The download code from the link however should have the latest version that works correctly with RTM...
# Unresolved compile error
I have been studying the LINQ landscape for a long time and your solution to the need for a clear separation of DAL and BLL looks to be the right answer for LINQ as it stands at this time.
I grabbed the code but have a couple of unresolved errors and I wonder if I might pick your brain.
In the BusinessFramework table does not support either add or remove.
I am using VS2008 and Framework 3.5.
Perhaps there have been some late breaking changes in LINQ or perhaps I am simply overlooking something.
Thanks Rick
353 table.Add(entity);
523 table.Remove(entity);
Hey thanks for the quick come back Rick.
The compile error is in the code from the link "Grab the code" so not sure what to make of this.
Suggestions?
Thanks
# re: A simple Business Object wrapper for LINQ to SQL
The correct link for .NET 3.5 RTM is:
http://www.west-wind.com/files/conferences/conn_LinqToSqlBusiness.zip
# re: A simple Business Object wrapper for LINQ to SQL
Your article seems to be a very solid approach to work with link. However, I'm still concerned about one thing. For you saving method to work it is required to have a timestamp field in each table in the database to figure out whether we're dealing with a new entity or not. Wouldn't it be simpler to check if the primary key is set or not on the entity? Because if an entity is new, its primary key sould be null until it's saved to the database.
I haven't had the time to try to implement it yet and I don't even know if such a thing would be easy to do or not... let me know what you think.
Thanks again for this great post.
Fab
# re: A simple Business Object wrapper for LINQ to SQL
Why not manually make your classes since they're just a standard class with iNotify etc. on it.
Then add ot hte link context for the tables.
By doing so, you can write one class that handles all of your business logic AND in the process have full business logic implimented on every single property (i.e. throw errors intelligently etc. too)
And for data binding you can impliment the iErrorProvider etc. and then you get error reporting too.
Seems like a pretty elligant solution, and if you create a base class with inheritence you get everything you need all in one place. So what if you lose the designer? It's lame anyhow.
# re: A simple Business Object wrapper for LINQ to SQL
Not saying that this can't work but you're now mixing DAL and busines layer into one.
# re: A simple Business Object wrapper for LINQ to SQL
I'm not sure why you'd want a DAL separate from the Business Objects. To me the two go hand in hand and Linq to SQL works nicely because it gives us an excellent query environment that generates intelligent database queries, AND it can also do saves and we can intercept that (hopefully) and put in special logic for those saves.
The one thing that really screws this up is that you can't define any fields in your parent class that are on every table (really dumb design decision if you ask me!)
# re: A simple Business Object wrapper for LINQ to SQL
I have use your simple business object in my new Winform application and I must say It is vary good idea to combine TEntity and TContext in business object and then write in all smart business logic and data manipulation. Class diagram is also very good, clear and understandable. I also have many issue to resolve how can I easily connect this new bussines object with my edit form which contain BindingSource, BindingNavigator, GridView and many field value editors but mostly issue are related to my "don't know how". Anyway I have some questions:
1. Is there any beter way to write some generic "Nullable check" in bussines rules, because we have allready in database model (dbml) for each collumn in table property "Nullable" set?
2. We also have define all FK references so is there some generic way to check reference integrity before delete event? btw. on MSDN I have found how to direct check one reference on detail table and then delete detail records in cascade mode but I don't wont to dump my hole database, I just wont to nicely inform user about reference problem...
3. What you suggest about best connection between Binding source and Business object? For this I mean best "synhronize mode", for example I use right now:
<code lang="c#">
public partial class frmEditUsers : Form
{
private DTUser BOUser = BulldySystemBusinessObjectFactory.GetUser();
private void frmEditUsers _Load(object sender, EventArgs e)
{
object qUser = BOUser.GetUserList();
this.DTUserBindingSource.DataSource = BOUser.Converter.ToList(qUser);
}
private void tolInsert_Click(object sender, EventArgs e)
{
DT_USER NewUser = BOUser.NewEntity();
this.DTUserBindingSource.Add(NewUser); // This code insert new Entity but BindingSource don't move to new Entity position, maybe I must manual set position?
}
private void tolDelete_Click(object sender, EventArgs e)
{
if (this.DTUserBindingSource.Position > -1)
{
if (MessageBox.Show("Are you sure to delete this record?", "Question", MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
{
DT_USER user = (DT_USER)this.DTUserBindingSource.Current;
if (!this.BOUser.Delete(user))
GetMainForm().StatusText = "Couldn't delete: " + this.BOUser.ErrorMessage;
else
{
// Record has been successfully deleted from database but not from DataBinging list!
// this.DTUserBindingSource.Remove(user); This code remove entitiy from BindingSource list but it also cause in some cases exception in BOUser.Save() -> Object reference not set to a instance of object
GetMainForm().StatusText = "Record has been successfully deleted.";
}
}
}
}
private void tolSave_Click(object sender, EventArgs e)
{
this.Validate();
this.DTUserBindingSource.EndEdit();
try
{
if (!BOUser.Save()) // This also throw exception if we just edit some value thru edit box, don't know how to save only changed records...
GetMainForm().StatusText = "Error while saving: " + this.BOUser.ErrorMessage;
}
catch (Exception ex)
{
GetMainForm().StatusText = ex.Message;
}
}
...
}
</code>.
On form I have all controls and grid view databind to DataBinding source and it have define datasource type of my Entity (DT_USER).
I must admit that I'am little confuse here, special because I come from Borland Delphi and BDE which don't have this problems but also don't have LINQ :)
Thank you very much for your time and any posible response you give me!
# re: A simple Business Object wrapper for LINQ to SQL
I also have correct some errors in code. In form Load event I have now: this.DTUserBindingSource.DataSource = qUser;
which bether works with BindingSource. I have also correct exception "Object reference not set to a instance of object", this error cause my code in override Validate() method...
Still I wish to make better delete code and of course some generic validate for foreign keys and nullable checks...
# re: A simple Business Object wrapper for LINQ to SQL
Exception:Test method ProjectTest.busCategoryTest.GetCategoriesTest threw exception: System.ApplicationException: dbo.tblCategories doesn't have a version field. Business object tables mapped require a version field.
This is coming from wwBuisnessObject.cs: public TableInfo(DataContext context, Type entityType) { if (metaTable.RowType.VersionMember == null) throw new ApplicationException(this.Tablename + " doesn't have a version field. Business object tables mapped require a version field."); } // *** Generically get the table Table<TEntity> table = context.GetTable( typeof(TEntity)) as Table<TEntity>; // *** BIG assumption here: version field and using Reflection - yuk! // can't see another way to figure out whether we're dealing with // a new entity or not. object tstamp = entity.GetType().GetProperty(this.TableInfo.VersionField).GetValue(entity, null);
# re: A simple Business Object wrapper for LINQ to SQL
ie. if you have a Customer object that is using tblCustomer table. Add a field that is a timestamp to the tblCustomer table
# re: A simple Business Object wrapper for LINQ to SQL
I tried declaring my business object by inheriting from wwBusinessObject with the entity and the datacontext generated by the object relational designer as generic parameters. However, since TContext has to be a wwDataContext and DataContext cannot be instantiated without parameters, this does not seem to work. I get the following compilation error:
"The type 'DataClassesDataContext' cannot be used as type parameter 'TContext' in the generic type or method 'Westwind.BusinessFramework.wwBusinessObject<TEntity,TContext>'. There is no implicit reference conversion from 'DataClassesDataContext' to 'Westwind.BusinessFramework.wwDataContext'."
Do I have to derive my own datacontext class from wwDataContext? I tried finding out more about this by looking at your Timetrakker site, but it seems you are using the ORD datacontext without any further modification. I only have Visual Web Developer at the moment though, so I could not compile your project to see if it works.
Any help would be greatly appreciated.
Thanks,
Adrian
# re: A simple Business Object wrapper for LINQ to SQL
The business object depends on various extension features I added to the data context, so this is not an optional step...
# re: A simple Business Object wrapper for LINQ to SQL
Adrian
# re: A simple Business Object wrapper for LINQ to SQL
First Thanks for your great articles..
I have similar question to the one Pierre asked, how do you handle collections of entities? for example collections bound to datagridview.. how do you handle inserts, updates and deletes and sending back the changes to the database?
Also, what is the preferred way of transferring collections of entities between methods in a single form or passing collections between different forms.
The second question is regarding the converter class, is there a way to convert a dataset / Datatable back to entities?
Thanks..
# re: A simple Business Object wrapper for LINQ to SQL
If you use a business object the business object holds the entities and sub entities generally. You end up passing a single object that's a container of most everything else related to your business object. But that really depends on your scenario. Either way collections are just another object that can be passed around like individual entities.
Re: converter back to DataSet - no. Why would you want to do this? Either use entities or use data sets but not both. exporting to DataTables can be useful in some binding scenarios as DTs bind much faster while still giving support for paging/sorting etc. Also works with entities but it's MUCH slower due to Reflection overhead.
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
A little question I have:
Is the DataContext base in general automaticly thread safe?
So before I used the LINQ library, I've worked with a static DAL, so there is automaticly a thread safe Data Access.
But I'm not sure, how does the DataContext managed this. So mabe I can use the singleton pattern.....
Thanks and best regards
Beni from Switzerland, (sorry for my english :-)
# re: A simple Business Object wrapper for LINQ to SQL
Linq to SQL DataContext Lifetime Management
http://www.west-wind.com/weblog/posts/246222.aspx
# re: A simple Business Object wrapper for LINQ to SQL
best regards
Beni
# re: A simple Business Object wrapper for LINQ to SQL
The last time, I've spend a lot of time, to understand all the classes etc... but I'm not be able to get the "DataContext" (for ASP.NET).
I've try to work with you're "DataContextFactory", but the problem I've go is the "ObjectDisposedException".
So the code from the "DataContextFactory.GetWebRequestScopedDataContextInternal(...)" give me not a null reference but an object how was already disposed.
How can I test, whether the "DataContext" was already disposed?
Thank!
Beni
# re: A simple Business Object wrapper for LINQ to SQL
Correct me if I'm wrong, but wouldn't your idea of storing a datacontext with each business object mean that the business objects are not serializable? I'm working on a design for a new web project and was hoping to use your method to encapsulate my business objects, but I was also hoping to be able to do many things with them that require them to be serializable (e.g. put them into ViewState/Session, pass them to Webservices, store them as dependency properties in workflows etc). All of this is impossible if the business objects themselves aren't serializable to XML.
If I'm right, do you have any suggestions as to how to use Linq to SQL in an environment that requires serializable business objects?
Thanks
Col
# Small Addition to the wwBusinessObject - LoadShallowCopy
Occasionally I need to copy objects to a new object and wanted a quick and easy way of doing it. Please feel free to point out any code that doesn't look right. This is my first post of any code back to a web page.
-----------------------------------------
public TEntity LoadShallowCopy(object primaryKey) { string sql = "select * from " + this.TableInfo.Tablename + " where " + this.TableInfo.PkField + "={0}"; this.SetError(); try { TContext context = this.Context; // *** If disconnected we'll create a new context if (this.Options.TrackingMode == TrackingModes.Disconnected) context = this.CreateContext(); IEnumerable<TEntity> EntityList = context.ExecuteQuery<TEntity>(sql, primaryKey); TEntity OriginalEntity = null; OriginalEntity = EntityList.Single(); TEntity NewEntity = this.NewEntity(); System.Collections.ObjectModel.ReadOnlyCollection<MetaDataMember> TableMembers = context.Mapping.GetTable(typeof(TEntity)).RowType.DataMembers; Type EntityType = typeof(TEntity); foreach (MetaDataMember TableMember in TableMembers) { if (TableMember.DbType != null) { System.Reflection.PropertyInfo PInfo = EntityType.GetProperty(TableMember.Name); if (PInfo != null) { PInfo.SetValue(NewEntity, PInfo.GetValue(OriginalEntity, null), null); } } } // *** Clear the primary key and the version field EntityType.GetProperty(this.TableInfo.PkField).SetValue(NewEntity, null, null); EntityType.GetProperty(this.TableInfo.VersionField).SetValue(NewEntity, null, null); // *** and return instance return NewEntity; } catch (InvalidOperationException) { // *** Handles errors where an invalid Id was passed, but SQL is valid this.SetError("Couldn't load original entity - invalid key provided."); this.Entity = this.NewEntity(); return null; } catch (Exception ex) { // *** handles Sql errors this.SetError(ex); this.Entity = this.NewEntity(); } return null; }
# re: A simple Business Object wrapper for LINQ to SQL
public DataSet ConvertTo<T>(IList<T> list) { DataTable table = CreateTable<T>(); Type entityType = typeof(T); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType); foreach (T item in list) { DataRow row = table.NewRow(); foreach (PropertyDescriptor prop in properties) { row[prop.Name] = prop.GetValue(item); } table.Rows.Add(row); } DataSet dSet = new DataSet(); dSet.Tables.Add(table); return dSet; } public DataTable CreateTable<T>() { Type entityType = typeof(T); DataTable table = new DataTable(entityType.Name); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType); foreach (PropertyDescriptor prop in properties) { if (prop.PropertyType.Name == "Nullable`1") { table.Columns.Add(prop.Name,System.Nullable.GetUnderlyingType(prop.PropertyType)); } else { table.Columns.Add(prop.Name, prop.PropertyType); } } return table; }
# re: A simple Business Object wrapper for LINQ to SQL
http://www.codeplex.com/MultiTierLinqToSql
(mk)
# re: A simple Business Object wrapper for LINQ to SQL
While implementing your business objects framework I noticed that you used VersionMember. My Qestion concerns neccesity. In the scenerio that I use, I was to use SQLServer with the default security objects that are normally in aspnetdb. Specifically, I want to embed their controls in my database which would allow me to not have create login code. I also want to use the user objects as a level in a n-way tree representing the application. I don't want to create my own user mechanism like you have in your TimeTrakker app. The aspnet tables root table is aspnet_Applications which doesn't have a timestamp. This is the primary partition for aspnet security. I want to add to this table at will. Your business are throwing this application exception. I want to know the consequences of removing this exception since I don't want to modify the base security tables.
# re: A simple Business Object wrapper for LINQ to SQL
While implementing your business objects framework and I noticed that you use the variable VersionMember. My Qestion concerns neccesity. In my scenario , I want to use SQLServer with the default security objects that are normally reside in aspnetdb. Specifically, I want to embed their controls in my database. This would give me the luxury of not having to create the login code. I also want to use the user objects as a level in a n-way tree representing the application. I don't want to create my own user mechanism like you have in your TimeTrakker app. The aspnet tables root table is aspnet_Applications which doesn't have a timestamp. This is the primary partition for aspnet security. I want to add to this table at will. Your business objects are throwing an application exception. I want to know the consequences of removing this exception since I don't want to modify the base security tables?
Note: Sorry for my previous incoherent message !!
# re: A simple Business Object wrapper for LINQ to SQL
The more current version of the BusinessObjectWrapper is available in the West Wind Web Toolkit project here:
Web Toolkit:
http://www.west-wind.com/WestwindWebToolkit/
Docs:
http://www.west-wind.com/WestwindWebToolkit/docs?page=_2mt0581bd.htm
Subversion:
http://www.west-wind.com:8080/svn/WestwindWebToolkit/trunk/Westwind.BusinessFramework/LinqToSql/
Hope this helps,
+++ Rick ---
# re: A simple Business Object wrapper for LINQ to SQL
First off thanks alot for this - it's great!
I have a quick question on the subject of the query type methods - I personally don't like to instantiate an instance of the object just to call a method on it (if it doesnt needed to be initialized) like this...
BusContact bc = new BusContact();
bc.FindContacts(contactSearchParams);
If those helper type methods were static then we could say
BusContact.FindContacts(contactSearchParams)
so BusContact could act more like a service (for those sorts of helper methods).
The method would either have to make a new DataContext or use a static one, but for these query type of methods, are there still issues with a static DataContext?
What I mean is, would it be kosher for the base class to declare a static dataContext called QueryDataContext or something, that the static query type methods could take advantage of?
Is passing something back as Iqueryable that comes from a static dataContext an issue?
Just wondering, thanks again!
# re: A simple Business Object wrapper for LINQ to SQL
Basically the bus object follows the same unit of work pattern that the DataContext on its own would follow and that ensures that connections are shut down and the context clears out between requests.
IAC, I wouldn't recommend static objects or for that matter caching the DataContext.
# re: A simple Business Object wrapper for LINQ to SQL
For:
public class wwBusinessObject<TEntity,TContext>
where TEntity: class, new()
where TContext: wwDataContext, new()
...
this.TableInfo = new TableInfo(Context, typeof(TEntity));
I Have:
Public Class BaseClass(Of TEntity As {Class, New}) ... Me.TableInfo = New TableInfo(db, TypeOf (TEntity))
however it is telling me TEntity is a type and cannot be used as an expression.
I guess
where TEntity: class, new()
creates an instance of TEntity? Do you know how I achieve this in VB?
Thanks
# re: A simple Business Object wrapper for LINQ to SQL
"@Scott - Each business object has its own data context, not each entity. If you are dealing with say an Entry entity in the example, the Entity has access to all related entities and if
necessary in the business layer code can access the context to load others. '
So does this mean that f I have a "Salesperson" business object, for example, that is exposed via a WPF application which permits multiple data entry forms to be open, then I would (or should) have one and only one data context instance shared by all of salesperson instances, since they're the same type? Or would I have a separate data context for each and every business object instance, even if they are of the same type?
I've looked at all the code on the SVN repository and am impressed and grateful for your contribution, but also a bit overwhelmed to be honest. Is there a simple "task-list" example program or something with source code that i could study to see how to actually implement this in a real world setting? Apologies if it's in there already, but I couldn't find it. Warm Regards and thank you, jbj
# re: A simple Business Object wrapper for LINQ to SQL
I was wondering about using stored procedures with your approach. Do you already do this or do you not bother with stored procedures in favor of using LinqToSql queries? If you do use stored procedures, can you explain your approach?
Thanks,
G
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
I've been reading a fair bit recently about L2S in the context of N-Tier applications. What I've found is that there some folks and M$ who would disagree with your characterization of L2S entities as merely data transfer objects. Zain Naboulsi writing on MSDN blog http://blogs.msdn.com/b/zainnab/archive/2008/08/20/dr-strangelinq-or-how-i-learned-to-stop-worrying-and-love-no-data-access-layer.aspx, says plainly that L2S entities are both DTO's and business objects. He also recommends placing BL methods directly in the DataContext. Also, the following article on MSDN, if I am interpreting the chart correctly, places validation logic and business rules inside the partial entity classes. Your thoughts?
# re: A simple Business Object wrapper for LINQ to SQL
# re: A simple Business Object wrapper for LINQ to SQL
Lets say I have a bunch of Invoices, and all invoices have a bunch of invoice-lines (details).
In order to delete an invoice i need to delete the invoice-lines before deleting the invoice.
how will this be done most efficiently with this Business Framework wrapper classes?
# re: A simple Business Object wrapper for LINQ to SQL
Thanks
Chandra
# re: A simple Business Object wrapper for LINQ to SQL
Shouldn't also the business object be data provider independent ?
The UI code should no have to change if you want to change data store, right ?
The nice thing with the entity classes generated by the OR tool is they are not provider specific.
So maybe have a wwBusinessObject<TEntity> and a Business Object service that fills it up in each provider's specific way.
I know that this is a Business Object wrapper for LINQ to SQL, but it seems it would not take much to add the option to change provider later if needed.