Geeks With Blogs

@azamsharp
  • azamsharp The WWDC app says that there are new videos but there is nothing under videos!!! about 408 days ago
  • azamsharp Can I post my opinion on iOS 7 Beta or is it under NDA or something? about 408 days ago
  • azamsharp iOS 7 BETA installed successfully! about 408 days ago
  • azamsharp iOS 7 BETA installed! Now restoring! I am on NET10 which uses AT&T network. Hopefully 3G will still work! Lets c.. about 408 days ago
  • azamsharp The missing of button borders in Xcode 5 makes it hard to determine the clickable area. about 408 days ago
  • azamsharp I might wait for iOS to be released before I can put it on my device! Don't want a bricked or unstable device! about 408 days ago
  • azamsharp Android has many features that no one uses, number of versions that no one upgrades to and thousands of apps that no one pays for. about 408 days ago
  • azamsharp Maybe it is just me but the new iOS 7 UI looks like Windows Phone 8 UI! #maybeIamCrazy about 409 days ago
  • azamsharp Anyone using H20 network upgraded to iOS 7 BETA Keep me updated if you face any problems. about 409 days ago
  • azamsharp @merowing_ You already downloaded it! I cannot even load the developer's website! :( about 409 days ago

AzamSharp Some day I will know everything. I hope that day never comes.

The DataContext is an object representation of the database. It is responsible for all the operations performed on the database. One of the biggest issues when working with LINQ to SQL is the life time of an object when part of the DataContext. The DataContext is designed in a way that it will only handle the objects that were created by it. Although you can attach objects of one DataContext to a different DataContext but when you do this all hell breaks loose.

