LLBL..sooo powerful

Posts   
 
    
MatthewM
User
Posts: 78
Joined: 26-Jul-2006
# Posted on: 30-Aug-2006 17:41:20   

We/I have finally started getting past the learning curves and now are getting into some awesome lpt code gen..I cannot believe the things that are possible!

Just wanted to say thanks for a great product.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 31-Aug-2006 07:06:17   

Thanks for the feedback

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 31-Aug-2006 09:50:57   

MatthewM wrote:

We/I have finally started getting past the learning curves and now are getting into some awesome lpt code gen..I cannot believe the things that are possible!

Just wanted to say thanks for a great product.

Thanks!! smile

Frans Bouma | Lead developer LLBLGen Pro
Jeff M
User
Posts: 250
Joined: 04-Aug-2004
# Posted on: 03-Sep-2006 23:47:56   

MatthewM wrote:

We/I have finally started getting past the learning curves and now are getting into some awesome lpt code gen..I cannot believe the things that are possible!

It's an "eye-opener", isn't it? Once you climb the learning curve, you can really appreciate the beauty and eloquence of the business layer that LLBLGen creates. It's like good poetry.

Jeff

MatthewM
User
Posts: 78
Joined: 26-Jul-2006
# Posted on: 06-Sep-2006 20:42:25   

Just wanted to share some of things we have done so far:

I have modified the standard templates to add properties for encrypting every Entity ID as well as exposing high level members for fields that are multilingual and then hiding the actual property.

I then created another set of LPT templates which generate an actual middle layer that wraps the DAL classes exposing only the members I want. These templates incorporate use of a caching engine and handle synchronization of the DAL generated classes.

All in all it is shaping up to be quite nice.

The only thing that is/was proving quite challenging is keeping the DAL internal relationships intact when a new entity is added to a cached entity...And the fact that the caching is cross server.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 06-Sep-2006 20:51:55   

cross server caching? Isn't that causing a lot of headaches for example related to security?

Frans Bouma | Lead developer LLBLGen Pro
MatthewM
User
Posts: 78
Joined: 26-Jul-2006
# Posted on: 06-Sep-2006 23:58:43   

Otis wrote:

cross server caching? Isn't that causing a lot of headaches for example related to security?

Security from what angle?

Headaches, omg yes..Essentially the middle layer save looks like this:

public override void Save()
{
  lock(UnderylingEntity)
  {
    UnderylingEntity.Save();
  }

  CacheManager.SignalRefetch(UnderylingEntity.EntityType, UnderylingEntity.ID);
}

..And SignalRefetch blocks until the other servers return from the signal.

The nasty part I am still working out is to keep the internal relationships straight.

Lex
User
Posts: 2
Joined: 15-Nov-2006
# Posted on: 15-Nov-2006 10:27:05   

MatthewM wrote:

Just wanted to share some of things we have done so far:

I have modified the standard templates to add properties for encrypting every Entity ID as well as exposing high level members for fields that are multilingual and then hiding the actual property.

I then created another set of LPT templates which generate an actual middle layer that wraps the DAL classes exposing only the members I want. These templates incorporate use of a caching engine and handle synchronization of the DAL generated classes.

All in all it is shaping up to be quite nice.

The only thing that is/was proving quite challenging is keeping the DAL internal relationships intact when a new entity is added to a cached entity...And the fact that the caching is cross server.

Hi,

Would it be possible for you to share this ? Thank you.

MatthewM
User
Posts: 78
Joined: 26-Jul-2006
# Posted on: 15-Nov-2006 20:59:34   

Lex wrote:

Would it be possible for you to share this ? Thank you.

A couple of things:

1) IP issues. 2) Some things are extremely proprietary to our needs 3) I wouldn't mind sharing snippets and a general overview.

So..here are a few things that might be of interest.

