Csla and LinqToSql integration - tell me how?

Csla and LinqToSql integration - tell me how?

Old forum URL: forums.lhotka.net/forums/t/5464.aspx


tchimev posted on Thursday, September 25, 2008

I would like to use CSLA and LinqToSql in my solutions.
I want to use LinqToSQl functionality instead of stored procedures or any sql statements to select/insert/update/delete data from the Database.
I want to simplify the code for the DataPortal methods so I can use it in snippets or abstract classes.
I would like to share my solution with you.
Can you tell me is there a better solution to achieve the goal?

Fetch data:
in my design I construct and execute Linq queries in master business objects only. All child business objects populate themselves with Linq object passed from the parent object.
example of BO.Persons master business list object:
DB.Person is Linq object

   private IQueryable<DB.Person> ConstructQuery(Criteria criteria, DB.DefaultDataContext db)
   {
       IQueryable<DB.Person> result = null;
       result = from p in db.Persons
                  where p.FirstName.StartsWith(criteria.Name)
                  select p;

       return result;
   }

   private void DataPortal_Fetch(Criteria criteria)
   {
       RaiseListChangedEvents = false;
       using (var db = ContextManager<DB.DefaultDataContext>.GetManager(Properties.Settings.Default.DefaultConnectionString, false))
       {
            foreach (var item in ConstructQuery(criteria, db.DataContext))
            {
                 this.Add(Person.GetPerson(item));
            }
       }
       RaiseListChangedEvents = true;
   }

example of BO.Person child business object:

        //recieve Linq object as parameter
        private void Child_Fetch(DB.Person person)
        {
            this.MapFromLinq(person);
        }

        private void MapFromLinq(DB.Person person)
        {
            LoadProperty<string>(_firstName, person.FirstName);
            LoadProperty<string>(_middleName, person.MiddleName);         
        }



Insert/Update data:
In situations when I have master business base with child business base objects I have to create Linq object from master business base which has to know its foreign keys.
I prefer to use foreign key linq objects instead of ID's for foreign keys.
I implemented it like this:
1. In DataPortal_Insert() method I call MapToLinq() method which creates master object to Linq object.
2. Then create Linq objects from children.
3. After that I call DataPortal.UpdateChild(childBB, dbChild) and pass the created Linq object as parameter, so the business child object can save itself to Database.
4. Set foreign key objects to master linq object.
5. Attach master linq object ot DataContext
6. Reload master business object from the saved master Linq object.

        public DB.Employment MapToLinq()
        {
            DB.Employment dbEmployment = new DB.Employment()
            {
                /* load Linq Employment properties */
                HireDate = ReadProperty<SmartDate, DateTime>(_hireDate)
            };

            //read Person(child)
            BO.Person boPerson = ReadProperty<Person>(_employee);

            //map child business object Person to Linq object
            DB.Person dbPerson = boPerson.MapToLinq();

            //insert/delete/update business object Person
            DataPortal.UpdateChild(boPerson, dbPerson);

            //map saved Linq table Person to his parent table Contract
            dbContract.Person = dbPerson;
            
            return dbContract;
        }

        private void DataPortal_Insert()
        {
            using (var db = ContextManager<DB.DefaultDataContext>.GetManager(Properties.Settings.Default.DefaultConnectionString, false))
            {
                //map master and update child
                DB.Employment dbEmployment = this.MapToLinq();

                //mark for insertion
                db.DataContext.Employments.InsertOnSubmit(dbEmployment);

                //save to DataBase
                db.DataContext.SubmitChanges();

                //reload business object properties from saved data
                this.MapFromLinq(dbEmployment);
            }
        }

        private void DataPortal_Update()
        {
            using (var db = ContextManager<DB.DefaultDataContext>.GetManager(Properties.Settings.Default.DefaultConnectionString, false))
            {
                //map master and update child
                DB.Employment dbEmployment = this.MapToLinq();

                //attach Employment for update
                db.DataContext.Employments.Attach(dbEmployment, this.IsSelfDirty);
                
                //save to DataBase                
                db.DataContext.SubmitChanges();

                //reload business object properties from saved data
                this.MapFromLinq(dbEmployment);
            }
        }

