I was just looking into a way to keep my business logic all in one place, and to be able to extend entities and I wonder what people think about using extension methods to do this.
extension methods would allow me to do this:
dim doc as DocumentEntity = documentManager.FetchByPrimaryKey(1) If doc.IsInDocumentGroup(5) 'do something End If
rather than this:
dim doc as DocumentEntity = DocumentManager.FetchByPrimaryKey(1) If DocumentManager.IsInDocumentGroup(doc,5) 'do something End If
Adding a method to an instance of an entity. Still you are limited to a static/shared method, just like in a manager class, however the syntax when calling is a bit clearer.
The reason i want to be able to do this, rather than adding to entities the dbgeneric project is quite similar to the reason we need IoC for validation of entities, this is business logic and you don't want it with the entities, and sometimes you want to make DB calls within your business logic.
Now here is the question that I have where is the line drawn. Should this only be used to extend the fields, or should it be used to extend actions like save and delete? Or are extension methods bad because they will limit some other functionality that I am not thinking about.
I create a manager class per entity. I auto generate:
Fetch FetchUsingPrimaryKey FetchUsingUniqueConstraint FillFieldsMappedOnRelations 'this allows you to do this documentManager.FillDocumentGroups(document) 'I call these postfetches (the lazy cousin of the prefetch) Save SaveCollectin Delete DeleteCollection
and then I define any custom methods that I need like
UserManager.Logout(user as userEntity) UserManager.HasRight(user as userEntity, rightId as integer)
I think a lot of these could also be changed to extension methods so you could use:
user.Save user.Delete If user.Rights is Nothing 'postfetch / kindof lazyload user.FillRights() End If user.HasRight(18) user.Logout
additionally if the extension attribute is applied directly to your shared/static manager functions you can call things both ways.
Anybody have any ideas on this?
In my opinion, your approach make sense when using SelfServicing pattern, and you should be able to do that using the SelfServicingTwoClasses preset.
However, using Adapter pattern, I wouldn't recommend putting the functionality in entity extended objects.
Are you planning this for SelfServicing?
I am using adapter too, that is the part that is kind of cool.
It is basically just a short cut to your manager class, and all of your business logic is still where it is supposed to be. If you are passing the entities somewhere that doesn't have access to your manager dll the extended methods are not there. Additionally you only expose methods that someone accessing your manager classes could access anyhow.
So if you don't want them to delete an entity, delete is not there. and it is also not in your manager class.
so you can call
User.Save(True) UserManager.Save(User, True)
both calls use the exact same function in your manager class
you could also do this for fetch methods, like the self-service, but i think that syntax is a bit confusing.
dim u as new userEntity() u.fetchByPrimaryKey(5)
to me this is clearer to someone reading the code.
dim u as new userEntity() u= UserManager.fetchByPrimaryKey(5)
I have attached one of my manager classes
here is some code showing with and without exntesions
'WITH EXTENSIONS Dim document = DocumentManager.FetchUsingPrimaryKey(1) 'this fills the related Company Entity (postfetch) document.FillCompany() document.DocumentDescription = "new description" & document.Company.CompanyName document.Save(True) Dim documents = DocumentManager.FetchByPart(New PartEntity(5), True, True) For Each doc In documents Response.Write("<br>" & doc.SummaryHtml) 'this is nice doc.DocumentDescription = "change" Next docs.SaveAll(False) 'WITHOUT EXTENSIONS Dim document = DocumentManager.FetchUsingPrimaryKey(1) 'this fills the related Company Entity (postfetch) DocumentManager.FillCompany(document) document.DocumentDescription = "new description" & document.Company.CompanyName DocumentManager.Save(document, True) Dim documents = DocumentManager.FetchByPart(New PartEntity(5), True, True) For Each doc In documents Response.Write("<br>" & DocumentManager.SummaryHtml(doc)) doc.DocumentDescription = "change" Next DocumentManager.SaveAll(docs, False)
|Filename||File size||Added on||Approval|
Ok Gabe.. I need pick your brain if you allow me to
If I understand you right, you would extend the entities with methods that would have been in a manager class to start with. But because you are writing the shared (static) methods to be called in the extended syntax, the UI developer can only call these methods in the extended syntax not in the manager-class syntax. In a nutshell, you have turned the adapter architecture into self-service but with the added benefit that behavior (methods) live in a separate assembly (the BL).
Now I can imagine the argument would be, which syntax is cleaner. For me, I must put this to test in a real project to get "the feeling" of the syntax.
The examples i listed above can be mix and match. once a method is set as an extension, it may be called either way. which ever you prefer.
The once the UI developer references the business layer the entities will all get the new methods, and they will be able to call the manager classes as well.
so with my current manager classes I can do this
dim user as UserEntity = UserManager.FetchByPrimaryKey(5) response.write user.FullName() 'this is an extension my user class only has first and last response.write UserManger.FullName(user) 'exact same call as above user.FirstName="Otis" user.Save(true) 'calls the extension method user.FirstName="Frans" UserManager.Save(user,true) 'again calls the save method
I think that adding these extensions gives the UI developers (me) an easy way to see things they can do with the entity, or entity collection. It doesn't take away anything, and seems to be a nice shortcut.
I have added this to my manager templates and I am using it on my current project.
So far I like it!
Also be sure to check out the manager class I attached in my last post, The forum doesn't make it easy to see there is an attachment if you don't know where to look.
I like your approach to the manager classes. Is there any chance you could share your template to generate them so that I/we might be able to use it as a base?
Thanks a lot, Patrick pub # patrickwolf@net
I would also like to get these templates.
Can you provide them?
Pat & Fishy
Sorry for the slow reply, I haven't been working on the LLBL stuff too much lately. However I will share my manager template file.
I really want to rework a lot of this, there are a lot of functions that are generated that are there from a previous version of my template. I would like to yard a lot of this stuff out. Having LINQ makes most of this stuff unnecessary since now you can get data so easily.
I would like to say that the Fill methods are one thing I really like about my template. It is very simple but gives you the option to do "on demand lazy Loading" Like This:
If OrderHeader.Account is Nothing Then OrderHeader.FillAccount End If
Also i changed my managers from a classes to a modules so i could extend methods without having to write a wrapper function in an extension module. I feel like this may have been a mistake. I would like the ability to have a manager base to handle some of the common stuff. As it stands this code is duplicated, which i feel causes a bit of code bloat and slow compile time.
The biggest problem I have is when to allow access. For example I don't want to allow OrderManager.Save(orderEntity) out of the box, I want to be able to control this. To solve this problem I renamed my Save to SaveEntity and made it private. In order to call this in the UI , i Define a new function in my usercode area called Save, and it calls SaveEntity.
When i get a chance I am going to move to a shared/static manager base class, and have each manager as a shared class. Then create a companion Extension Module for each Manager. I'm not exactly sure but here are some of the options.
ManagerBase <-- OrderManager + OrderManagerExtensions this model the custom/modified code would go directly in my OrderManagerClass within a defined code region.
the other option i am thinking may be to do:
ManagerBase <-- OrderManagerBase <-- OrderManager + OrderManagerExtensions
the OrderManagerBase would be generated, and the OrderManager woudl be just user code. This would allow for the ability to allow or disallow calls to private / public base methods.
or I may just move out a lot of the stuff to a Manager Helper class and add wrapper calls to the save delete and methods that I want to make public.
I am sure there are more options i haven't had much time to think about it yet. I would appreciate any design ideas if you have them
|Filename||File size||Added on||Approval|
Thanks for replying. I have made a little progress by taking what you had as an example. Here is the code which I have not yet created a template for:
Imports MedfordSchoolDistrict.Sis Imports MedfordSchoolDistrict.Sis.EntityClasses Imports MedfordSchoolDistrict.Sis.FactoryClasses Imports MedfordSchoolDistrict.Sis.HelperClasses Imports MedfordSchoolDistrict.Sis.DatabaseSpecific Imports MedfordSchoolDistrict.Sis.Linq.LinqMetaData Imports SD.LLBLGen.Pro.ORMSupportClasses Imports SD.LLBLGen.Pro.LinqSupportClasses Imports MedfordSchoolDistrict.SisBl.LLBLGenProQueryExtender Public Class StudentMstrManager Public Enum PrefetchItemEnum None Attendance StudActive StudData End Enum Private Shared Function CreatePathEdges(ByVal ParamArray prefetchItem() As PrefetchItemEnum) As IPathEdge() Dim wp As New List(Of IPathEdge) For Each pa In prefetchItem Select Case pa Case PrefetchItemEnum.Attendance : wp.Add(New PathEdge(Of AttendanceEntity)(StudentMstrEntity.PrefetchPathAttendance)) Case PrefetchItemEnum.StudActive : wp.Add(New PathEdge(Of StudActiveEntity)(StudentMstrEntity.PrefetchPathStudActive)) Case PrefetchItemEnum.StudData : wp.Add(New PathEdge(Of StudDataEntity)(StudentMstrEntity.PrefetchPathStudData)) End Select Next Return wp.ToArray End Function ''' <summary> ''' Fetches the entity by the primary key along with specified ''' Prefetch Items. ''' </summary> ''' <param name="studentId"></param> ''' <param name="prefetchItem"></param> ''' <returns></returns> ''' <remarks></remarks> Public Shared Function FetchEntityByPrimaryKey(ByVal studentId As String, ByVal ParamArray prefetchItem() As PrefetchItemEnum) As StudentMstrEntity Dim meta As New Linq.LinqMetaData(New DataAccessAdapter) Dim q = From d In meta.StudentMstr Where d.StudentId = studentId Select d Dim c = q.WithPath(CreatePathEdges(prefetchItem)).ToEntityCollection(Of StudentMstrEntity)() If c.Count > 0 Then Return c(0) Return Nothing End Function Public Shared Function FetchCollection(ByVal prefetchItem As PrefetchItemEnum) As EntityCollection(Of StudentMstrEntity) Dim meta As New Linq.LinqMetaData(New DataAccessAdapter) Dim q = From d In meta.StudentMstr Select d Select Case prefetchItem Case PrefetchItemEnum.StudData q = q.WithPath(New PathEdge(Of StudDataEntity)(StudentMstrEntity.PrefetchPathStudData)) End Select Return q.ToEntityCollection(Of StudentMstrEntity)() End Function End Class
I want to add additional Fetches (Unique Contraints, Predicates) but have not done so yet.
My biggest struggle right now is with Template Studio, but I am hoping that I will figure out how to use it properly by the end of the day. If you could attach your template files, that may help me finish it up. Thanks,
[Edit] I just saw your attachment and is pending approval. Thanks