1) This is LPT code for updating an entities (A) parent (B) collection (C) which holds the entity (A) in question. Note It is not pretty as I have had no need to clean it up yet and I'm not even sure if it will make sense. Why would I want this? Well, the parent (B) is in memory, it has specific collections (C) in memory. The entity (A) does not have any reference to the parent (B) other than by ID and the cache manager can retrieve the parent (B) without a Db hit. BUT, I need the parent (B) to know about the entity (A) in its child collection. Now, the fun is that there might be N# of parent's that have collections of A so all of those parent's must be updated.

        internal override void UpdateGraphRelations(bool IsNew)
        {
            // Place additional code for updating graphs within the USER_CODE_REGION
<%=DotNetTemplateEngine.GetUserCodeRegion("CNetUpdateGraphRelations", "//")%>       

<%
string ChildCollectionName = "";
bool FoundDropped = false;
EntityRelation ChildRelation = null;
for (int f = 0;f < e.Relations.Count;f++)
    if (e.Relations[f].RelationType == EntityRelationType.ManyToOne)
        if (!e.Relations[f].RelationEndName.StartsWith("Domain") && e.Relations[f].RelationEndName != "Module")
        if (e.Name != "UserActivity" && e.Name != "Portal")
        {
            for (int g = 0;g < e.Relations[f].RelationEndPoint.Relations.Count;g++)
            {
                if (e.Relations[f].RelationEndPoint.Relations[g].RelationEndName == e.Relations[f].RelationStartName &&
                    e.Relations[f].RelationEndPoint.Relations[g].RelationType == EntityRelationType.OneToMany)
                {
                    ChildCollectionName = e.Relations[f].RelationEndPoint.Relations[g].UtilizingPropertyName;
                    ChildRelation = e.Relations[f].RelationEndPoint.Relations[g];
                    break;
                }
            }
            if (!FoundDropped){%>bool Found = false;<%FoundDropped=true;}%>
            <%if (e.Relations[f].FieldRelations[0].RelationStartField.IsNullable){%>if (this.<%=e.Relations[f].FieldRelations[0].RelationStartField.FieldName%> != null)
            {<%}%>
                <%=e.Relations[f].RelationEndName%> _<%=e.Relations[f].RelationEndName%> = new <%=e.Relations[f].RelationEndName%>( (int)this.<%=e.Relations[f].FieldRelations[0].RelationStartField.FieldName%>);
                if (_<%=e.Relations[f].RelationEndName%>.ExistsInCache && _<%=e.Relations[f].RelationEndName%>.CurrentObject.alreadyFetched<%=ChildCollectionName%>)
                {
                    Found = false;
                    if (!IsNew)
                    {   // check if it exists
                        lock(_<%=e.Relations[f].RelationEndName%>.CurrentObject.<%=ChildCollectionName%>)
                        {
                            for (int f = 0;f < _<%=e.Relations[f].RelationEndName%>.CurrentObject.<%=ChildCollectionName%>.Count;f++)
                                if (_<%=e.Relations[f].RelationEndName%>.CurrentObject.<%=ChildCollectionName%>[f].<%= ((EntityFieldDefinition)ChildRelation.RelationEndPoint.PrimaryKeyFields[0]).FieldName%> == this.<%=((EntityFieldDefinition)ChildRelation.RelationEndPoint.PrimaryKeyFields[0]).FieldName%>)
                                {
                                    Found = true;
                                    break;
                                }
                        }       
                    }
                    if (!Found)
                    {
                        lock(_<%=e.Relations[f].RelationEndName%>.CurrentObject.<%=ChildCollectionName%>)
                        {
                            // update the parent collection
                            _<%=e.Relations[f].RelationEndName%>.CurrentObject.<%=ChildCollectionName%>.Add(CurrentObject);
                        }
                    }
                }
            <%if (e.Relations[f].FieldRelations[0].RelationStartField.IsNullable){%>}<%}%>
    <%}%>
        }

That produces this:

    internal override void UpdateGraphRelations(bool IsNew)
        {
            // Place additional code for updating graphs within the USER_CODE_REGION
    // __LLBLGENPRO_USER_CODE_REGION_START CNetUpdateGraphRelations
    // __LLBLGENPRO_USER_CODE_REGION_END        

bool Found = false;
            
                DocumentLibraryModule _DocumentLibraryModule = new DocumentLibraryModule((int)this.DocumentLibraryModuleId);
                if (_DocumentLibraryModule.ExistsInCache && _DocumentLibraryModule.CurrentObject.alreadyFetchedDocumentLibraryDocument)
                {
                    Found = false;
                    if (!IsNew)
                    {   // check if it exists
                        lock(_DocumentLibraryModule.CurrentObject.DocumentLibraryDocument)
                        {
                            for (int f = 0;f < _DocumentLibraryModule.CurrentObject.DocumentLibraryDocument.Count;f++)
                                if (_DocumentLibraryModule.CurrentObject.DocumentLibraryDocument[f].DocumentLibraryDocumentId == this.DocumentLibraryDocumentId)
                                {
                                    Found = true;
                                    break;
                                }
                        }       
                    }
                    if (!Found)
                    {
                        lock(_DocumentLibraryModule.CurrentObject.DocumentLibraryDocument)
                        {
                            // update the parent collection
                            _DocumentLibraryModule.CurrentObject.DocumentLibraryDocument.Add(CurrentObject);
                        }
                    }
                }
            
    
            if (this.InterwovenServerId != null)
            {
                InterwovenServer _InterwovenServer = new InterwovenServer((int)this.InterwovenServerId);
                if (_InterwovenServer.ExistsInCache && _InterwovenServer.CurrentObject.alreadyFetchedDocumentLibraryDocument)
                {
                    Found = false;
                    if (!IsNew)
                    {   // check if it exists
                        lock(_InterwovenServer.CurrentObject.DocumentLibraryDocument)
                        {
                            for (int f = 0;f < _InterwovenServer.CurrentObject.DocumentLibraryDocument.Count;f++)
                                if (_InterwovenServer.CurrentObject.DocumentLibraryDocument[f].DocumentLibraryDocumentId == this.DocumentLibraryDocumentId)
                                {
                                    Found = true;
                                    break;
                                }
                        }       
                    }
                    if (!Found)
                    {
                        lock(_InterwovenServer.CurrentObject.DocumentLibraryDocument)
                        {
                            // update the parent collection
                            _InterwovenServer.CurrentObject.DocumentLibraryDocument.Add(CurrentObject);
                        }
                    }
                }
            }
    
        }

