Multiple DALs

Posts   
 
    
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 14-Apr-2017 17:04:52   

Self-Servicing 4.2.20160929

Can you advise on multiple DAL projects? I have a project that contains its own generated DAL. That project contains methods that are used from multiple projects. The methods take a UOW in as a parameter and they act on that unit of work. Let's refer to this as the 'shared code'. I of course have other code bases with their own DAL that use this 'shared code' and we will call that the 'main code'.

This has not been an issue until recently. We ran into a situation where we get an error for an element name missing. The 'main code' creates the UOW and passes it in and the main code contains the DocumentToSendDetailEntity, but the 'shared code' does not contain the DocumentToSendDetailEntity

Sometimes it works and sometimes it does not. The variable is the order in which operations are done. In the example we are working with now, the shared code must be called last and everything works fine. If we do AddForSave() to the UOW after the shared code has used it, we get the error below. It is like the 'context' switches for which DAL is doing the saving.


System.ArgumentException: The element name 'DocumentToSendDetailEntity' isn't known in this provider Parameter name: elementName
   at SD.LLBLGen.Pro.ORMSupportClasses.PersistenceInfoProviderBase.GetElementMappingInfo(String elementName)
   at SD.LLBLGen.Pro.ORMSupportClasses.PersistenceInfoProviderBase.CanPerformAction(String entityName, Int32 actionToCheck)
   at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.DetermineIfSaveActionIsAllowed(EntityBase entityToSave, Boolean insertActions, IPersistenceInfoProvider persistenceInfoProvider)
   at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.PersistQueue(List`1 queueToPersist, Boolean insertActions, ITransaction transactionToUse)
   at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork.HandleInserts(ITransaction transactionToUse)
   at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork.Commit(ITransaction transactionToUse, Boolean autoCommit)
   at WayneBrantley.Cqrs.Llblgen.CqrsUow.Commit(ITransaction transactionToUse, Boolean autoCommit)
   at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork.SD.LLBLGen.Pro.ORMSupportClasses.IUnitOfWorkCore.Commit(ITransactionController controller, Boolean autoCommit)


Do you have any guidance on how to best do this? What are the pitfalls? Would this be better if we were using Adapter instead?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Apr-2017 07:20:58   

Could you please share the relevant code in the process?

And yes, this is better in Adapter template: you could share the same model (DBGeneric classes) and have multiple DBSpecific projects (DALs) that you use only when needed in your main logic code.

David Elizondo | LLBLGen Support Team
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 15-Apr-2017 21:22:02   

Not really about code daelmo. Code is nothing, just simple 'addForsave()' on UOW.

Just trying to understand when you add stuff to UOW and if you are using multiple DALs how it knows which one to look for the entity.

Adapter - thanks for info.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 17-Apr-2017 10:25:51   

It needs to obtain the persistenceinfoprovider which provides the mappings. It pulls the persistenceinfoprovider from the first entity in the queue, as normally that's the same for all entities, so it's silly to do that on a per-entity basis.

You have entities of multiple DALs in one UoW. And this goes wrong: the persistenceInfoProvider from the first entity doesn't need to be the same one as the one from the second entity in the queue (e.g. as it's from another DAL).

If you want to combine multiple dals in the same transaction, use System.Transactions. What you want is not supported, as by design everything is self-contained in selfservicing and as some elements are stored in singletons / statics, they are local per namespace and can't be mixed.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 17-Apr-2017 14:59:09   

It pulls from the first entity in the queue.

That is what we needed to know to consistently get around this.

Perhaps this is the driving factor that will push us to do a big conversion to adapter...

Posts: 1251
Joined: 10-Mar-2006
# Posted on: 17-Jan-2018 17:39:18   

Would there be any way when I create a UOW if I could set the persistenceinfoprovider to use? Maybe constructor overload or some other way?

Still struggling with this causing hard to find issues

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 17-Jan-2018 17:49:51   

I haven't been in the conversation from the start. But just to understand the case and logic behind it.

Why are you using multiple DALs in the same project, not one combined DAL, especially that you are using them in the same logic (using the same UoW)?

Are you still on SelfServicing or have you moved to Adapter?

Posts: 1251
Joined: 10-Mar-2006
# Posted on: 17-Jan-2018 18:42:16   

Hey Walaa!

Still on self servicing.

I have a project A - that needs access to 5 tables lets say. That project has several methods that are shared with project B and C. So A has a DAL and B has a DAL and C has a DAL, each with the tables they need.

Project A is built and distributed via a nuget package, so it becomes a shared library.

Now the issue is if I call a method in project A using a UOW, then add a Save() from a table that is only in project B, it crashes. This is because the project A dal is the one used for saving. So I have to be very careful for order and must add the Save() first then the shared method.

Otis explained why and it is because the UOW determines the persistenceinfoprovider from the First() item added to the UOW. So, an easy solution to solve my issues seemed to be to let me pass in the persistenceinfoprovider in the UOW constructor. (Our UOW is created through DI, so it would be one change in one piece of code for us).

Hope that explains us and I wish I could find an easy path forward to adapter.

Posts: 1251
Joined: 10-Mar-2006
# Posted on: 17-Jan-2018 18:49:50   

Looked through code and I see a possible solution:

There is the DaoBase.PersistQueue static method could take an optional IPersistanceInfoProvider and if that is provided not get that from the 'first' item in the list. The UOW could have an optional constructor option that lets me pass in the PersistenceInfoProviderToUse and that is simply passed to PersistQueue.

Is it that easy?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 18-Jan-2018 06:54:52   

WayneBrantley wrote:

Looked through code and I see a possible solution:

There is the DaoBase.PersistQueue static method could take an optional IPersistanceInfoProvider and if that is provided not get that from the 'first' item in the list. The UOW could have an optional constructor option that lets me pass in the PersistenceInfoProviderToUse and that is simply passed to PersistQueue.

But what you want is to addForSave multiple items from projects A, B or C into your UOW, Right? The solution you are proposing is to pass the persistence info to the UOW's ctor which es practically the same as it works now, with the difference that now it takes the persistence info from the first entity in the queue. Am I interpreting it correctly?

David Elizondo | LLBLGen Support Team
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 18-Jan-2018 15:07:05   

The solution you are proposing is to pass the persistence info to the UOW's ctor which es practically the same as it works now, with the difference that now it takes the persistence info from the first entity in the queue. Am I interpreting it correctly?

Correct. So what that lets me do is in the 'main project' when I create a UOW I pass in the main projects DAL in the constructor...and now it does not matter what order items are added to the UOW. And it is 100% backwards compatible if you do not use the new constructor parameter.

Thanks for considering. (I think it is a really clean backwards compatible solution)

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 19-Jan-2018 12:08:00   

The thing is that you still have to be careful what to pass (which persistenceinfo provider). Not only that, but what you actually want is what adapter provides. There the persistence info is obtained from the adapter provided.

Adding this makes, sorry, no sense for us, also because user discoverability for this feature is 0: a user still has the problem of 'what do to if they run into this same problem' and then if they're luckily have to stumble upon this particular method in the referencemanual. For the design of our API it isn't useful (one would immediately wonder why the method is designed like that) and as it's then a public facing feature we have to stick with it forever. So this won't happen.

The main issue is that your code has this: A is allowed to persist stuff from B. This is done because code is shared from A to B. While that's perfectly fine in general of course, if you share code from A that allows B's stuff to be persisted by code in A, then you have to redesign the sharing part: It also could break when dal of A is on another server than B: the transaction then won't span inserts/updates on both A and B.

For _you _that might not be a problem now but a user using it would expect it to work, and that too makes it a feature that will bring only problems.

So, I'm sorry, but I answer is no. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 19-Jan-2018 14:36:40   

you are killing me. This makes it nearly impossible to have a library that uses LLBLGen and UOW with self servicing. Uggggggg. (Think of this more as a fix less of a discoverable feature)

I need something here....as it is now I have had to copy this shared code around to all projects...ugh...

It is literally like 4 lines of code on your side to make this possible...

If you still say no, I accept it...but need one more plea! smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 20-Jan-2018 09:26:05   

Sorry but, even if it's just 1 keyword, or a single bit that changes, it has to make sense for the API simple_smile . In the past we've said too many times 'OK' to small(ish) changes and we still have to live with that every day. No, this isn't going to happen, I'm sorry.

This makes it nearly impossible to have a library that uses LLBLGen and UOW with self servicing. Uggggggg. (Think of this more as a fix less of a discoverable feature)

No, it's not really doable to share code in a library if you are going to persist entities from multiple dals in that code and all use selfservicing. And that's OK, as it's not in the design of the API. With adapter this shouldn't be done either, for the simple reason I gave above: what if the dals are on multiple servers? If you want a multi-dal spanning transaction use system.transactions, and use per-dal a uow. that's how it's designed and that's how it will stay. I won't change that. I'm sorry simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 20-Jan-2018 20:40:04   

So you wont change it, ok. Help me understand the solution then.

Lets say I have this code in 10 projects...


public void SomeSharedFunction(UnitOfWork uow, ...other params)
{
//   do some stuff, 
//   Add new entity to uow, etc..
}

I want to create a library that has this shared code, package it up and put on private nuget server

I would not think I am the only person that would like to do this. We end up with all kinds of different project from windows services to websites to http rest services that need to share code...as the code is complex.

This is working today with self servicing...the one catch is you cannot have the FIRST item added to a UOW come from the DAL in the shared library....other than that works perfectly.

Let's say I am using adapter. You are saying I cannot do this in adapter either...something about running on a different server or something....no idea what you are talking about. I simply want to share code that happens to use your product across multiple projects.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 21-Jan-2018 09:52:22   

WayneBrantley wrote:

So you wont change it, ok. Help me understand the solution then.

Lets say I have this code in 10 projects...


public void SomeSharedFunction(UnitOfWork uow, ...other params)
{
//   do some stuff, 
//   Add new entity to uow, etc..
}

I want to create a library that has this shared code, package it up and put on private nuget server

This is your mistake: you want this and if it breaks it's someone else's problem to solve. But that's not going to work of course (analogy: class C isn't thread safe. My design requires C to be threadsafe, and my code only works if I'm careful. So I ask whether C can be made thread safe. If that's not done (as that's pehaps a burden/impossible) it's a problem for someone else? No, it's a problem for me, as I use C wrong).

Sharing things means you have a strict boundary and if things cross that boundary it has to work always. Your design doesn't work here, so you have to do it differently. If you want to share a common persistence system, make sure the code using it doesn't need to know about unit of works. We already discussed that in your unit of work question where you wanted a 'unit of work with other unit of works inside it'.

So don't share this api, share an api, e.g. a service (doesn't need to be a true service, can be a system injected with DI) that accepts an entity to persist and makes sure it is saved in the right DB, does transaction management etc. Your shared API is too low level for this as it requires all entities in the UoW to be in the same DAL which can't be guaranteed.

I would not think I am the only person that would like to do this. We end up with all kinds of different project from windows services to websites to http rest services that need to share code...as the code is complex.

This is working today with self servicing...the one catch is you cannot have the FIRST item added to a UOW come from the DAL in the shared library....other than that works perfectly.

Let's say I am using adapter. You are saying I cannot do this in adapter either...something about running on a different server or something....no idea what you are talking about. I simply want to share code that happens to use your product across multiple projects.

Dal A is in server ServerA. Dal B is on ServerB. If I put an entity from A and an entity from B in 1 UoW, and persist it with the adapter of A, it doesn't work either (besides that that adapter doesn't even know the persistence info for B): opening a connection to ServerA, saving the A entity, then saving the B entity doesn't work: B isn't even on that server.

So this requires something above that, something that can start 2 unit of works here one on each dal, and use a system.transactions transaction to span both persistence actions in 1 transaction.

This is the same with your situation. You want to effectively do the same as passing the persistence info of the adapter of B to the adapter of A to make the unit of work with the two entities from the example work. But that's not going to fly. That your current code 'works' if you're careful is luck. You should refactor some of its design.

So instead of sharing a low-level DAL with a low-level API, share a service type API that provides a service API for your system: it accepts work and can route it to the right place to get it done. Doesn't need to be super fancy, but at least it offers a clear boundary you won't cross.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 01-Feb-2019 23:27:52   

Well, I fear your answer here....but...

version 5.5 breaks my currently working behavior.

Given a UOW, the following are added to it through 'AddForSave'

Updated entity from DAL-A inserted entity from DAL-A inserted entity from DAL-B inserted entity from DAL-A

When the UOW is commited in 5.4, it picks DAL-A from the list (because it is first) and everything is good.

When the UOW is commited in 5.5, it picks DAL-B from the list (because it is first) and it crashes of course.

So, the ordering of the inserts were lost/changed between versions breaking my code which is based on the above thread.

Further research shows that if the entities above are just plain entities with no releationships it works the same as it always did. However, if I have an entity like this:

DALBEntity = new SomeEntity { RelatedEntity = new RelatedEntity {} }

And then do the AddForSave() in the same order as the above...this DALBEntity goes first. I think it may have to do with you moving to a 'hashset' in your graph code.

Thoughts?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Feb-2019 07:09:02   

WayneBrantley wrote:

Further research shows that if the entities above are just plain entities with no releationships it works the same as it always did. However, if I have an entity like this:

DALBEntity = new SomeEntity {
    RelatedEntity = new RelatedEntity {}
}

And then do the AddForSave() in the same order as the above...this DALBEntity goes first. I think it may have to do with you moving to a 'hashset' in your graph code.

Please elaborate more on this. How can we reproduce exactly your behavior?

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 04-Feb-2019 10:09:49   

I think I know why this is occurring: in 5.5 with batching now a core feature, the elements to insert are first sorted, then grouped per type, based on the dependency of types (Order depends on Customer, so all Order entities are grouped and inserted before all Customer entities as that always works. As they're grouped they can be batched).

This is done inside the graph sorter, which is shared among adapter and selfservicing, so even though selfservicing doesn't have batching, under the hood this still happens. In normal usage this isn't a problem.

However in your scenario it is, as you depend on the original order as they were in the queue in previous versions. As I've explained earlier in this thread, it's not a supported scenario and depending on this order is brittle and error prone (and results in unwanted side effects such as this).

It's unfortunate as the persistenceinfo object obtained is used solely for checking whether save is allowed on that entity instance, and as they're all static methods without state it's not possible to override them and change the behavior. I'm not going to re-do the whole thread again, but as said earlier, the best option for you is to create a small abstraction for the UoW in the form of a service (not a service with an HTTP endpoint, but a class which offers a service wink ) where you hand the entities to save, it will create per dal a uow and use system.transactions to control the transaction (or if you know the dals all target the same server, use a normal transaction).

Frans Bouma | Lead developer LLBLGen Pro
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 04-Feb-2019 14:12:52   

Thanks for insight and I figured this would be your answer.

A somewhat 'easy' answer is let me tell directly tell the UOW the persistanceinfo object instead of it guessing....other than that help from you I think we will just have to move to adapter!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 04-Feb-2019 15:58:25   

WayneBrantley wrote:

Thanks for insight and I figured this would be your answer.

A somewhat 'easy' answer is let me tell directly tell the UOW the persistanceinfo object instead of it guessing....other than that help from you I think we will just have to move to adapter!

It's in an awkward piece of code: static method calls static method... disappointed I looked again to see if something could be overriden to make it work but that's sadly not possible here.

Although adapter is the right choice for many situations, it won't help you here, as the adapter instance has a persistence info object attached to it, so passing entities in a UoW to it which aren't in that persistenceinfo object (which is the same problem here) will lead to the same issue.

If you really want to get rid of it, you have to adjust the DaoBase.PersistQueue method a bit, so it calls the SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.DetermineIfSaveActionIsAllowed(EntityBase entityToSave, Boolean insertActions, IPersistenceInfoProvider persistenceInfoProvider) method with the persistenceinfoprovider obtained from the DaoBase created using the active entity. This leads to slower code however (and more memory pressure).

I'd go for the small service class which creates multiple UoWs and passes a Transaction instance to all of them, commits that Transaction afterwards and everything works fine. I mean, it's not as if entity A in dal X has a relationship with entity B in dal Y and therefore a dependency, so they can be saved with 2 UoWs, one for stuff from dal X and one from dal Y and the order in which that happens isn't important.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 1251
Joined: 10-Mar-2006
# Posted on: 04-Feb-2019 16:30:20   

Although adapter is the right choice for many situations, it won't help you here, as the adapter instance has a persistence info object attached to it, so passing entities in a UoW to it which aren't in that persistenceinfo object (which is the same problem here) will lead to the same issue.

We always make sure that LOCAL DAL has every entity - all the entities it needs and any entity that 'SHARED DAL' has. So not really an issue - using that adapter would always work.

I'd go for the small service class which creates multiple UoWs and passes a Transaction instance to all of them, commits that Transaction afterwards and everything works fine. I mean, it's not as if entity A in dal X has a relationship with entity B in dal Y and therefore a dependency, so they can be saved with 2 UoWs, one for stuff from dal X and one from dal Y and the order in which that happens isn't important.

Yeah my situation is complex - due to CQRS pattern where UOW is handled by decorators (it is really bad ass smile )....but I agree a multiple UOW solution is best here.

Thanks for discussion, I need to think about what that solution would look like.