Let's take a look at the example below where I am using a DepartmentRepository class to get the Department by Id.

 public static tblDepartment GetDepartment(int id)
        {
           
            using (VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
            {
                var department = (from d in dc.tblDepartments
                                  where d.DepartmentID == id
                                  select d).SingleOrDefault();

              

                return department;
            }
        }

And here is the calling code in Main method.

var department = DepartmentRepository.GetDepartment(4551);

Everything works fine at this point. Now, let's try to update the department using the method below.

 public static void UpdateDepartment(tblDepartment department)
        {
            using (VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
            {
                dc.SubmitChanges();
            }
        }

And here is the code in the Main method:

 var department = DepartmentRepository.GetDepartment(4551);

            department.Abbreviation = "edited";
                       
            DepartmentRepository.UpdateDepartment(department);         

The code will run without any exceptions! But if you check the database the update will not be performed. The reason is that the department object that you are trying to update belongs to a different DataContext and the update method uses a new DataContext object which does not contain an entry for the dirty department object (dirty meaning changed!). You can try attaching the object to the DataContext as shown below:  

 public static void UpdateDepartment(tblDepartment department)
        {
            using (VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
            {
                dc.tblDepartments.InsertOnSubmit(department);
                dc.SubmitChanges();
            }
        }

But this will insert a duplicate entry for the department object. You can try attaching the object to the DataContext using the DataContext's attach method.

 public static void UpdateDepartment(tblDepartment department)
        {
            using (VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
            {
                // attaching the entity, the second parameter means modified!
                dc.tblDepartments.Attach(department,true);
                dc.SubmitChanges();
            }
        }

The above code will produce the following error:

 An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.

Basically, the error is saying that it is having a hard time finding the last update. You can easily remove the error by adding a timestamp field to the tblDepartments as shown below:

Now, if you run the code it will work fine and the department object will be updated. The solution achieved by adding a timestamp column was not a good solution since now we are forced to add a timestamp column in our table. Let's say that we accept this timestamp fiasco and added another table to our application. This table is called the "tblCourses" and it has a relation with "tblDepartments" as shown below:

Now, with the relationship intact if we try to update the department it will fail with the same error message.

 An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.

The first thing that will come to your mind is to add the timestamp to the tblCourses. Well, it won't work and you will get the same error. One way to solve this error is to detach the child objects of the department object when persisting the department object. Here is the detach method for the department object which is placed inside the partial class "tblDepartment".

 public void Detach()
        {
            this.PropertyChanged = null;
            this.PropertyChanging = null;

            this.tblCourses.Assign(default(IEnumerable<tblCourse>));
        }

In the code above I am simply resetting the objects inside the tblCourses collection. The default(IEnumerable<tblCourse>) will return null. You can also write the above code like this:

 public void Detach()
        {
            this.PropertyChanged = null;
            this.PropertyChanging = null;

            this.tblCourses.Assign(null);
        }

But it is much better to use the default keyword when assigned default values.

Still! this is not a good technique. Because if you have many relationships going out of the tblDepartment class then you will be removing those relationships when performing action on the tblDepartment object.

The design of the LINQ to SQL DataContext object tells us that the DataContext object should be used for per transaction operations. This means that the operations performed on a particular object should belong to a single DataContext object. This means that for each group of action performed on the entity a single DataContext object should handle it. Take a look at the following class diagram:

I have created a DepartmentRepository class which inherits from the BaseRepository and implement the IDepartmentRepository interface. Here is the code:

 public class BaseRepository
    {
        protected VirtualRoomDBDataContext dc = null;

        public BaseRepository()
        {
            dc = new VirtualRoomDBDataContext();
        }
    }

 public interface IDepartmentRepository
    {
         bool Create(tblDepartment department);
         tblDepartment Get(int id);
         tblDepartment Get(string name);
         void Update(tblDepartment department);
    }


 public class DepartmentRepository : BaseRepository,IDepartmentRepository
    {
        public void Update(tblDepartment department)
        {
            dc.SubmitChanges();
        }

             public tblDepartment Get(string name)
        {
            var department = (from f in dc.tblDepartments
                              where f.Name == name
                              select f).SingleOrDefault();

            return department;
        }

        public bool Create(tblDepartment department)
        {
            bool result = false;

            try
            {
                dc.tblDepartments.InsertOnSubmit(department);
                dc.SubmitChanges();
                result = true;
            }
            catch (Exception ex)
            {
                // log exception!
            }          

            return result;
        }

        public tblDepartment Get(int id)
        {
            var department = (from f in dc.tblDepartments
                             where f.DepartmentID == id
                             select f).SingleOrDefault();

            return department;
        }
     
    }

And now I can use the above classes in my TestSuite like this:

 private IDepartmentRepository repository;

        [SetUp]
        public void initialize()
        {
            repository = new DepartmentRepository();
        }

 [Test]
        [RollBack]
        public void should_be_able_to_update_the_department_successfully()
        {
            var cosc = TestHelper.CreateComputerScienceDepartment();
          
            repository.Create(cosc);

            cosc.Name = "updated name";
            cosc.Abbreviation = "updated abbreviation";
            cosc.DateModified = DateTime.Now;

            repository.Update(cosc);

            var vCosc = repository.Get(cosc.DepartmentID);

            Console.WriteLine(vCosc.Name);
            Console.WriteLine(vCosc.Abbreviation);

            Assert.AreEqual(cosc.Name, vCosc.Name);
            Assert.AreEqual(cosc.Abbreviation, vCosc.Abbreviation);
        }

        [Test]
        [RollBack]
        [ExpectedException(typeof(Exception),"Department name should be unique")]
        public void should_throw_exception_if_department_name_is_not_unique()
        {
            var cosc = TestHelper.CreateComputerScienceDepartment();
            repository.Create(cosc);

            var cosc2 = TestHelper.CreateComputerScienceDepartment();
            repository.Create(cosc2);            
        }

        [Test]       
        [RollBack]
        public void should_be_able_to_create_department_successfully()
        {
            var cosc = TestHelper.CreateComputerScienceDepartment();

            repository.Create(cosc);

            Assert.IsTrue(cosc.DepartmentID > 0);

            var vCosc = repository.Get(cosc.DepartmentID);

            Assert.AreEqual(cosc.Name, vCosc.Name);
            Assert.AreEqual(cosc.Abbreviation, vCosc.Abbreviation);
        }

The question that what if I really want to use multiple DataContexts and pass objects from one context to the other. There are couple of ways that I have already discussed above which includes using timestamps and detaching the objects. If you don't feel comfortable with those techniques then there are some other ways too. One way is to pass the parameters to the Repository and construct the object inside the Repository. Take a look at the following code:

 public static void  Update1(int departmentId, string name, string abbreviation, bool active)
        {
            using (VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
            {
                var department = (from d in dc.tblDepartments
                                  where d.DepartmentID == departmentId
                                  select d).SingleOrDefault();

                if (department == null) throw new ArgumentNullException("Department does not exists!");

                // update the department

                department.Name = name;
                department.Abbreviation = abbreviation;
                department.DateModified = DateTime.Now;
                department.Active = active;

                dc.SubmitChanges();
            }
        }

If you have lot of columns then the above technique could become messy. Another way is to assign the updated values at the user interface level and send in the object itself instead of the individual parameters. Take a look at the code below:

 public static void Update2(tblDepartment department)
        {
            using (VirtualRoomDBDataContext dc = new VirtualRoomDBDataContext())
            {
                var persistedDepartment = (from d in dc.tblDepartments
                                           where d.DepartmentID == department.DepartmentID
                                           select d).SingleOrDefault();

                // make sure that the persistedDepartment exists!

                persistedDepartment.Name = department.Name;
                persistedDepartment.Abbreviation = department.Abbreviation;

                dc.SubmitChanges();
            }
        }

So, there you have it!   

UPDATE:

The technique for having a separate instance of the repository will only work if your object uses primitive types to refer to the other objects. For it to work using the object relationship you will need to create a single repository which will handle all the objects. Something like shown below:

IProjectRepository:

bool AddCustomer(Customer customer);
bool AddAccount(Account account);
Customer GetCustomer(id);

Now, you inherit from the IProjectRepository interface. The ProjectRepository class will be the ONLY repository in the project. You will not need multiple repositories due to the LINQ to SQL DataContext issue.


 


 


Posted on Saturday, May 17, 2008 5:57 AM | Back to top


Comments on this post: Dealing with LINQ to SQL DataContext Issues!

# re: Dealing with LINQ to SQL DataContext Issues!
Requesting Gravatar...
I discovered pretty much the same things you did. I'm working on an asp.net app and trying to do Linq to SQL using POCOs. I've managed to hack it together for my current iteration but will probably drop linq to sql as the DAL in the next iteration.

I wouldn't have had so much trouble if I could have stopped the datacontext from attaching all the other related linq to sql objects in the graph when I attached a detached object. Since there wasn't a built in way, my solution was similar to yours; set the references to null. I created two methods: ClearRelationships() and RestoreRelationships(). Before I attach an object I run ClearRelationships() that copies the references over to a temporary field. After SubmitChanges() I loop through any inserted or updated objects and call RestoreRelationships() which copies the reference from the temp field back over to "real" field.

Obviously, my poco's aren't poco's anymore.

Left by be on May 25, 2008 5:57 AM

Your comment:
 (will show your gravatar)
 


Copyright © Mohammad Azam | Powered by: GeeksWithBlogs.net | Join free