2) My wrapper class is pretty straight fwd except that in place of a child entity making a Db call to lazy load it like the std template does, it instead hits the cache manager and returns the cached entity. I am going to have to refrain from the cache manager impl itself as that was a lot of time & energy. However, you'll notice a reference to "CurrentObject". This is a property in the wrapper that looks like below and serves to negotiate returning the underlying represented entity.

    internal DocumentLibraryDocumentEntity CurrentObject
        {
            get 
            {
                if (m_DocumentLibraryDocumentEntity != null)
                    return m_DocumentLibraryDocumentEntity;
                else if (CachedEntities.Instance.ExistsInCache(EntityType.DocumentLibraryDocumentEntity, m_DocumentLibraryDocumentId))
                    return (DocumentLibraryDocumentEntity)CachedEntities.Instance.CachedItem( EntityType.DocumentLibraryDocumentEntity, m_DocumentLibraryDocumentId);
                else 
                {
                    if (m_DocumentLibraryDocumentId != 0)
                        m_DocumentLibraryDocumentEntity = new DocumentLibraryDocumentEntity(m_DocumentLibraryDocumentId);
                    else
                        m_DocumentLibraryDocumentEntity = new DocumentLibraryDocumentEntity();
                    return m_DocumentLibraryDocumentEntity;
                }
            }
        }

3) As for encrypting Ids, or multi-lingual, those weren't too bad to puzzle out. I could elaborate more later though.

Any other questions about what we've done I would be happy to try and answer..although I fear most of it will be too far out of context.

4) I'll share one other thing which I am very proud of..Our wrapper objects are usable in transactions, much the same way as the LLBL core entities..Taking advantage of their native transacting, and breaking apart the wrapper's save code until the transaction commit I am able to avoid updating the cache until the comit is done. See these:

    internal override bool coreSave(bool Recurse, bool UnsafeSave, bool finishSave)
        {
            bool Result = false;
            m_IsNew = CurrentObject.IsNew;
            bool IsNew = m_IsNew;

            if (!UnsafeSave && !CanSave())
                return false;

            if (!PreSave(IsNew))
                return false;

            WriteUserActivityRecord();
            
            lock(CurrentObject)
            {
                try
                {
                    Result = CurrentObject.Save(Recurse);
                }
                catch (Exception)
                {
                    throw;
                }
            }

            if (!Result)
                return false;

            if (finishSave && !ParticipatesInTransaction)
                FinishSave();
        
            return Result;
        }

    internal override void FinishSave()
        {
            if (IsNew)
            {
                ID = m_DocumentLibraryFolderEntity.DocumentLibraryFolderId;
                CachedEntities.Instance.CacheEntity(EntityType.DocumentLibraryFolderEntity, m_DocumentLibraryFolderId, CurrentObject); // add this item to the cache
                m_DocumentLibraryFolderEntity = null; // force a reference from the cache by CurrentObject call
                AddToWhatsNew();
            }
            
            CachedEntities.Instance.SignalUpdate(EntityType.DocumentLibraryFolderEntity, m_DocumentLibraryFolderId);    // Notify Cache to update itself and web farm
            [b]UpdateGraphRelations(IsNew);[/b]
            
            PostSave(IsNew);
            m_IsNew = false;
            ParticipatesInTransaction = false;
        }

And here is the transaction class meant for the wrapper classes. It was quite clever I thought how to avoid doing post processing work (the FinishSave method above) until the commit was done

public class Transaction
    {
        private List<BALBase> Entities;
        private LW.CNet.Data.DataCore.HelperClasses.Transaction Trans;

        public Transaction(IsolationLevel IsolationLevel, string Name)
        {
            Trans = new LW.CNet.Data.DataCore.HelperClasses.Transaction(IsolationLevel, Name);
            Entities = new List<BALBase>();
        }

        public void Add(BALBase Entity)
        {
            Trans.Add(Entity.TransactionalElement);
            Entities.Add(Entity);
            Entity.ParticipatesInTransaction = true;
        }

        public void Commit()
        {
            Trans.Commit();

            for (int f = 0; f < Entities.Count; f++)
            {
                Entities[f].FinishSave();
            }
        }

        public void Rollback()
        {
            Trans.Rollback();
            for (int f = 0; f < Entities.Count; f++)
                Entities[f].ParticipatesInTransaction = false;
        }

    }

Obviously much about the caching is left out. Such as "how do you query?" This really becomes moot for our needs and what filtering of collection data there is takes place on the server. Or "how do you save graphs", again moot for our needs, the real graphs are only for the cache manager to maintain, everything else is virtually constructed graphs from cached data; IOW a wrapper entity has no hard list to other related entities, related data is queried from the cache manager's true instance and get's the real entities relevant child collection.

I'll try to answer Qs as I can.

Lex
User
Posts: 2
Joined: 15-Nov-2006
# Posted on: 24-Nov-2006 12:01:48   

Thanks for sharing. It's gonna take some time to understand all this :-)