- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
IConcurrencyPredicateFactory
Bug fixed on June 17th: Save() and Save(false) didn't consult a set ConcurrencyPredicateFactory instance, only during recursion.
I think you ran into that one. The latest runtime libs available at the website will fix this for you.
JimFoye wrote:
I have the June 28 release. This fix is not in that?
Hmm, should be indeed. If you request the value RuntimeLibraryVersion.Build (is a static struct in the ORMSupport classes) in your code, what does it say? 06282004 ?
How do I get that? Here's today's fun with the command window:
?SD.LLBLGen.Pro.ORMSupportClasses.RuntimeLibraryVersion.Version
null
?SD.LLBLGen.Pro.ORMSupportClasses.RuntimeLibraryVersion
error: identifier 'SD' out of scope
Incredibly, I find myself longing for C again where I can just friggin' look at something and see what's there!
VS.NET issues are indeed hard to fight against sometimes.
You can do this:
string buildnumber = RuntimeLibraryVersion.Build;
// and then write it somewhere, via message box, or via teh debugger, or via Console.WriteLine("Buildnr: {0}", buildnumber);
you can also directly write RuntimeLibraryVersion.Build of course. It's a const string in a struct.
Well, I'll be damned! It's "06192004".
The date on the designer window is June 28. I must be out of synch.
I will download the latest hotfixes and recheck the version number and then retest.
Maybe I will put a check for this at runtime in the app, too.
Thanks.
Can't reproduce it here. With this code: OrderEntity newOrderECT = new OrderEntity(); newOrderECT.ConcurrencyPredicateFactoryToUse = new OrderCPF(); newOrderECT.EntityValidatorToUse = new OrderEntityValidator(); newOrderECT.CustomerID = "CHOPS"; newOrderECT.EmployeeID=6;
// set the rest of the mandatory order fields newOrderECT.OrderDate = DateTime.Now; newOrderECT.RequiredDate = DateTime.Now.AddDays(+1); newOrderECT.Save();
I end up in the routine which retrieves the predicates for save. It won't use the predicate of course, as it won't use it for inserts, but it does retrieve it.
The bug was in Save() itself in EntityBase:
public bool Save()
{
if(_fields == null)
{
// nothing to save
return true;
}
if(!_fields.IsDirty)
{
// not changed
return true;
}
return Save(GetConcurrencyPredicate(ConcurrencyPredicateType.Save), false);
}
where the Save() call didn't consult the predicatefactory set.
Well, yes of course I can set the ConcurrencyPredicateFactoryToUse property in the code that instantiates the object, but the whole point is I'm trying to push this down in the DAL somewhere so that the caller only needs to check the return value of Save() and not worry about setting up the concurrency stuff.
The code that I tried that you gave me previously doesn't work, and I'm not sure how your code here changes that, because now you're doing something different .
So here's what I've done to get this working. Now that I see that there are two overrides to UpdateEntity() that I can use as a gateway to the database, ...
1) I've given up on using ConcurrencyPredicateFactoryToUse because I can't get it to work from within DAL, only when setting in calling code. I like the name, but I can't use it!
2) I've given up on using static instance of predicate to save on code and memory size. I am just happy to have this work at all.
3) I'll just create the predicate in these two functions.
Here are my new overrides for UpdateEntity(). The second one may be unnecessary for this application, but I did it anyway for the sake of completeness. I tested both of these in the debugger and also looking at the SQL submitted using SQL Profiler, and everything seems to work fine.
protected override bool UpdateEntity()
{
IPredicateExpression ip = new PredicateExpression();
ip.Add(new FieldCompareValuePredicate(this.Fields["RV"], ComparisonOperator.Equal));
return base.UpdateEntity(ip);
}
protected override bool UpdateEntity(IPredicate updateRestriction)
{
IPredicateExpression ip = new PredicateExpression();
ip.Add(new FieldCompareValuePredicate(this.Fields["RV"], ComparisonOperator.Equal));
ip.AddWithAnd(updateRestriction);
return base.UpdateEntity(ip);
}
Does this look ok to you? If so I will copy to my other 30 entity classes.
Here is my calling code. This seems to work ok for handling an actual concurrency conflict (your suggestions for improvement invited - I edited out user messages, try/catch, etc.)
if (!dalAddressType.Save())
{
if (!dalAddressType.Refetch())
{
// record's been deleted
}
else
{
// record's been changed
}
}
}
}
and like I said, I also tested with calling code already setting a predicate
IPredicateExpression ip = new PredicateExpression();
ip.Add(OEL.FactoryClasses.PredicateFactory.CompareValue(AddressTypeFieldIndex.AddressType, ComparisonOperator.NotEqual, "meaningless"));
if (!dalAddressType.Save(ip))
and that works fine.
Jim
JimFoye wrote:
Well, yes of course I can set the ConcurrencyPredicateFactoryToUse property in the code that instantiates the object, but the whole point is I'm trying to push this down in the DAL somewhere so that the caller only needs to check the return value of Save() and not worry about setting up the concurrency stuff.
Jim, this was testcode, to test the Save() issue you had. It didn't mimic what you wanted to do nor was it my intention to show you have to do things, as that was already clear .
The code that I tried that you gave me previously doesn't work, and I'm not sure how your code here changes that, because now you're doing something different
.
The code posted last was to illustrate what I did to reproduce the Save() issue you had so you know what I've done to reproduce your error.
So here's what I've done to get this working. Now that I see that there are two overrides to UpdateEntity() that I can use as a gateway to the database, ...
1) I've given up on using ConcurrencyPredicateFactoryToUse because I can't get it to work from within DAL, only when setting in calling code. I like the name, but I can't use it!
Your code should work. Especially the Save() method SHOULD access the factory set, if not it's a bug. There was a bug in the code which was fixed on june 17th, which simply didn't access the factory if you called Save(), which what you did, that's why I mentioned that
2) I've given up on using static instance of predicate to save on code and memory size. I am just happy to have this work at all.
Memory for static instances is reserved just once, and code is allocated once anyway (and shared among each class, data is per object, this is the CLR way of working with classes/data)
3) I'll just create the predicate in these two functions.
Here are my new overrides for UpdateEntity(). The second one may be unnecessary for this application, but I did it anyway for the sake of completeness. I tested both of these in the debugger and also looking at the SQL submitted using SQL Profiler, and everything seems to work fine.
protected override bool UpdateEntity() { IPredicateExpression ip = new PredicateExpression(); ip.Add(new FieldCompareValuePredicate(this.Fields["RV"], ComparisonOperator.Equal)); return base.UpdateEntity(ip); } protected override bool UpdateEntity(IPredicate updateRestriction) { IPredicateExpression ip = new PredicateExpression(); ip.Add(new FieldCompareValuePredicate(this.Fields["RV"], ComparisonOperator.Equal)); ip.AddWithAnd(updateRestriction); return base.UpdateEntity(ip); }
Does this look ok to you? If so I will copy to my other 30 entity classes.
They look ok . Perhaps you can make the first UpdateEntity() just call the other one passing null and place a null check in the other one. Saves you the presence of a predicate creation statement.
You also don't need to copy them over, you can generate them with the include template as I've explained earlier in my long reply in this thread . It will save you a lot of work.
Still I'm curious why your factory class doesn't work. I'll re-investigate the complete thread to get a clear picture and will check if I can see some things which might have gone wrong.
Here is my calling code. This seems to work ok for handling an actual concurrency conflict (your suggestions for improvement invited - I edited out user messages, try/catch, etc.)
if (!dalAddressType.Save()) { if (!dalAddressType.Refetch()) { // record's been deleted } else { // record's been changed } } } }
and like I said, I also tested with calling code already setting a predicate
IPredicateExpression ip = new PredicateExpression(); ip.Add(OEL.FactoryClasses.PredicateFactory.CompareValue(AddressTypeFieldIndex.AddressType, ComparisonOperator.NotEqual, "meaningless")); if (!dalAddressType.Save(ip))
and that works fine.
Looks ok
I'll re-read the thread to see if I can find the issue why the factory class didn't work.
I'll re-read the thread to see if I can find the issue why the factory class didn't work.
OK, and thanks for all the help. Let me know if you need/want anything else from me on this subject.
I realize I can do the template alteration, I'm just a bit jittery at the moment about what happens to my code when I regenerate. This is due to confusion about the naming of the templates (some of which may or may not overwrite derived entity classes - not sure), and seeing contents of ConstantsEnums.cs getting smoked (separate thread, and no I haven't reproduced). So I prefer just to spend a few minutes manually copying everywhere. I'm in a time crunch, too, due to all the time spent on this (I imagine you are, too ).
I have suggested previously what I think would be a neat way for Gen Pro to automatically handle concurrency, I retract that and suggest instead:
The designer knows when it reads the schema that there's a timestamp field. So it should just have a flag for that field or its entity, and if this is set, then code gets generated like I have here to always create or add the timestamp comparison predicate on saves. And of course a project preference to turn them all on or off by default. This should stay out of the way of other concurrency schemes and make life easy for folks like me. But now that I've got the trick, I'm ok for now. Until then, maybe someone else will see this thread and make use of it.
Thanks again.
And I did implement your suggestion on the code:
protected override bool UpdateEntity()
{
return UpdateEntity(null);
}
protected override bool UpdateEntity(IPredicate updateRestriction)
{
IPredicateExpression ConcurrencyPredicate = new PredicateExpression();
ConcurrencyPredicate.Add(new FieldCompareValuePredicate(this.Fields["RV"], ComparisonOperator.Equal));
if (updateRestriction != null)
ConcurrencyPredicate.AddWithAnd(updateRestriction);
return base.UpdateEntity(ConcurrencyPredicate);
}
JimFoye wrote:
I'll re-read the thread to see if I can find the issue why the factory class didn't work.
OK, and thanks for all the help. Let me know if you need/want anything else from me on this subject.
Found it. Setting the predicate factory in the UpdateEntity() override is too late. Let me elaborate: when you call Save(), you call this code:
public bool Save()
{
if(_fields == null)
{
// nothing to save
return true;
}
if(!_fields.IsDirty)
{
// not changed
return true;
}
return Save(GetConcurrencyPredicate(ConcurrencyPredicateType.Save), false);
}
That overload of Save called at the bottom, first calls Validate, then does some recursive save setup if applicable and after that checks what to call: insert or update and calls these routines. Needless to say, setting the factory in the UpdateEntity method is too late, as the factory member variable is already consulted.
I recognize that this is a disadvantage and will make changes to the initclass methods of the entities to include a custom template you can alter to inject extra code into the initclass methods, for example setting a predicate factory.
I realize I can do the template alteration, I'm just a bit jittery at the moment about what happens to my code when I regenerate.
You shouldn't be . I understand why you are not that keen on regenerating code, but re-generating code should be fine, as the source for your generated code is the llblgen pro project file, which you can use to re-generate the code if necessary. There are no known issues with the generation process and if they appear, we try to fix these a.s.a.p. (within the same day if possible).
This is due to confusion about the naming of the templates (some of which may or may not overwrite derived entity classes - not sure), and seeing contents of ConstantsEnums.cs getting smoked (separate thread, and no I haven't reproduced). So I prefer just to spend a few minutes manually copying everywhere. I'm in a time crunch, too, due to all the time spent on this (I imagine you are, too
).
It's perhaps wise to take a peek into the SDK . There is a clear separation between generator configuration files and template set config files. Which files are generated, if they're overwritten if they exist etc. etc. is controlled by the generator config files (the ones you select in the top drop down box). Which templates to use for a given action (for example generate code for an entity class) is defined by the templateset config files, you select in the lower drop down box. These bind ID's to files. The ID's are used by the generator config files, so a task there for example uses SD_EntityBaseTemplate for generating code for the entitybase classes. Which actual template file is used then is determined from the template set config file you've chosen. For example teh C# templateset config file binds to the ID SD_EntityBaseTemplate the file "C#\entityBase.template". So the file "<llblgen pro installation folder>\Drivers<db used>\Templates\C#\entityBase.template" is used as a template for the task to generate entitybase classes. So when you bind a different file to that SD_EntityBaseTemplate ID by altering the csharptemplateset config file, that other file is used.
I have suggested previously what I think would be a neat way for Gen Pro to automatically handle concurrency, I retract that and suggest instead:
The designer knows when it reads the schema that there's a timestamp field. So it should just have a flag for that field or its entity, and if this is set, then code gets generated like I have here to always create or add the timestamp comparison predicate on saves. And of course a project preference to turn them all on or off by default. This should stay out of the way of other concurrency schemes and make life easy for folks like me. But now that I've got the trick, I'm ok for now. Until then, maybe someone else will see this thread and make use of it.
That would work, if other databases than SqlServer also use a timestamp field like SqlServer does. Unfortunately, that's not the case . At first, adding a generic form of concurrency control seems appealing, and a great timesaver. But after a while you'll see that it is only applicable in some situations, which then makes the generic solution get in your way. That's why we opted for a generic approach using the concurrencypredicatefactory.
We didn't opt for generating these up front, as feedback from customers told us that more classes being generated up front would bloath their projects even more. That's why the interface approach was chosen.
Thanks for the explanation about templates and such, which I don't pretend to understand right now , but I will save this, because I am actually very serious about using an ORM product for all my future projects, so if I keep using yours, I definitely want to get a much better understanding of the internals (well, maybe you don't even think of this stuff as internals
). I just have the immediate constraint of finishing this project on budget right now.
I briefly looked at about 12 or 15 ORM products before starting this. The two other ones that really got my attention were Object Broker (?) which seemed to be in eternal beta and whose license so far as I could tell didn't even let me use it in any kind of real application, and Pragmatier. The latter is of course much more expensive, but the killer was their designer is written in VB 6 and absolutely stinks. I couldn't even connect to my database without help from them.
According to Mats (who says hello, by the way) they are coming out with a new version with a much improved GUI and new licensing scheme. So I will keep an eye on them.
This is my long-winded way of saying, I want to settle on one product and then get to know that product inside and out so I don't have to post so many inane questions on the tech support forum .
Years ago I used to write some articles in magazines reviewing products, I wonder if anyone has done a really comprehensive review of ORM products? To really do a good review of so many products would take a lot of time. Bu these magazines don't pay crap (which is why I stopped doing it, on the other hand, it's not bad marketing for my business).
Speaking of licensing schemes, yours is so generous I hope you are making enough money to stay in business
JimFoye wrote:
Thanks for the explanation about templates and such, which I don't pretend to understand right now
, but I will save this, because I am actually very serious about using an ORM product for all my future projects, so if I keep using yours, I definitely want to get a much better understanding of the internals (well, maybe you don't even think of this stuff as internals
). I just have the immediate constraint of finishing this project on budget right now.
Don't worry Finishing projects is I think what matters most. If you need help with explanations of what the tool does and how it works because you don't have enough time due to project constraints, feel free to ask, we're here to help
I briefly looked at about 12 or 15 ORM products before starting this. The two other ones that really got my attention were Object Broker (?) which seemed to be in eternal beta and whose license so far as I could tell didn't even let me use it in any kind of real application, and Pragmatier. The latter is of course much more expensive, but the killer was their designer is written in VB 6 and absolutely stinks. I couldn't even connect to my database without help from them.
EntityBroker . I met Thomas Tomiczek at Teched, (we already had email contact) and he's very ok. He told me EntityBroker is more of a product used internally for projects than a product sold as a product per se, perhaps that's why it is indeed always in beta (although it can do a lot). Pragmatier has bad sideeffects, also because it requires .net 1.0 ODBC to be installed and other nasty things. It also generates ALL the code, which is IMHO not a wise decision.
According to Mats (who says hello, by the way) they are coming out with a new version with a much improved GUI and new licensing scheme. So I will keep an eye on them.
Yeah Mats and I have something to work out as he surprised me a bit with the NPresence lib and his designer to work with that lib (in a bad way surprised me), but I think we'll work it out, he's ok
This is my long-winded way of saying, I want to settle on one product and then get to know that product inside and out so I don't have to post so many inane questions on the tech support forum
.
All starting is hard, and if you have a question, just ask
Years ago I used to write some articles in magazines reviewing products, I wonder if anyone has done a really comprehensive review of ORM products? To really do a good review of so many products would take a lot of time. Bu these magazines don't pay crap (which is why I stopped doing it, on the other hand, it's not bad marketing for my business).
I haven't seen reviews of O/R mapping tools other than the few single tool reviews here and there. O/R mapping is really just a niche at the moment, which is sad, but that's reality. However with more and more people getting fed up with writing db code by hand, more and more people are looking for tools to help them. So I expect later this year/early next year an increase of interest in O/R mapper tools.
Speaking of licensing schemes, yours is so generous I hope you are making enough money to stay in business
![]()
Oh don't worry about that, that's going very ok .
We opted for a low price to get a large market momentum. Marketshare is key now, as the market for O/R mapper tools is very young and within a year more and more players are going to enter this market. When you have a large marketshare by then, you're first the choice of many people.
It also generates ALL the code, which is IMHO not a wise decision.
Yeah, I noticed that.
Ok, I just got off my lazy a**, as we like to say in America, and made an article proposal to VB.NET Advisor for a review of O/R mappers, either just yours or else a survey of all of them. Let's see what happens.
Do you recommend any other magazines that would be good for this if they don't bite? I'd prefer to use C# for examples, but there's no C# Advisor (I know there are C# magazines, but this is the group I published articles for in the past).
We probably want to take this discussion offline. Send me an email if you want to discuss further, I'll send you a copy of the proposal I just wrote.
Jim
JimFoye wrote:
It also generates ALL the code, which is IMHO not a wise decision.
Yeah, I noticed that.
Ok, I just got off my lazy a**, as we like to say in America, and made an article proposal to VB.NET Advisor for a review of O/R mappers, either just yours or else a survey of all of them. Let's see what happens.
Sounds great!
Do you recommend any other magazines that would be good for this if they don't bite? I'd prefer to use C# for examples, but there's no C# Advisor (I know there are C# magazines, but this is the group I published articles for in the past).
Not really, I only read on-line stuff, not paper magazines. There is a .NET magazine from MS, perhaps they want to publish it as well...
We probably want to take this discussion offline. Send me an email if you want to discuss further, I'll send you a copy of the proposal I just wrote. Jim
Done