example of BO.Person child business object:

        private void MapFromLinq(DB.Person dbPerson)
        {
    LoadProperty<string>(_firstName, dbPerson.FirstName);
            LoadProperty<string>(_middleName, dbPerson.MiddleName);         
        }

        public DB.Person MapToLinq()
        {
            //check if business object Person isValid
            if (this.IsValid)
            {
                  return new DB.Person()
                    {                           
                       FirstName = ReadProperty<string>(_firstName),
              MiddleName = ReadProperty<string>(_middleName)
                    };

    }

            return null;
        }

        private void Child_Insert(DB.Person dbPerson)
        {
            using (var db = ContextManager<DB.DefaultDataContext>.GetManager(Properties.Settings.Default.DefaultConnectionString, false))
            {
                //mark parameter dbPerson(Linq object) for insert
                db.DataContext.Persons.InsertOnSubmit(dbPerson);

                //save to DataBase
                db.DataContext.SubmitChanges();

                //reload business object from saved data
                this.MapFromLinq(dbPerson);
            }
        }

        private void Child_Update(DB.Person dbPerson)
        {
            using (var db = ContextManager<DB.DefaultDataContext>.GetManager(Properties.Settings.Default.DefaultConnectionString, false))
            {
                //map parameter dbPerson(Linq to sql table) for update
                db.DataContext.Persons.Attach(dbPerson, this.IsSelfDirty);
                
                //save to DataBase
                db.DataContext.SubmitChanges();

                //reload business object from saved data
                this.MapFromLinq(dbPerson);
            }
        }



Delete Data:
in DataPortal_Delete(Criteria) method master business object fetches the object by criteria then calls delete() and save()
on the fetched object.
After that DataPortal_DeleteSelf() is called which maps the master business object to Linq object through MapToLinq() method.
And also maps and Updates any child business objects.
Then commit changes to Db.

        private void DataPortal_DeleteSelf()
        {
            using (var db = ContextManager<DB.DefaultDataContext>.GetManager(Properties.Settings.Default.DefaultConnectionString, false))
            {
                //map master and update child
                DB.Contract dbContract = this.MapToLinq();

       //attach for deletion
                db.DataContext.Contracts.Attach(dbContract);
                db.DataContext.Contracts.DeleteOnSubmit(dbContract);

       //save to Db
                db.DataContext.SubmitChanges();
            }
        }

    private void DataPortal_Delete(Criteria criteria)
    {
        BO.Contract obj = Contract.GetContractById(criteria.Id);
        obj.Delete();
        obj.Save();
    }

tchimev replied on Friday, September 26, 2008

No one to respond?
Is it my description hard to understand or just no one uses LinqToSql in that way?

ajj3085 replied on Friday, September 26, 2008

Looks similar to what I have.  For fetching though, I did try returning a query from a function to build the "base" query.. but I'm not sure I'll continue doing that, since it makes the code a little more difficult to read, and linq2sql is about readablity.

Your updates look similar to what I do (although in my DataContext subclass, I do have mappings that still use procs for CUD operations).  On thing though on doing updates..

You use Attach( object, bool ).  I found that doesn't seem to work even if you tell it to update.. so I've found I need to create a dummy entity and just set any primary key / timestamp values and use the Attach( object, object ) overload.  This seems to be the only reliable way to make the update happen.

Also, for child objects you'll need to subscribe to the entity's PropertyChanged event, so that you can get primary key's back.  Something like this:

dbPerson.PropertyChanged +=
    delegate( object sender,PropertyChangedEventsArgs e ) {
         if ( e.PropertyName == "ChildId" ) {
               childId = dbPerson.PersonId.Value;
         }
     };

But that's only for child objects; in the root object you can just get the value directly out of the data entity after calling SubmitChanges.

HTH
Andy

PIDC replied on Friday, September 26, 2008

I found that Linq is very good for fetching data from SQL server. However , with InsertUpdate I prefer to use stored procedures for reasons :

1. When using LINQ generated class to do InsertUpdate, i have problem with the lengh of data fields. If the field lenght (string) is bigger than database column. LINQ throws exception. That mean you need to make sure about the data length before submitting to Server.

2. I use InsertUpdate not only for .NET, but also for my DB datamigration/backup/restore.

3. the code looks simple. (Linq for CSLA 2.1.4)

Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)
Using ctx = ContextManager(Of REF_DATADataContext).GetDataContext

Dim qry = From _nc In ctx.DataContext.SSRFANVs Where _nc.SUN_DB = _DTB AndAlso _nc.CATEGORY = criteria.Category AndAlso _nc.CODE = criteria.Code Take 1

