Linq to SQL uses a DataContext to manage it's access to the database as well as tracking changes made to entities retrieved from the database. Linq to SQL has a persistent approach to managing its 'connection' to the database via this data context and it basically assumes that you use a single DataContext to make all of your data related access. This doesn't mean that it makes persistent connections to the database, but means that the DataContext instance maintains state about active result sets, which is especially important if change tracking is on which is the default.
This is somewhat contrary to other ORM tools which tend to have a static manager to which you pass entities or queries that are then returned. In that respect most other ORMs are stateless in their data connectivity and management object where LINQ to SQL clearly takes a connected approach where everything revolves around this single DataContext instance. DataContext holds all the change management information and it makes it very difficult to transfer that context information to another DataContext. In short it's a very different approach and requires some thinking about how you create and manage the DataContext object.
BTW, the ADO.NET Entity framework too uses a similar connected approach with its ObjectContext object which also manages state persistently and requires that you keep the object around if you want to do things like track changes.
This raises some interesting questions on how to manage the lifetime of the DataContext object. There are a lot of options of how you can deal hanging on (or not) to the DataContext. Here are a few different approaches:
- Create a new Context for each atomic operation (Application level management)
- Create a global DataContext and handle all operations against this single DataContext object
- Create a thread specific DataContext
- Create a per business object DataContext
What doesn't work
The first thing to understand is if you are coming from another ORM manager you might be tempted to create a new context for each individual operation. Other ORMs typically have a static DataManager that load, save and otherwise manage entity instances. So you might create a context and load an entity and then modify it. Later you then create another instance and try to save this entity. Other ORMs typically have a method to load an entity and another to save one with a parameter for an entity to save. This approach really doesn't work with LINQ to SQL because of the change information is contained in the DataContext. LINQ to SQL really only has one way to commit updates which is by calling SubmitChanges() that takes all the changes stored on DataContext and writes them to the data store. There's no real way to abort changes either other than re-creating a new DataContext (which is another very odd design choice) and effectively abandoning the existing data context.
What this means is that if you plan on using LINQ to SQL like a traditional ORM - forget it. It just doesn't work well. You need to deal with the DataContext and a connected approach.
So let's talk about a few different approaches.
Create a new Context for each atomic operation
Basically with this approach you'd create a new DataContext for each atomic operation which would include potentially multiple queries and updates against a database. This would also have to include transactional operations such as running Transactions across multiple operations. The idea is that you perform your task or tasks that atomically belong together on a single instance of the DataContext and you then get rid of it (or don't reuse it at least) when you're done.
This approach works in many situations, but it can be problematic if you need to pass data objects or the data context itself around to other components in your application. For example, if you use business objects there's no real clean way to get a context passed between business objects.
In addition LINQ to SQL is very different in the way it deals with 'disconnected entities' - entities that have been detached. It's very difficult to do the truly disconnected entity approach that most other ORMs use where you use a central manager to create entities and then pass those entities back to the manager to update. LINQ to SQL's change manager marks objects in such a way that these objects can be reattached to another DataContext, so you have to use the same instance or use a complex approach (such as first serializing and deserializing an entity) to 'clear' the previous context and then Attach it. Attach is pretty ugly with lots of quirks that don't work so this should be reserved for only those truly disconnected (ie. Web Services etc.) situations. If at all possible you should try to stay connected to DataContext to get consistent behavior.
When I say 'atomic' here I'm talking about sticking with a connected instance of the Context and using that instance for one or more operations that are in the same application context - a single update or related set of updates that make up an operation. In Web applications this can often be the context of a single page or a single operation (like Save or Delete) in that page.
Create a global DataContext and handle all operations against this single DataContext object
It might seem very tempting to use a global DataContext that is used for all data access and indeed in some types of applications like a Desktop application that might actually work. You can have a global context that is used for all data operations, and that single global data context can then manage all data access and update task.
At first this might seem a perfect situation. You get the benefit of just one instance and you globally have access to this single DataContext. But there are problems with this too. In some situations you might need to perform multiple data operations possibly on the same data that can't be handled in the context of a single DataContext. For example, say you update multiple sets of tables - for example say Invoices and Purchase Orders and before you update either you decide that you only need to update one of the two updated sets and discard the other. DataContext's global context approach really allows only two options which is the SubmitChanges() or recrate your data context. If you have two sets of updated data and you want to only update one set there's really no way to do this. In other words if you have multiple simulataneous data operations that might not be directly linked/atomic, you don't have the individual control to just update a part of the changed data unless you manually roll back the changes by undoing them which is pretty ugly.
In addition if you have a single instance of the DataContext you also need to consider that options applied then are global. Options such ObjectTrackingEnabled or DeferredLoadingEnabled can't be set easily without potentially affecting other operations that also need that same DataContext.
So while this appraoch can work in some situations - especially query only based applications - it probably isn't a great idea, as it's just too coarse of a scope and gives too little control over the DataContext operation.
Create a Thread Specific DataContext
The 'global' approach above also couldn't work for Web applications because Web applications have multiple threads - one for each request - accessing data, often simultanouesly. But it might still be useful to a have a request specific DataContext that is tied to the active thread, or in the case of a Web request to the active ASP.NET HttpContext. Along the same lines it might also be useful to tie a DataContext to a specific thread so it can be reused on that thread.
The following is a DataContextFactory implementation that allows tieing to HttpContext.Current if available or the current thread context:
///
/// This class provides several static methods for loading DataContext objects
/// in a variety of ways. You can load the data context as normal one new instance
/// at a time, or you can choose to use one of the scoped factory methods that
/// can scope the DataContext to a WebRequest or a Thread context (in a WinForm app
/// for example).
///
/// Using scoped variants can be more efficient in some scenarios and allows passing
/// a DataContext across multiple otherwise unrelated components so that the change
/// context can be shared.
///
public class DataContextFactory
{
///
/// Creates a new Data Context for a specific DataContext type
///
/// Provided merely for compleness sake here - same as new YourDataContext()
///
///
///
public static TDataContext GetDataContext()
where TDataContext : DataContext, new()
{
return (TDataContext)Activator.CreateInstance();
}
///
/// Creates a new Data Context for a specific DataContext type with a connection string
///
/// Provided merely for compleness sake here - same as new YourDataContext()
///
///
///
///
public static TDataContext GetDataContext(string connectionString)
where TDataContext : DataContext, new()
{
Type t = typeof(TDataContext);
return (TDataContext) Activator.CreateInstance(t,connectionString) ;
}
///
/// Creates a ASP.NET Context scoped instance of a DataContext. This static
/// method creates a single instance and reuses it whenever this method is
/// called.
///
/// This version creates an internal request specific key shared key that is
/// shared by each caller of this method from the current Web request.
///
public static TDataContext GetWebRequestScopedDataContext()
where TDataContext : DataContext, new()
{
// *** Create a request specific unique key
return (TDataContext)GetWebRequestScopedDataContextInternal(typeof(TDataContext), null, null);
}
///
/// Creates a ASP.NET Context scoped instance of a DataContext. This static
/// method creates a single instance and reuses it whenever this method is
/// called.
///
/// This version lets you specify a specific key so you can create multiple 'shared'
/// DataContexts explicitly.
///
///
///
///
public static TDataContext GetWebRequestScopedDataContext(string key)
where TDataContext : DataContext, new()
{
return (TDataContext) GetWebRequestScopedDataContextInternal(typeof(TDataContext),key, null);
}
///
///
///
///
///
///
public static TDataContext GetWebRequestScopedDataContext(string key, string connectionString)
where TDataContext : DataContext, new()
{
return (TDataContext) GetWebRequestScopedDataContextInternal(typeof(TDataContext), key, connectionString);
}
///
/// Internal method that handles creating a context that is scoped to the HttpContext Items collection
/// by creating and holding the DataContext there.
///
///
///
///
///
static object GetWebRequestScopedDataContextInternal(Type type, string key, string connectionString)
{
object context;
if (HttpContext.Current == null)
{
if (connectionString == null)
context = Activator.CreateInstance(type);
else
context = Activator.CreateInstance(type, connectionString);
return context;
}
// *** Create a unique Key for the Web Request/Context
if (key == null)
key = "__WRSCDC_" + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString();
context = HttpContext.Current.Items[key];
if (context == null)
{
if (connectionString == null)
context = Activator.CreateInstance(type);
else
context = Activator.CreateInstance(type, connectionString);
if (context != null)
HttpContext.Current.Items[key] = context;
}
return context;
}
///
/// Creates a Thread Scoped DataContext object that can be reused.
/// The DataContext is stored in Thread local storage.
///
///
///
///
public static TDataContext GetThreadScopedDataContext()
where TDataContext : DataContext, new()
{
return (TDataContext)GetThreadScopedDataContextInternal(typeof(TDataContext), null, null);
}
///
/// Creates a Thread Scoped DataContext object that can be reused.
/// The DataContext is stored in Thread local storage.
///
///
///
///
public static TDataContext GetThreadScopedDataContext(string key)
where TDataContext : DataContext, new()
{
return (TDataContext)GetThreadScopedDataContextInternal(typeof(TDataContext), key, null);
}
///
/// Creates a Thread Scoped DataContext object that can be reused.
/// The DataContext is stored in Thread local storage.
///
///
///
///
static object GetThreadScopedDataContextInternal(Type type, string key, string ConnectionString)
{
if (key == null)
key = "__WRSCDC_" + Thread.CurrentContext.ContextID.ToString();
LocalDataStoreSlot threadData = Thread.GetNamedDataSlot(key);
object context = null;
if (threadData != null)
context = Thread.GetData(threadData);
if (context == null)
{
if (ConnectionString == null)
context = Activator.CreateInstance(type);
else
context = Activator.CreateInstance(type,ConnectionString);
if (context != null)
{
if (threadData == null)
threadData = Thread.AllocateNamedDataSlot(key);
Thread.SetData(threadData, context);
}
}
return context;
}
///
/// Returns either Web Request scoped DataContext or a Thread scoped
/// request object if not running a Web request (ie. HttpContext.Current)
/// is not available.
///
///
///
///
///
public static TDataContext GetScopedDataContext(string key, string connectionString)
{
if (HttpContext.Current != null)
return (TDataContext) GetWebRequestScopedDataContextInternal(typeof(TDataContext), key, connectionString);
return (TDataContext)GetThreadScopedDataContextInternal(typeof(TDataContext), key, connectionString);
}
///
/// Returns either Web Request scoped DataContext or a Thread scoped
/// request object if not running a Web request (ie. HttpContext.Current)
/// is not available.
///
///
///
///
public static TDataContext GetScopedDataContext(string key)
{
if (HttpContext.Current != null)
return (TDataContext)GetWebRequestScopedDataContextInternal(typeof(TDataContext), key, null);
return (TDataContext)GetThreadScopedDataContextInternal(typeof(TDataContext), key, null);
}
///
/// Returns either Web Request scoped DataContext or a Thread scoped
/// request object if not running a Web request (ie. HttpContext.Current)
/// is not available.
///
///
///
///
public static TDataContext GetScopedDataContext()
{
if (HttpContext.Current != null)
return (TDataContext)GetWebRequestScopedDataContextInternal(typeof(TDataContext), null, null);
return (TDataContext)GetThreadScopedDataContextInternal(typeof(TDataContext), null, null);
}
}
This approach might be a little more palatable than the global approach in the last bullet, especially for Web applications in that single page requests in a Web app typically stay within a specific range of related operations that can often be accomplished within a single data context. Using this approach lets you create a single DataContext that can be easily shared for a single page request.
The class above basically lets you create data contexts in HttpContext's Item collection or on a ThreadContext bag. These objects are then generically accessible by name from anywhere that has access to the static DataContextFactory.
In essence this is a variation of the global scheme but one that is a bit more generic and even easy enough to hook up so that you can use several DataContexts side by side easily.
Because this is tied to a static factory object it also makes it much easier to share these instances between components. You can access these shared objects both from the application layer as well as from a business layer without having to track an instance in your own code.
Create a per Business Object instance of DataContext
Another semi-persistent approach is to use a DataContext object that's hung off a business object. In this way you have a business object and a related DataContext that ties operations of that business object to a specific DataContext. Presumably your business object's operations would be fairly atomic in nature and so you effectively can wrapper the business object around the DataContext. If for some reason you need two instances that perform tasks side by side with different DataContext options you could simply fire up another business object instance and configure that independently.
The idea is that each business object manages its own DataContext instance since a business object is already a typical unit of related operations. So a typical method in a business object then has direct access to the data context:
///
/// Gets a list of recent entries
///
///
///
///
public IQueryable<EntryEntity> GetEntries(int userPk)
{
IQueryable<EntryEntity> q =
from e in this.Context.EntryEntities
orderby e.TimeIn descending
select e;
return q;
}
The other advantage of this approach is that the DataContext is effectively wrapped up inside of the business object. In this way the business object controls DataContext instantiation and destruction, and more importantly you can pass the data context to other components in the system simply by passing the business object.
For clarification a business object in this scenario would be an object that performs operations against the entity model and LINQ to SQL. LINQ to SQL provides the entity/data access layer while the business object provides the application logical layer. In addition a business object would also provide more high level features like Load/Delete/Save/NewEntity/Validate etc. functionality that doesn't require actual access to the LINQ model directly - the business objects would handle all the data manipulation.
I've been using this approach as part of my Business Object wrapper around LINQ to SQL for some time and it works very well. You do potentially end up with multiple DataContext instances (one for each business object) but the number is typically small (ie. 1 to 3 at a time typically). Overhead for creating a DataContext is not nill, but in my experience it's not enough to worry with small numbers of instances created per request.
The real advantage for me is the fact that I don't have to worry about the data context lifetime. I create my business objects on the application level, call a method and it internally manages creating the instance making the calls, managing change state and updates and then I can simply get rid of the object and be done. No worries about lifetime beyond the business object lifetime. In addition the business object provides an easier way yet to consistently handle basic CRUD operations without having to write LINQ syntax.
This approach can also be combined with the Thread specific approach in the last bullet. Rather than explicitly creating a data context you could decide if your object could be shared amongst many operations and in that case could be loaded from the thread context rather than creating a new instance each time.
DataContext and Threading
One thing I was wondering about today was how DataContext would work if you access a DataContext across threads - for example for an asynchronous operation in an ASP.NET. It turns out that this actually works fine (although I don't think it's thread safe for simultaneous access from multiple threads simultaneously.
The reason I was worried about this is that a few months back I'd been looking at Visual WebGui and it didn't work well in its semi-stateful mode of persisting a DataContext across requests. Basically what happened with VIsual WebGui is that loading the page and then later accessing the same DataContext would break apparently of the way that the threading and reconnection of the state for callbacks works.
To test I ran a kind of a silly test in an ASP.NET page to simulate a disconnected operation. It's totally faked up with a short wait operation to let the thread operation complete. Obviously this is not a practical approach for anything async - using AsycnTasks is a much better choice for any disconnected operations.
public partial class ThreadContext : System.Web.UI.Page
{
TimeTrakkerContext Context = new TimeTrakkerContext();
IQueryable<CustomerEntity> CustomerList;
CustomerEntity Customer;
protected void Page_Load(object sender, EventArgs e)
{
CustomerList =
from c in Context.CustomerEntities
where c.Company.StartsWith("West")
select c;
this.Customer = CustomerList.First();
Response.Write("Original Values:
" + this.Customer.Company + " " + this.Customer.Address + " " + Thread.CurrentThread.ManagedThreadId.ToString());
this.Customer.Company = "West Wind Technologies " + DateTime.Now.ToLongTimeString();
Thread thread = new Thread(new ThreadStart(this.AccessDataContextOnThread));
thread.Start();
Thread.Sleep(2000);
}
public void AccessDataContextOnThread()
{
this.Customer.Address = "33 Kaiea Place " + DateTime.Now.ToLongTimeString();
this.Context.SubmitChanges();
Response.Write("
From Thread:
" + this.Customer.Company + " " + this.Customer.Address + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
However it does demonstrate clearly that you can in fact access the DataContext across separate threads. For asynchronous scenarios in ASP.NET this should work well enough so you can do offloading with AsyncTasks or off to separate threads to continue processing after a request has completed or asynchronously while ASP.NET frees up an HttpApplication thread.
Even so DataContext works across threads, using it with multiple threads simultaneously surely would be disastrous, and wouldn't make much sense. One user updating while another is still editing would result in very undesirable operations indeed ... in any case DataContext is not thread safe anyway and all you'd accomplish is a hard crash eventually so avoid simultaneous multi-thread access.
In the end the key for DataContext change management is to ensure that the DataContext is always accessible and doesn't need to be recreated. As long as you can find some way to hang on to a single data context it can be used and be expected to correctly deal with change management.
Unfortunately as I pointed here and here , once you disconnect from the DataContext, the story with LINQ to SQL reconnecting and synching with another DataContext is bleak at best. So the best strategy is to keep hanging on to to the DataContext if you at all can
For anything else it'll be better to implement something like one of the strategies above to hang on to your DataContext.
by Damien Guard February 05, 2008 @ 4:36 am
The problem stems from the fact Microsoft have effectively merged two design patterns (Unit of work and Data gateway) into a single object - DataContext.
I believe they want us to consider one DataContext per unit of work hence why the object creation cost was dramatically reduced between beta 2 and RTM (it used to recreate the entire metadata mapping layer).
The real problem with sharing them between threads/users is calling SubmitChanges on one thread might attempt to commit new objects and associations that aren't yet complete on the other.
[)amien
by Rick Strahl February 05, 2008 @ 4:54 am
I briefly looked at overhead for object creation in the RC and the overhead was nearly negliable. I suspect it's fine in most situations to create a DataContext when needed for each unit of...
by mike February 05, 2008 @ 5:13 am
In ASP.NET the request that loads an entity for editing and the postback containing the new values from the user are completely separate, no way around that. How come someone like Scott Guthrie tells us to use the Attach() method, when in reality this does not work (without timestamp fields or tediously setting all fields to not check for concurrency)? Does he even know, or are all his demos using the LinqDataSource? MS will always say that it is by desing, but imho then the design has a bug.
You are one of the few that are explaining this in depth, thanks for your effort! It helps me understand (with some effort) the nature of Linq to SQL much better than the ASP.NET website or the drag and drop stuff Scott Guthrie makes us believe is sooo wonderful.
by Richard Deeming February 05, 2008 @ 5:26 am
1. If you're not passing a connection string to the constructor, and you have specified the "new()" constraint, you don't need to call Activator.CreateInstance - just create an instance:
2. If your method doesn't use the "new TDataContext()" syntax, you don't need the "new()" constraint.
3. When building the key for the request / thread scoped context, you don't need to use the hash-code or ID of the current context. Apart from the fact that you're limiting your solution to a single data context per app-domain, you're creating a new data slot for every thread, when a single data slot will suffice. Try using a simple key instead:
4. For performance reasons, it might be a good idea to offer overloads which accept a Func
5. The GetWebRequestScopedDataContext methods look like good candidates for extension methods:
by Rick Strahl February 05, 2008 @ 5:29 am
The main point I'm trying to make is that LINQ to SQL works well when you keep to a connected DataContext instance. I suspect you are trying to do exactly what I recommend not to do here
But that's part of the point as well - there are definitely some scenarios where it would just be a lot easier to keep an entity around and then reconnect to the DataContext but that's just a scenario that's very badly implemented with LINQ to SQL and looks like it will still be awkard in the Entity Framework as well.
by Rick Strahl February 05, 2008 @ 5:34 am
Hmmm... extending HttpContext? Not sure if I like that idea. I've been kinda weary of extension methods especially when extending framework types and bloating these types.
by mike February 05, 2008 @ 5:36 am
Don't worry, I wouldn't just copy-paste your code, but it helps to understand so I don't make a mistake that seems too easy to make with Linq to SQL.
by Rick Strahl February 05, 2008 @ 5:46 am
Now this works only if your service layer is in fact local. If it is remote (Web Service, Remoting or some other 'external' service) then the above won't work. However, in that case Attach should work with the right flags. If this is what you're using for your business layer though I'd argue Linq to SQL is a bad choice precisely because of the lack of disconnected functionality.
by mike February 05, 2008 @ 6:27 am
// Simplified
public class WidgetManager {
private static NWContext context = new NWContext();
public static GetWidgetById(int id) {
return context.SingleOrDefault(w => w.Id == id);
}
public static SaveWidget(Widget widget) {
// This is not right?
// Does the static context make this more difficult?
context.Widgets.Attach(widget);
context.SubmitChanges();
/* What are some options?
Option A: Get the original widget now, from the database.
It could be different than the widget that was requested by the page_load, user was thinking about update, coworker updated same widget in the mean time.
Option B: Get the original widget from the caller as a second parameter
Now the page needs to create a second widget (should it care that Linq to SQL makes that necessary?)
It might keep that original widget from the page_load in session or viewstate, why is that frowned upon?
*/
}
}
Either way, I need to study this more, I find it confusing :(
by Matt Brooks February 05, 2008 @ 8:39 am
Could you explain to me why you're having problems re-attaching entities to a data context? I've recently used LINQ-to-SQL on a simple ASP.NET E-Commerce solution and found attaching entities to work just fine.
A high level view of the operation:
1. First page load: load entity by PK/ID and data bind. Store ID and VERSION field (e.g. in view state).
2. On post-back (e.g. save): create/instantiate new entity; set PK/ID and VERSION field on entity from the stored values in view state; call context.EntityName.Attach(entity) to associate this new entity with the data context.
3. Update the entity fields/data with values posted back to server.
4. Call context.SubmitChanges();
Here you can see I'm working with the disconnected approach and I haven't tried to hold on to any instances.
I would be interested to hear your thoughts.
by Roger Jennings February 05, 2008 @ 8:51 am
"Any .NET developer who has adopted or is considering implementing either LINQ to SQL or Entity Framework should read this well-researched post (and its comments) carefully."
--rj
by Mike Thomas February 05, 2008 @ 9:12 am
by Nick February 05, 2008 @ 9:19 am
Essentially, this leaves you unable to optimize complex queries across relationships unless you know exactly what you're going to be loading before you ever use the DataContext.
by Jon Sagara February 05, 2008 @ 9:24 am
After banging my head against the wall a couple of weekends ago, I finally settled on an approach similar to your "Business Objects" scheme. Glad to see someone else is doing it that way, too.
Thanks for the insights.
- Jon
by Donnie Hale February 05, 2008 @ 9:41 am
I was the one who pointed out the new() constraint on a previous article of yours.
Re. Richard's fifth point on extension methods - His code checks whether the "this" parameter (context) is null and has some specific behavior if it is. But I wasn't under the impression that you could call an extension method on a null reference. For example, would this work:
((HttpContext) null).GetWebRequestScopedDataContext
Donnie
by Egil Hansen February 05, 2008 @ 11:22 am
An interesting post once more.
As far as I can tell, Microsoft intents the DataContext to only be used for single or related atomic operations, thus giving it a short lifetime (I have seen that stated by a few MS employees on the MSDN LINQ forums). This is supported by the fact that the DataContext class implements IDisposable, which indicates that it uses resources that should be freed as soon as possible (in this case, a database connection).
In regards to the Attach confusion, I highly recommend reading Data Retrieval and CUD Operations in N-Tier Applications (LINQ to SQL) from MSDN. That explains how to probably update an existing entity, with or without a TimeStamp field, and provide example code for each possible combination.
Basically, there are three options (I am copy/pasting here):
* Optimistic concurrency based on timestamps or RowVersion numbers.
* Optimistic concurrency based on original values of a subset of entity properties.
* Optimistic concurrency based on the complete original and modified entities.
After reading the above mentioned MSDN article, I have not had any issues with attaching disconnected entities (only been working in a asp.net environment though).
I also use the business wrapper approach in a current project, inspired by your previous article. I am curious though, have you made any changes to your original wwBusinessObject? I ended up building my own business object wrapper (did not like the use of reflection and wanted it to implement IDisposable), but it would be interesting to see how your wwBusinessObject has evolved.
Best regards, Egil.
by Egil Hansen February 05, 2008 @ 11:24 am
Data Retrieval and CUD Operations in N-Tier Applications (LINQ to SQL): http://msdn2.microsoft.com/en-us/library/bb546187.aspx
by Guru Stop February 05, 2008 @ 12:21 pm
by Tom Brune February 05, 2008 @ 1:10 pm
Some things I'd like to add to this discussion.
1. DataContexts are Disposable
-------------------------------
Since we don't really know what connection usage is under the hood of the data context, the context should always be disposed after use.
using (DataContext context = new DataContext())
{
...
}
Note that any streaming type data access (DataReader) will not work after the dispose because the connection will be closed. Moving items into a list will get around this problem.
2. Loading and Saving from Different Contexts
---------------------------------------------
The following is definitely not the most efficient way to do things, but does get around the cross context problem. The following code addresses a scenario of loading an entity from contextA, editing it, and saving it to contextB.
1.1 Create contextA
1.2 Load the entity from contextA
1.3 Close context A (see dispose above)
1.4 Edt the entity somehow (change a property value)
1.5 Create contextB
1.6 Load the item from contextB
1.7 Using reflection copy the properties from the entity from contextA to the entity in contextB
1.8 Save to context B
The extra load from context B is overhead, but it does allow objects to be cross context and therefore "disconnected". Here's some support methods to accomplish this.
public static void Save(TEntityType aEntity)
{
using (DataContext context = GetNewDataContextThatNeedsToBeDisposed())
{
if (aEntity.Id == 0)
context.GetTable
else
MoveItemIntoCurrentContext(context, aEntity);
context.SubmitChanges();
}
}
private static void CopyProperties(TEntityType aFrom, TEntityType aTo)
{
// Copy each property to new object in another context.
// LINQ cannot save an object created in one data context
// to another data context
foreach (PropertyInfo property in aFrom.GetType().GetProperties())
if (property.CanWrite)
property.SetValue(aTo, property.GetValue(aFrom, null), null);
}
Though LINQ is flexible, I think placing it anywhere other than a business object seems like a bad idea. Creating manager classes with static methods and placing LINQ code inside those classes encapulates the data access logic where it should be.
I'd be interested to hear opinions on this.
by Jenkis February 05, 2008 @ 3:17 pm
I suppose you could add a manual check of the timestamp or version field(s), and throw your own exception?
by Tom Brune February 05, 2008 @ 3:46 pm
by jdn February 05, 2008 @ 5:46 pm
jdn
by Steve from Pleasant Hill February 05, 2008 @ 7:14 pm
But, I went looking for some basis for this effort, and found this:
In an interview with Redmond Developer News, Hejlsberg made the case that LINQ will ultimately bind data querying into programming much as it was done decades ago with programs such as dBase and FoxPro. "It's my hope that in five to 10 years, programming languages simply will have queries as a concept built in, because that's just a must," Hejlsberg says.
I guess one day VB/C#/SQL Server will just be "one". However, constructing difficult queries that are efficient on large sets of data is not always a no-brainer.
by Rick Strahl February 06, 2008 @ 12:26 am
I understand that we should Dispose() but in my experience so far, releasing the reference is enough to close connections. I've done some load testing a while back and I never saw more than the 5 connections set up in the connection pool and no lock ups due to unavailable connections which would surely occur under high load if there was any problem with hanging connections. I also didn't see any memory increases once the load stabilized.
Keep in mind too that DataContext and Connections aren't tied 1 to 1. Connections will only be open while you iterate over data (or if you explicitly use the Connection provided with a Command or streaming to a DataReader()). Once you reach the end of a list connections close automatically.
Re: Reflection - I played aroudn with this a bit, but this gets really nasty once you start looking at related entities and related collections. The problem is you have to walk the whole tree and child objects and prevent circular references etc. It's doable and possibly generic to do this but it's going to take some serious tweaking to make this fully generic so it's reusable. I think it's actually easier to use DataContractSerializer and serialize/deserialize and then .Attach() again and it may actualy be more efficient at that since you don't have two instances you need to walk through with Reflection. You also have to then figure out how to identify new records and deal with those separately etc. etc. I'm sure it can be done, but you know this is what a data platform is supposed to do FOR YOU. The whole point of a high level tool like LINQ to SQL is to abstract the data and update logistics so that you don't have to think about it, yet with LINQ to SQL you have to build an entire infrastructure on top of the base model just to get what should be basic behavior (for disconnected) that almost every application will need at some point.
by Rick Strahl February 06, 2008 @ 12:33 am
But as I've said in many posts before even knowing some of hte limitations the technology is very, very tempting because it reduces the amount of application so significantly. For me at least LINQ and then ability to further manipulate returned data is a huge time saver that solves an age old problem I've never really had a good solution for previously (ie. passing queries back and letting the front end decide to add a final filter, orders and the actual output format). The CRUD functionality too is cool although admittedly you can get that from just about any other framework. The compelling thing about LINQ to SQL is that it's easy to get from data to object with minimal fuss, plus LINQ to query the data.
It'll be interesting to see what other vendors and ORM frameworks will do with LINQ. Ultimately I think other frameworks offer more solid solutions than anything Microsoft ships in the box (nHibernate, Frans' llbgen etc.), but right now there's no LINQ support. I suspect that will change in the future (Frans' been blogging about implementing LINQ support for example). It'll be a standard thing in the future for .NET frameworks and that's a good thing.
But it'll also mean that this is maybe the end of the home built framework because building a custom LINQ Provider (at least correctly) is no picnic.
by Richard Deeming February 06, 2008 @ 4:52 am
For example:
will be translated to:
However, in practice it would probably be better to always throw an exception if the first parameter is null, otherwise there would be no easy way to determine whether a method invocation would fail on a null reference.
by teedyay February 06, 2008 @ 8:20 am
We were just putting our LINQ framework/ way-of-working together when this came up.
We do only web-based stuff and had decided it would be generally best (in terms of ease-of-use) to have a DataContext per page, with the option to create more ad hoc for unusual fringe cases.
We like your idea of associating it with the thread (option 3) - that seems a nicer way.
I'm really not convinced by option 4: it seems to be hanging on to the old style of ORM. We think it's worth making the leap to the LINQ model, rather than hacking the new system to make it look like the old one.
by LINQ Recipes February 06, 2008 @ 8:03 pm
by jdn February 06, 2008 @ 10:11 pm
http://www.blogcoward.com/archive/2008/02/07/398.aspx
jdn
by David Fauber February 10, 2008 @ 11:26 am
by Dan Lynn February 11, 2008 @ 9:07 am
by James February 12, 2008 @ 8:54 am
I wanted to point out that i used a similar approach to the Request option you provided although it looks more clean. In the current application I'm working on each asp.net page is inherited from a base page class which is in turn inherited from the asp.net page class, so we added a protected property which exposes our DataContext and the property creates the DataContext on the first request. In the Page_Unload we call the dispose method to clean up the DataContext.
This is what the base class looks like:
And this is how we use it:
by LJ February 13, 2008 @ 11:46 am
So, if you are just updating your object AND you are using RowVersion or a binary timestamp you can add the optional parameter
Also, from the same article it says
On the middle tier, you then must call the appropriate Attach method and then InsertOnSubmit, DeleteAllOnSubmit, or InsertOnSubmit()()() (without Attach, for insertions) for each entity before you call SubmitChanges. Do not retrieve data from the database as a way to obtain original values before you try updates.so I would avoid creating a new context and loading it to get the original values to compare against.
If you have nested objects, like children or chlidren with children, I believe you need to attach each of them to the data context as well, AND indicate what to do with it (delete, update, insert), before calling SubmitChanges() on the data context.
Hope that helps someone!
by Tim February 19, 2008 @ 2:59 pm
I looking to migrate some of my nHibernate apps over to linq for the great in line syntax, better than hsql strings.
LINQ to SQL has a lot of similarities to nHibernate. Unfortunately in my opinion it will result in the killing off of nHibernate. All that is required is a wide range of linq providers.
by leppie February 27, 2008 @ 6:01 am
by Benjamin Eidelman February 27, 2008 @ 11:51 am
I've following your posts about Linq to SQL, I've seen that you also had the "Attach only if deserialized" problem.
Further testing lead me to discover that an entity loaded from DataContext with Deferred Loading, and Object Tracking disabled, is completely dettached.
If we have this, of course we could use what it's called Linq to SQL "POCO Support", this means that we are not forced to use the auto-generated classes with its EntitySets and EntityRefs, we can use just any plain class with the appropiate attribute decoration.
This allows to create your own disconnected change tracking (there's no Microsoft solution to this problem in current versions, either in the upcoming Entity Framework)
We developed a Code Generator Custom Tool replacement for the default "MSLinqToSQLGenerator", allowing us to take advantage of O/R Designer UI, but customizing the code generation.
We uploaded this tool to CodePlex as GPL: http://www.codeplex.com/ULinqGen, as it could be good jump-start to anyone wishing to use Linq to SQL in an N-Tier application.
We're planning to add some disconnected change tracking features in future releases, but keeping the capability to use your own change tracking implementation. Of course it'll be nice to have you opinion on this.
Regards,
by JamesLv March 10, 2008 @ 7:47 pm
"Setting load options is not allowed after results have been returned from a query."
by Rune Gulbrandsen March 14, 2008 @ 8:28 am
After having a long look at the LINQ to SQL trying to figure out how to use it in a "normal" DAL-scenario, I thought that maybe my approach was wrong.
I guess the MS crew has been looking a lot at what people like Martin Fowler has been talking about as the next abstraction level in programming language, which is exactly the concept of context. Today we create a lot of objects and every time we use them, we need to create a runtime for how we are relating them to each other. This is something we do over and over for each scenario we use the objects. We construct our "object context" over and over. The next logical step is to deliver class frameworks with complete object contexts, so that the end-user just creates a new domain context for the classes and the context is reusable in a lot of applications without any hazzle. You just tweak the context a bit to fit your requirements, and you are ready to go.
Maybe one way to go would be to implement the same context thought to the business layer, and create a business context for all the BOs, which also had a reference to the data context. The data context would then live and die with the business context. Then you could manipulate all the objects in the object context, and when you confirmed the changes in the object context, then the related data context changes would be updated.
It just seems to me to be a better approach to the whole data context concept. Just looked at LINQ to SQL for 3-4 weeks, so I'm no expert.
by .Net Unplugged March 22, 2008 @ 7:22 pm
by Mohammad Azam March 31, 2008 @ 2:00 pm
Thanks for the awesome post! I got one question how would you handle the scenario when your business objects contains both the instance methods and the static methods. Take a look at the following code:
Since, the DataContext is attached to the entity I can access it in the instance methods but then how should I access them in the static methods.
public partial class tblDepartment : BusinessBase
{
public bool Save()
{
if (this.DepartmentID <= 0)
{
DataContext.tblDepartments.InsertOnSubmit(this);
DataContext.SubmitChanges();
if (this.DepartmentID > 0) return true;
else return false;
}
return false;
}
public static tblDepartment GetById(int id)
{
return null;
}
}
by Rick Strahl March 31, 2008 @ 2:59 pm
Static methods should be - well, static and self contained. usually statics are for utility functionality (things like conversions or manipulation of values) so I'm not sure where using a static method would ever make sense for a business operation.
If you really need static methods then you have to instantiate the business object (or the context only) on its own to reestablish context or using a Factory Pattern.
by Mohammad Azam March 31, 2008 @ 5:55 pm
Thanks for the fast reply. Actually, I am not using Repositories or Managers for my DAL. LINQ to SQL already creates a DAL for us so I don't think I want to provide any extra layer. Here is my DAL class object.
public partial class tblDepartment : BusinessBase
{
public bool Save()
{
using(VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
{
if (this.DepartmentID <= 0)
{
dc.tblDepartments.InsertOnSubmit(this);
dc.SubmitChanges();
if (this.DepartmentID > 0) return true;
else return false;
}
return false;
}
}
public static tblDepartment GetById(int id)
{
VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext();
return dc.tblDepartments.Where(d => d.DepartmentID == id).SingleOrDefault();
}
}
Now, to perform action on a particular instance I can do the following:
tblDepartment department = new tblDepartment();
// assign properties;
department.Save();
If I want to use a method that should be available to all the objects then I would use the static methods like the following:
tblDepartment.GetById(23); // returns the tblDepartment object
For some methods I am disposing the data context right after I am done with it. But with methods like GetById I need to access the childs of the tblDepartment object so I cannot dispose. Off course I can use the DataLoadOptions but then I don't prefer that in my case.
Thanks for you reply Rick!
by Konstantin April 03, 2008 @ 2:46 am
You are saying that DataContext of LINQ to SQL is very different from other ORM's. But is it so different from HNibernate's Session? As I understand the approach is very similar, isn't is so?
by Roger April 25, 2008 @ 6:39 am
I have experimentet with attach/detach of entities, but just seem to be running into problems all the time. Even if objects seems to be detached, I get an exception when relating entities.
I use a somewhat similar solution as yours. I'm developing a ASP.NET application for intranet use and use the asp.net Cache object instead, caching with a key made up from the users identity. This way I make sure there is only one user on one ObjectContext and I don't have to recreate the ObjectContext and reload entities on each postback. The context object(or manager class that contains the context) is removed from the cache if it is idle for 10 minutes, using a callback mehod from the cache object. The Manager class(with the context) is accessible from a base class implemented by my user controls.
Maybe you have some comments about this approach? Some hidden traps?
by Simone Belia May 04, 2008 @ 5:36 am
by Mike Lockyer May 11, 2008 @ 12:11 pm
Been reading all your articles on LINQ for SQL, DataContext issues and Tiers and really don't like to have a TimeTrackerContext obect in the code behind page but can't see any better option at the moment
My intention was to start teaching this sort of stuff to undergraduates in September (using ASP.NET) but can't find an easy to understand solution.
Any advice would be most appreciated.
I had built my own solution but just came across a weird behaviour when I had 2 datacontexts.
The first was created and used to get a list of films
then I used another DataContext to update a film
and then used the first DataContext to retrieve the films again - the query was executed but the list of films remained unchanged - odd !!
Once I realised what had happened I expected the SELECT statement NOT to be called a second time as it was presumable caching the results but it does call the SELECT but not use the results - is that weird or just my lack of understanding ?
I have a short code example if anyone wants to see it
Thanks for great articles
Best wishes
Mike
by Rick Strahl May 11, 2008 @ 12:24 pm
You can find more info and the code for this approach here:
http://www.west-wind.com/WebLog/posts/160237.aspx
by Igor May 14, 2008 @ 11:58 pm
There is one problem and i could not find a good solution for this.
The problem appears when the transaction of SubmitChanges (or explicit transaction) is rolled back and you expected it and you would like to reuse the DataContext again.
I didn't find any way how to reuse this DataContext (which last operation was rolled back).
Even when the transaction is rolled back - the pending changes of DataContext are actually not rolled back.
And when you try to reuse the DataContext and do any next operations with it and then try to SubmitChanges - the previous changes (that were actually rolled back) are submitted to database again.
Now i don't see any better way than do always something like this:
try
{
op1...
op2...
dbContext.SubmitChanges();
}
catch
{
dbContext = new MyDataContext(....);
}
but this actually makes me think about "Create a new Context for each atomic operation " approach. that would relieve me from such problems.
any thoughts?
or probably there is some solution?
thanks!
by RANJI May 22, 2008 @ 2:27 pm
I have an unrealted question regarding DataContext. Like you suggested, my team wraps the datacontexts in appropriate business objects but how do you deal with linq to sql classes within your datacontexts when they have to participate in more than one datacontext.
for instance, my user table participates in the SecurityDataContext as well as PurchaseOrderDataContext. So now, I am forced to have 2 different classes for a given database table(user)/ or linq to sql class(user). And then ofcourse, my User business object class.
Where am I going wrong?
by Rajinder July 17, 2008 @ 9:13 pm
Datareader is already open.. then my website goes down for you say 4-5 minutes.. It works fine again..
Is this due to connection pooling?
Plese suggest me..
Im developing a website like www.telebid.com.
What approch should i use in that?
I mean static object or something else..
Thanks and Regards..
by Steven Pack July 21, 2008 @ 4:02 pm
Your code combinded with Richard Deeming's suggestions (to provide a Func method for object construction) works a treat.
Thanks for taking the time to post.
Regards,
Steve Pack
by John Debo August 07, 2008 @ 5:12 pm
Thanks
by Viktar Karpach August 14, 2008 @ 7:21 am
As Rajinder, I used static DataContext in Business Controller. My site is query based, so it should not be a problem, but unfortunately it is. Once a while it shows some weird errors like Datareader is already open or null reference for something deep inside of LINQ.
I started to look for another approach. So, far what I read above there is no good solution.
Before LINQ, I was using Subsonic ActiveRecord and never had any problems. Looks like LINQ is indeed new generation of abstraction languages.
What do you think about Entity Framework?
by Omel August 15, 2008 @ 6:58 pm
I have some problem about my code behind use vb.net to login file. I thought problem at selected file to db and open the linq db. But i don't know to correctly...pls help me.
Protected Sub Login1_Authenticate(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs) Handles Login1.Authenticate
Dim connectionString As String = ConfigurationManager.ConnectionStrings("AssignmentConnectionString").ToString()
Dim db As New DataClasses1DataContext(connectionString)
Dim sql As String
Dim Usr As New User
If (StrComp(Usr.UserName, Usr.Psswd, 1) = 0) Then
End If
'Sql = "SELECT * FROM User WHERE UserName = '" & Request("UserName") & "' and Psswd = '" & Request("Password") & "'"
sql = "SELECT [UserName], [Psswd] FROM [User] WHERE (([UserName] = @UserName) AND ([Psswd] = @Password))"
If (Usr.UserName IsNot DBNull.Value) Then
'e.Authenticated = True
lblmsg.Text = "Success"
Else
'e.Authenticated = False
lblmsg.Text = "Pls Try Again"
End If
End Sub
by Omel August 15, 2008 @ 7:03 pm
I have some problem about my code behind use vb.net to login file. I thought problem at selected file to db and open the linq db. But i don't know to correctly...pls help me.
Protected Sub Login1_Authenticate(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs) Handles Login1.Authenticate
Dim connectionString As String = ConfigurationManager.ConnectionStrings("AssignmentConnectionString").ToString()
Dim db As New DataClasses1DataContext(connectionString)
Dim sql As String
Dim Usr As New User
If (StrComp(Usr.UserName, Usr.Psswd, 1) = 0) Then
End If
'Sql = "SELECT * FROM User WHERE UserName = '" & Request("UserName") & "' and Psswd = '" & Request("Password") & "'"
sql = "SELECT [UserName], [Psswd] FROM [User] WHERE (([UserName] = @UserName) AND ([Psswd] = @Password))"
If (Usr.UserName IsNot DBNull.Value) Then
'e.Authenticated = True
lblmsg.Text = "Success"
Else
'e.Authenticated = False
lblmsg.Text = "Pls Try Again"
End If
End Sub
by Mike Podonyi August 17, 2008 @ 7:02 am
I love this Factory and agree that it is the best to Save the DataContext in the Items Collection (From a Webdeveloper Point Of View).
But stop... Is this really a "Factory". Spoken in Pattern it seems for me more like a "Service Locator".
What do you think?
Thanks
Mike
by wilsont September 09, 2008 @ 12:54 am
I am facing the exact problem as Igor do, reinit(dbContext = new MyDataContext(....);) the datacontext in the catch does work but in my case my datacontext share with all the business object as this is a static object in the parent class of all the business object.
So you can imagine reinit it in the catch function make other business object Table dis-attach from the datacontext too as this is static.
Does anyone have any workaround on this problem? Can I only remove those problematic changes in the datacontext so I do not have to reinit it again?
by Mike September 22, 2008 @ 6:02 am
GetWebRequestScopedDataContext
GetWebRequestScopedDataContext
In fact you get the same context. But you have to know that from reading the implementation or from documentation. Unfortunately, the only method without documentation is in fact the above mentioned method!
by Gustave November 06, 2008 @ 7:14 am
by Rick O'Shay December 15, 2008 @ 12:00 pm
There seem to be two legitimate options. One is creating a context for each transaction in the business class (e.g., a partial class you provide as a companion to the auto-generated entity). The other is to associate the context with a thread. Java has built-in support for thread local variables called (surprise!) thread locals. Hibernate users have been using that for eons. Can't you do the same thing in C#?
by Rick Strahl December 15, 2008 @ 1:52 pm
But that also means that for desktop apps Linq to SQL is probably not a good match, especially if the app might have multiples of the same entity instances in operation at any given point in time that need to be udated. I don't think there's any solution to that scenario with L2S which - well, sucks!
by Rick O'Shay December 16, 2008 @ 9:34 pm
What I mean by conversation is the popular pattern of using one HTTP session resident object per use-case, versus spraying the session with loose objects. Keeping a Context around in the Conversation (and by extension the HTTP session) works great.
The whole ASP.NET family of technologies seems geared toward small applications with light traffic, and putting a Context in your session conversation fits that mold. Of course, it's not a scalable solution by any stretch of the imagination.
by Ahmed's Random Expressions January 15, 2009 @ 9:35 pm
by Oscar Bautista April 07, 2009 @ 7:11 am
public override IEnumerable
{
using (BulletinWizardDataContext context = DataContext)
{
IEnumerable
join ma in context.MemberAddresses
on m.UserId equals ma.UserId
join s in context.States
on ma.StateId equals s.StateId
where s.StateName == @state
select new memberState
{
userId = m.UserID,
firstName = m.FirstName,
middleInitial = m.MiddleInitial,
lastName = m.LastName,
createDate = m.CreateDate,
modifyDate = m.ModifyDate
}).ToArray
return mems;
}
}
The tables in my joins (Members, States, and MemberAddresses are actual tables in my Database). I created the object memberStates so I could use it in the query above (notice the "Select New memberState". When the data is updated on the web page how do I persist the changes back to the Member Table? My Member Table consists of the following columns: UserId, FirstName, MiddleInitial, LastName, CreateDate, ModifyDate. I am not sure how save the changes back to the database.
Thanks,
by Eric Swann April 07, 2009 @ 5:20 pm
http://ericswann.org/blog/archive/2009/04/06/linq-to-sql-datacontext-provider-revisited.aspx
by Gopinath June 02, 2009 @ 3:47 am
I'm getting the following error when tried to get an instance of data provider
Error 1 'MyDataContext' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TDataContext' in the generic type or method 'DataContextFactory.GetWebRequestScopedDataContext
The code i used to get instance is
DataContextFactory.GetWebRequestScopedDataContext
Can you please tell what is wrong with the my code?
by Mateus June 26, 2009 @ 4:36 pm
Excelent job, hope surff is going alright this summer.
Here is a question thats is breaking my mind.
1 DBML or Multiple DBMLs.
My DB has somewhere close to 150 tables if I create 1 single DBML I will have this huge object, but I will have Unit of Work anywhere I try and so on.
The multiple idea is good, because it breaks the size and gives little manageble DBMLs but it causes other problems like
Agent
AgentFinacialAccounts
FinancialAccounts
Agent is part of the core, so this many to many relationships will happen many times, so lets say we have one DBML with Agent and other related tables, another one will have FinancialAccounts and related tables.
Now AgentFinancialAccounts could leave in the Agents one, or in the FinancialAccounts one, or even in both( I don't like this ).
Anywhere we put it there will be a downside.
Exemple:
I need for every agent created that a financialAccount is created.
So I create the agent, then create the financialAccount, at this point, both have Ids = to 0.
Since AgentFinancialAccounts leave in only one side I can only add a record there after I make a trip to the database and save the changes and get real Ids. If you want to make this process obcure to the user, you have lots of trouble. How are going to tell either the Agent or the FinancialAccount when the other one is done, so you can create the relation record.
The solution in a pratical matter is to have the many-to-many table in both sides, so independently of where things started you can finish it. But then you have redundance of table, and I am almost sure you are going to have troubles ahead.
Hopefully I was clear enough.
Any help will be good...
Thanks
by Free Article Directory July 06, 2009 @ 7:32 am
I want to use DataContext in my Web Application http://www.doyouknow.in
Anyone can suggest which one is a better approach
=> Use as an Application Object ?
=> Use as an Session Object ?
=> Use in every page request ?
Thans in Advance.
Jimit
by Rick Strahl July 06, 2009 @ 7:33 pm
Personally I use the per business object approach and occasionally for special occasions per unit of work. Per business object (in web applications at least) guarantees you're specific to the current request and still get some persistence and reuse if you use multiple business object methods to retrieve/update data. And if I need a more granular approach I can still use I can still create a new instance of a bus object and use that for unit of work operations. Alternately if you don't use a business layer you can apply these same rules to a global page/request context instance.
by .NET Blog July 08, 2009 @ 6:17 am
by Matt H July 20, 2009 @ 5:24 am
Just as a query/suggestion, is there any reason why you wouldn't use a [ThreadStatic] DataContext object, rather than access the object via Thread.GetData()?
by Jerome Meyers July 29, 2009 @ 2:19 pm
I've encountered a problem using the Per Business Object bullet you described above. I wonder if you have encountered it as well.
I understand the situation like this: if we are using a single DataContext to manage a Business Object then I notice a couple of ways we can approach the scenario:
1) The business object is a parent entity that we mapped from our database:
Create the DataContext
Load the business object
Set a property on the business object with the DataContext
2) The business object is actually a sort of context of mapped entities and their relationships:
Create the business object
Load the object graph
In either case it seems that we will only go through the trouble if we have ObjectTracking = True because we want to not only LOAD the entities, but also to UPDATE them, or perhaps insert newly related entities and navigate those relations.
The problem arises when we call SubmitChanges and there is some sort of error that rolls back the transaction on the database side. Since we have all this data loaded, and were probably presenting aspects of it in a UI, we want to keep the context around, because if the user wants to try again, they are going to need the original DataContext. But the reality is it is no easy thing to roll back the changes on the DataContext itself. Lets say my user has loaded in an Order and is trying to create a LineItem. There was an error because of quantities in stock. The user wants to go on to another LineItem and call the Customer to get instructions on the failed attempt. They create a new LineItem and click Save but the Context still tries to insert the old LineItem. What to do?
I've tried creating generic methods that "flush" the DataContext, but with only some success. For instance (I know you don't like extension methods, but I like these {if they work}):
It looks good, but in fact, doesn't work in practice when the inserted entity has foreign key relationships tagged as NOT NULL.
In my world, an Order's LineItem must be associated with a GarmentType and is so through a FK relationship on GarmentTypeID. So when I create the LineItem I get the appropriate GarmentType from the DataContext and associate the two. Then when I try and save but have failures I remove the association and then "flush" the DataContext (simplified version):
What I get, nevertheless, is:
"An attempt was made to remove a relationship between a GarmentType and a LineItem. However, one of the relationship's foreign keys (LineItem.GarmentTypeID) cannot be set to null."
The only way I've found around it, which seems to me to be a real hack, is:
The error occurs not when I am setting the LineItem.GarmentType = null, but rather when the DataContext iterates over its Inserts or Deletes... for if I change the order of ClearInserts/ClearDeletes to ClearDeletes/ClearInserts the error always happens on the first method called on the foreach line.
Is there another way to flush out this DataContext so that it can be used with the loaded Entities. In many cases I've taken to just reloading the entire object graph and refreshing the UI layer so that the user can actually make changes.
Is this the only way?
-Jerome
by Rick Strahl July 29, 2009 @ 3:08 pm
I use the business object scope, but I have to keep an eye on how those resources are used explicitly.
As to your updates and the schema - that too is a problem of LINQ to SQL and personally I remove relationships from the model and foreign keys from tables if possible and instead manage them manually. I've found that database foreign key relationships can be painful to get in line with your model especially if you update the model incorrectly (ie. delete parent before children which won't work for example).
I realize this isn't always possible but LINQ to SQL is a limited tool when you're dealing with rigid data structures anyway.
by Jerome Meyers July 30, 2009 @ 1:34 am
Well, I understand what I was saying to have to do with the scope of the DataContext because it seems to be a factor that one must consider in the process of determining what usage model to pursue. If we are going keep a single DataContext around for the life cycle of a business object than that DataContext ought to be robust enough to handle the errors that typically pop up in that life cycle. However, it seems to take a bit of ugly hacking around to get that to pan out.
Somewhat off topic, but doesn't removing the relationships from the model and foreign keys from the tables reduce LINQ to SQL to being little but a fussy connection object? I guess it'd be a clean way to execute stored procedures.
Really, what I get out of all this, is that the amount of work-arounds a developer has to make in order to "successfully" use LINQ to SQL technology in a real-world or "close to" real world application seems somewhat prohibitive. I've managed to get it to work in medium sized application, for the most part, but it was no easy thing. None of the books cover the subject in the necessary depth, and up to date information is hard to come by. A lot of the advanced topics addressed on the web seem to be addressed to DLINQ and many times the methods used in the code no longer exist.
-Jerome
by Rick Strahl July 30, 2009 @ 2:02 am
That said, although I know of the problems that need to be dealt with I've found that in many applications I've worked on these are edge cases that are not hit very frequently and if hit relatively straight forward to work around. Especially if I don't rely on LINQ to SQL to enforce relationships I've been able to make things work with little fuss by manually controlling update orders and such. The thing is that even with these hassles L2S has been a big boon in reducing data access/business logic code dramatically because a solid 80-90% just works easily smoothly. For the remainder if worse comes to worse I can still easily fall back to raw ADO.NET if necessary and I'm none the worse off for it.
Again understand that's my experience and it's not what I would call a glowing recommendation :-}. I critical but pragmatic enough to recognize the benefits as well as the hazards of using this technology.
by DevelopMENTAL Madness July 30, 2009 @ 12:31 pm
by OLA August 02, 2009 @ 11:17 am
I have been using this in my app and its been excelently.
by OLA August 02, 2009 @ 11:21 am
by .NET Blog August 10, 2009 @ 8:33 am
by Lukask Blog September 23, 2009 @ 11:39 am
by Russ September 29, 2009 @ 1:44 pm
by Yami October 14, 2009 @ 7:58 pm
by Maxi November 02, 2009 @ 11:13 pm
I have read a lot you articles and they help me a lot.
This article gives me a lot insight on ways that I can hang on to my data context.
But I am dealing with a web server farm, distributed cache structure.
I may have data which being cached from another web server and deserialize back in another web server. I think hanging on by thread or by business object would work, or is my understanding incorrect?
What should be done if I am developing in such multi-tiers structure?
by Rick Strahl November 03, 2009 @ 6:02 am
by Daniel Edstrom December 08, 2009 @ 8:50 am
by Elliot L February 17, 2010 @ 5:29 am
A very informative post - I'm the Lead Developer at AssemblyPoint. We recently migrated our application to Windows Azure. The application depended upon a DataContext object cached in session state - which isn't an option in Azure. We considered rewriting the application to not use a cached DataContext object and decided that it would involve too much work. Hence, we wrote a library that could setup a new DataContext correctly based on the modified vs. original entity.
I've referenced your article in my blog.
Thanks,
Elliot L.
by M Shafqat March 08, 2010 @ 3:15 am
by SEO West Palm Beach May 24, 2010 @ 7:01 am
by Jason Butera March 10, 2011 @ 5:15 pm
How would you suggest implementing this factory in a WCF service? When I use your factory code in an ASP.net website or winform app, it works flawlessly. However, I get a lot weirdness in a WCF service even when I use the InstanceContextMode.PerCall behavior. Errors like "The Database generated a key that is already in use" when I'm adding a record in an autonumbered table with two separate wcf calls from the same console app. Basically, I hit the service to insert a record, then I deleted the records in the database and reseeded the table, then hit the service again. I know what the message means, however, I thought the PerCall contextmode would not have pulled the lastly used datacontext from the AllocateNamedDataSlot.
I'm reusing a data assembly with your datacontextfactory implemented. It's safe for my only in my WCF service to always use a new instance of the datacontext. How could I always return a new datacontext by modifying your code when it's being called by a WCF service? Checking for HttpContext tells me web, what tells me WCF?
by Sascha Holl May 15, 2011 @ 3:22 am