For Each _nc In qry
_category = _nc.CATEGORY.Trim
_code = _nc.CODE.Trim
_lookup = _nc.LOOKUP.Trim
_updated.Text = _nc.UPDATED.Trim
_name = _nc.NAME.Trim
_prohbPost = _nc.PROHB_POST
_budgetCheck = _nc.BUDGET_CHECK
_budgetStop = _nc.BUDGET_STOP
_data1 = _nc.DATA_1.Trim
Exit For
Next

End Using

End Sub

Protected Overrides Sub DataPortal_Insert()
Using ctx = ContextManager(Of REF_DATADataContext).GetDataContext
ctx.DataContext.pbs_NC_InsertUpdate(_DTB, _category, _code, _lookup, _updated.DBValue, _name, _prohbPost, _budgetCheck, _budgetStop, _data1)
End Using
End Sub

sergeyb replied on Friday, September 26, 2008

Just one note: you should have validation rules that would keep the user from entering strings that are too long into your UI.

Sergey Barskiy
Principal Consultant
office: 678.405.0687 | mobile: 404.388.1899

Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

-----Original Message-----
From: PIDC [mailto:cslanet@lhotka.net]
Sent: Friday, September 26, 2008 5:25 PM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] Csla and LinqToSql integration - tell me how?

I found that Linq is very good for fetching data from SQL server. However , with InsertUpdate I prefer to use stored procedures for reasons :

1. When using LINQ generated class to do InsertUpdate, i have problem with the lengh of data fields. If the field lenght (string) is bigger than database column. LINQ throws exception. That mean you need to make sure about the data length before submitting to Server.

2. I use InsertUpdate not only for .NET, but also for my DB datamigration/backup/restore.

the code looks simpler. (Linq for CSLA 2.1.4)

Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)
Using ctx = ContextManager(Of REF_DATADataContext).GetDataContext

Dim qry = From _nc In ctx.DataContext.SSRFANVs Where _nc.SUN_DB = _DTB AndAlso _nc.CATEGORY = criteria.Category AndAlso _nc.CODE = criteria.Code

For Each _nc In qry
_category = _nc.CATEGORY.Trim
_code = _nc.CODE.Trim
_lookup = _nc.LOOKUP.Trim
_updated.Text = _nc.UPDATED.Trim
_name = _nc.NAME.Trim
_prohbPost = _nc.PROHB_POST
_budgetCheck = _nc.BUDGET_CHECK
_budgetStop = _nc.BUDGET_STOP
_data1 = _nc.DATA_1.Trim
Exit For
Next

End Using

End Sub

Protected Overrides Sub DataPortal_Insert()
Using ctx = ContextManager(Of REF_DATADataContext).GetDataContext
ctx.DataContext.pbs_NC_InsertUpdate(_DTB, _category, _code, _lookup, _updated.DBValue, _name, _prohbPost, _budgetCheck, _budgetStop, _data1)
End Using
End Sub


PIDC replied on Friday, September 26, 2008

In some cases, tha data is generated automatically in the background without user intervention, so that it is better truncate data than aknowledge user about this.

ajj3085 replied on Monday, September 29, 2008

You can still have Linq call stored procedures when doing insert update and delete operations, so I'm not sure what you're getting at.

The code does look simpler, but I like having Linq update all the key references for me, and submitting the changes as a batch.

tchimev replied on Saturday, September 27, 2008

Thank you for your opinions.

I have missed the TimeStamp field I use in my business objects, but it is declared like this :

private byte[] timeStamp = new byte[eight];

I'm sorry I didn't write that code in the examples above, but I did it to reduce the post.

and I always update the value  in MapFromLinq() method, and it seems to work properly.
All my Database tables have TimeStamp column, so I can use Attach(object, bool) method.
In the MapFromLinq() method, which is called after each SubmitChanges(), I reload all the properties of the business object, like ID, TimeStamp, and others.

rasupit replied on Sunday, September 28, 2008

tchimev:
I would like to use CSLA and LinqToSql in my solutions.
I want to use LinqToSQl functionality instead of stored procedures or any sql statements to select/insert/update/delete data from the Database.
I want to simplify the code for the DataPortal methods so I can use it in snippets or abstract classes.
I would like to share my solution with you.
Can you tell me is there a better solution to achieve the goal?


If code gen is an option, you can take a look at this solution.
Ricky

Copyright (c) Marimer LLC