- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Null exception when using adaptor.DeleteEntityCollection
Joined: 02-Oct-2008
I am using This IConcurrencyPredicateFactory to set transactions to check a timestamp field
public class GenericConcurrencyFilterFactory : IConcurrencyPredicateFactory
{
public IPredicateExpression CreatePredicate(
ConcurrencyPredicateType predicateTypeToCreate,
object containingEntity)
{
var entity = (EntityBase2)containingEntity;
var timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
IPredicateExpression expression = new PredicateExpression();
switch (predicateTypeToCreate)
{
case ConcurrencyPredicateType.Save:
case ConcurrencyPredicateType.Delete:
expression.Add(timestampField == entity.Fields["Concurrency"].DbValue);
break;
}
return expression;
}
}
All failry standard
I am fetching an entity collection of user dictionary words which I persist in memory and out to disk (its easier to use a standard file for the spelling checker control that I am using)
I do this
public CustomUserDictionary(string connectionString, string userAccount)
{
// use Isolated Storage not the executing assembly location
//
//string appPath = Path.GetDirectoryName
// (Assembly.GetAssembly(typeof(CustomUserDictionary)).Location);
string appPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
UserDictionary = Path.Combine(appPath, WardenIIDictionaries.USER_DICTIONARY_NAME);
using (var adaptor = new DataAccessAdapter(connectionString))
{
try
{
connection_String = connectionString;
user_Account = userAccount;
wordList = new EntityCollection(new DictionaryEntityFactory());
deletedWords = new EntityCollection(new DictionaryEntityFactory());
wordList.ConcurrencyPredicateFactoryToUse = new GenericConcurrencyFilterFactory();
deletedWords.ConcurrencyPredicateFactoryToUse = new GenericConcurrencyFilterFactory();
userWordList = new RelationPredicateBucket();
userWordList.PredicateExpression.Add(DictionaryFields.CreatedBy == userAccount);
userWordList.PredicateExpression.Add(DictionaryFields.Merged == false);
wordList.RemovedEntitiesTracker = deletedWords;
adaptor.FetchEntityCollection(wordList, userWordList);
PersistToLocalStorage();
userDictionarySaved = false;
}
catch (Exception e)
{
throw new Exception("Cannot acquire users dictionary for " + userAccount, e);
}
}
}
A user can add and remove words from their user dictionary.
If the delete one or more words, then I update the wordlist entity collection
public void RemoveDeletedWords(ArrayList userList) { // removes words deleted from the user dictionary bool removedAWord; do { removedAWord = false; foreach (DictionaryEntity wordToCheck in wordList) { if (wordToCheck.Merged) continue; //ignore merged words if (WordMissing(userList, wordToCheck.Word)) { wordList.Remove(wordToCheck); removedAWord = true; break; } } } while (removedAWord); }
When I am saving changes made to the user dictionary which can include additions and deletions I use this
public bool SaveUserDictionary()
{
// ensure this is called only once
if(userDictionarySaved) return true;
ArrayList fileWordList = new ArrayList();
LoadWordList(fileWordList);
AddNewWords(fileWordList);
RemoveDeletedWords(fileWordList);
using (var adaptor = new DataAccessAdapter(connection_String))
{
adaptor.StartTransaction(IsolationLevel.ReadCommitted, "Dictionary");
try
{
if(wordList.Count > 0)
adaptor.SaveEntityCollection(wordList);
if (deletedWords.Count > 0)
adaptor.DeleteEntityCollection(deletedWords);
adaptor.Commit();
userDictionarySaved = true;
return true;
}
catch (ORMConcurrencyException)
{
// someone else has updated the same record as us.
// or some one has deleted one that we have deleted or updated.
return false;
}
catch (Exception e)
{
// We have an error, reverse any changes we have made
adaptor.Rollback();
throw new Exception("User dictionary update error for " + user_Account, e);
}
}
}
I have removed a word, it is moved into the deletedWords collection but I get a null exception on adaptor.DeleteEntityCollection(deletedWords); in DataAccessAdapter.cs here
case PredicateType.FieldCompareValuePredicate: FieldCompareValuePredicate compareValuePredicate = (FieldCompareValuePredicate)currentPredicate; if(compareValuePredicate.PersistenceInfo==null) { compareValuePredicate.PersistenceInfo = GetFieldPersistenceInfo((IEntityField2)compareValuePredicate.FieldCore); } break;
Any ideas what is going wrong. I have used similar logic elsewhere and I don't remember having any problems before
Heres details of the exception
System.NullReferenceException was unhandled by user code
Message="Object reference not set to an instance of an object."
Source="SD.LLBLGen.Pro.ORMSupportClasses.NET20"
StackTrace:
at SD.LLBLGen.Pro.ORMSupportClasses.EntityFieldCore.get_ContainingObjectName()
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetFieldPersistenceInfo(IEntityField2 field)
at PathWest.DataAccess.Dictionary.DatabaseSpecific.DataAccessAdapter.InsertPersistenceInfoObjects(IPredicateExpression expression) in C:\ProjectsUncontrolled\MessageOfTheDay\Dictionary DAL\DatabaseSpecific\DataAccessAdapter.cs:line 378
at PathWest.DataAccess.Dictionary.DatabaseSpecific.DataAccessAdapter.InsertPersistenceInfoObjects(IPredicateExpression expression) in C:\ProjectsUncontrolled\MessageOfTheDay\Dictionary DAL\DatabaseSpecific\DataAccessAdapter.cs:line 350
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.DeleteEntity(IEntity2 entityToDelete, IPredicateExpression deleteRestriction)
at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
InnerException:
Runtime lib build nr etc. ?
it looks like a field doesn't get its persistence info injected so we need the rtl lib to know if it's an old build or not. This error could also be caused by user error in your code, but let's first check whether the latest runtime build fixes the issue or not?
Joined: 02-Oct-2008
Otis wrote:
Runtime lib build nr etc. ?
it looks like a field doesn't get its persistence info injected so we need the rtl lib to know if it's an old build or not. This error could also be caused by user error in your code, but let's first check whether the latest runtime build fixes the issue or not?
My understanding is that I am using ther latest build.
According to the about form - Version 2.6 Final, Realeased on October 9th, 2009.
Joined: 02-Oct-2008
Here is a trace of whats happening
- Method Exit: DataAccessAdapterBase.ExecuteMultiRowRetrievalQuery
- Method Exit: DataAccessAdapterBase.FetchEntityCollectionInternal(7)
- Method Enter: DataAccessAdapterBase.CloseConnection
- Method Exit: DataAccessAdapterBase.CloseConnection
- Method Exit: DataAccessAdapterBase.FetchEntityCollection(
- MainWorkItem OnActivated
- MessageView OnButtonToolClick
- Before: OnClose - SmartPart=Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace
- After: OnClose - SmartPart=Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace
- Before: OnClose - SmartPart=Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace
- After: OnClose - SmartPart=Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace
- Before: OnClose - SmartPart=Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace
- After: OnClose - SmartPart=Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace
- MainWorkItem OnActivated
- MainWorkItem OnTearingDown
- Method Enter: DataAccessAdapterBase.StartTransaction
- Transaction name: Dictionary. Isolation level: ReadCommitted. Method Enter: DataAccessAdapterBase.OpenConnection
- New connection created.
-
Connection physically opened. Method Exit: DataAccessAdapterBase.OpenConnection Method Exit: DataAccessAdapterBase.StartTransaction Method Enter: DataAccessAdapterBase.SaveEntityCollection(3) Active Entity Collection Description: EntityCollection: PathWest.DataAccess.Dictionary.HelperClasses.EntityCollection. Will contain entities of type: DictionaryEntity
- Method Enter: UnitOfWork2.Commit(2)
- Transaction to use: Dictionary. AutoCommit: False
- Handling Pre Insert CallBacks.
-
Handle Inserts. Method Enter: DataAccessAdapterBase.PersistQueue Persistence action info: Action: Insert. Queue length: 1 Current persisted entity info: Entity: PathWest.DataAccess.Dictionary.EntityClasses.DictionaryEntity. ObjectID: ffcf5aaa-588b-4dd1-9a9e-015451e63ff4 PrimaryKey field: DictionaryId. Type: System.Int32. Value: <undefined value> Method Enter: DataAccessAdapterBase.ExecuteActionQuery Method Enter: DataAccessAdapterBase.OpenConnection Method Exit: DataAccessAdapterBase.OpenConnection Method Enter: Query.ReflectOutputValuesInRelatedFields
-
Syncing field DictionaryId with parameter @DictionaryId. Method Exit: Query.ReflectOutputValuesInRelatedFields Executed Sql Query: Query: INSERT INTO [WardenII].[Global].[Dictionary] ([Word], [Merged]) VALUES (@Word, @Merged);SELECT @DictionaryId=SCOPE_IDENTITY() Parameter: @DictionaryId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: 276. Parameter: @Word : String. Length: 50. Precision: 0. Scale: 0. Direction: Input. Value: "Chucken". Parameter: @Merged : Boolean. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: False.
- Method Exit: DataAccessAdapterBase.ExecuteActionQuery
- PersistQueue method result: queuePersisted result: True
- Method Exit: DataAccessAdapterBase.PersistQueue
- Handling Pre Update CallBacks.
- Handle Updates. Method Enter: DataAccessAdapterBase.PersistQueue Persistence action info: Action: Update. Queue length: 0 PersistQueue method result: queuePersisted result: True Method Exit: DataAccessAdapterBase.PersistQueue
- Handling UpdateEntitiesDirectly Calls.
- Handling Pre Delete CallBacks.
- Handle Deletes.
- Handling Post Delete CallBacks.
-
Handling DeleteEntitiesDirectly Calls. Method Enter: UnitOfWork2.Reset Method Exit: UnitOfWork2.Reset Method Exit: UnitOfWork2.Commit(2) Method Exit: DataAccessAdapterBase.SaveEntityCollection(3) Method Enter: DataAccessAdapterBase.DeleteEntityCollection Active Entity Collection Description: EntityCollection: PathWest.DataAccess.Dictionary.HelperClasses.EntityCollection. Will contain entities of type: DictionaryEntity
- Method Enter: UnitOfWork2.Commit(2)
- Transaction to use: Dictionary. AutoCommit: False
- Handling Pre Insert CallBacks.
- Handle Inserts. Method Enter: DataAccessAdapterBase.PersistQueue Persistence action info: Action: Insert. Queue length: 0 PersistQueue method result: queuePersisted result: True Method Exit: DataAccessAdapterBase.PersistQueue
- Handling Pre Update CallBacks.
- Handle Updates. Method Enter: DataAccessAdapterBase.PersistQueue Persistence action info: Action: Update. Queue length: 0 PersistQueue method result: queuePersisted result: True Method Exit: DataAccessAdapterBase.PersistQueue
- Handling UpdateEntitiesDirectly Calls.
- Handling Pre Delete CallBacks.
-
Handle Deletes. Method Enter: DataAccessAdapterBase.DeleteEntity(2) Active Entity Description: Entity: PathWest.DataAccess.Dictionary.EntityClasses.DictionaryEntity. ObjectID: f5e7f54f-24cd-4c3e-8a9a-8d1bc9410317 PrimaryKey field: DictionaryId. Type: System.Int32. Value: 272
A first chance exception of type 'System.NullReferenceException' occurred in SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll
Joined: 02-Oct-2008
Here is a definition of the table
CREATE TABLE [Global].[Dictionary](
[DictionaryId] [int] IDENTITY(1,1) NOT NULL,
[Word] [nvarchar](50) NOT NULL,
[Merged] [bit] NOT NULL,
[Concurrency] [timestamp] NOT NULL,
[CreatedBy] [nvarchar](30) NOT NULL,
[DateCreated] [date] NOT NULL,
[TimeCreated] [datetime] NOT NULL,
[UpdatedBy] [nvarchar](30) NULL,
[DateUpdated] [date] NULL,
[TimeUpdated] [datetime] NULL,
CONSTRAINT [PK_Dictionary] PRIMARY KEY CLUSTERED
(
[DictionaryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Every table has these same 7 fields
[Concurrency] [timestamp] NOT NULL,
[CreatedBy] [nvarchar](30) NOT NULL,
[DateCreated] [date] NOT NULL,
[TimeCreated] [datetime] NOT NULL,
[UpdatedBy] [nvarchar](30) NULL,
[DateUpdated] [date] NULL,
[TimeUpdated] [datetime] NULL,
Joined: 02-Oct-2008
I remembered I had changed the collation of [Word] nvarchar NOT NULL to make it case senstive.
I refreshed the catalog, then ran the generator and replaced the class libraries.
VS2008 reloaded them. Cleaned and Rebuilt the solution. Ran the application
used test words as per:- added a word - Chucken for insertion deleted the word - HokeyPokey
The logic only inserts new words and deletes existing ones. The insert works fine (path of least resistance I guess). The delete does not.
I did check the deleted entities collection with the debugger, it does contain one entity so that parts working.
I might remove the ConcurrencyPredicate for the delete and see if that works.
Joined: 02-Oct-2008
Removed the concurrencypredicates and it works fine.
Kinda worries me for later on though.
An insert does not need one.
It does not matter in this instance that a dictionary entry has been deleted within another transaction but in other cases it might.
What annoys me is that I am certain I used the same logic previously (it was with an older build of LLBLGen Pro) and it worked fine.
public class GenericConcurrencyFilterFactory : IConcurrencyPredicateFactory { public IPredicateExpression CreatePredicate( ConcurrencyPredicateType predicateTypeToCreate, object containingEntity) { var entity = (EntityBase2)containingEntity;
var timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency"); IPredicateExpression expression = new PredicateExpression(); switch (predicateTypeToCreate) { case ConcurrencyPredicateType.Save: case ConcurrencyPredicateType.Delete: expression.Add(timestampField == entity.Fields["Concurrency"].DbValue); break; } return expression; } }
Please post the DependencyInjectionInfo attribute decorating the above class. Try explicit type assignment, if it may help:
EntityBase2entity = (EntityBase2)containingEntity;
EntityField2timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
If not then maybe you can do something like:
expression.Add((EntityField2)entity.Fields["Concurrency"] == entity.Fields["Concurrency"].DbValue);
Joined: 02-Oct-2008
Here is the complete source (except the using statements)
namespace PathWest.Common.BusinessEntities { [DependencyInjectionInfo(typeof(IEntity2), "GenericConcurrencyFilterFactory", ContextType = DependencyInjectionContextType.NewInstancePerTarget)] [Serializable] public class GenericConcurrencyFilterFactory : IConcurrencyPredicateFactory { public IPredicateExpression CreatePredicate( ConcurrencyPredicateType predicateTypeToCreate, object containingEntity) { var entity = (EntityBase2)containingEntity;
var timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
IPredicateExpression expression = new PredicateExpression();
switch (predicateTypeToCreate)
{
case ConcurrencyPredicateType.Save:
case ConcurrencyPredicateType.Delete:
expression.Add(timestampField == entity.Fields["Concurrency"].DbValue);
break;
}
return expression;
}
}
}
I did not think the attributes were really necessay unless automatic dependancy injection was going to be used.
Joined: 02-Oct-2008
Try explicit type assignment, if it may help: Code:
EntityBase2entity = (EntityBase2)containingEntity; EntityField2timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
If not then maybe you can do something like: Code:
expression.Add((EntityField2)entity.Fields["Concurrency"] == entity.Fields["Concurrency"].DbValue);
Where exactly am I supposed to try this. In the ConcurrencyPredicateFactory or where I instantiate the entity collections?
Sorry I am a bit confused.
I am sure that logic has worked in the past.
The null exception caught me by surprise.
In the concurrency Predicate class.
[DependencyInjectionInfo(typeof(IEntity2), "GenericConcurrencyFilterFactory", ContextType = DependencyInjectionContextType.NewInstancePerTarget)]
Also you should try replacing the IEntity2 with EntityBase2 in the DependencyInjectionInfo attribute.
Joined: 02-Oct-2008
Just had a look at my ConcurrencyPredicateFactory (Actually more less cloned from one of your examples).
Code:
EntityBase2entity = (EntityBase2)containingEntity; EntityField2timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
I am already doing this
public class GenericConcurrencyFilterFactory : IConcurrencyPredicateFactory { public IPredicateExpression CreatePredicate( ConcurrencyPredicateType predicateTypeToCreate, object containingEntity) { var entity = (EntityBase2)containingEntity;
var timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
IPredicateExpression expression = new PredicateExpression();
switch (predicateTypeToCreate)
{
case ConcurrencyPredicateType.Save:
case ConcurrencyPredicateType.Delete:
expression.Add(timestampField == entity.Fields["Concurrency"].DbValue);
break;
}
return expression;
}
}
I am not sure how to implement the following
expression.Add((EntityField2)entity.Fields["Concurrency"] == entity.Fields["Concurrency"].DbValue);
Any pointers?
Joined: 02-Oct-2008
Must be blind as well as stupid
Am also doing this
expression.Add((EntityField2)entity.Fields["Concurrency"] == entity.Fields["Concurrency"].DbValue);
public class GenericConcurrencyFilterFactory : IConcurrencyPredicateFactory
{
public IPredicateExpression CreatePredicate(
ConcurrencyPredicateType predicateTypeToCreate,
object containingEntity)
{
var entity = (EntityBase2)containingEntity;
var timestampField = (EntityField2)EntityFieldFactory.Create(entity.GetType().Name, "Concurrency");
IPredicateExpression expression = new PredicateExpression();
switch (predicateTypeToCreate)
{
case ConcurrencyPredicateType.Save:
case ConcurrencyPredicateType.Delete:
expression.Add(timestampField == entity.Fields["Concurrency"].DbValue);
break;
}
return expression;
}
}
Joined: 02-Oct-2008
Ok changed the attribute to
[DependencyInjectionInfo(typeof(EntityBase2), "GenericConcurrencyFilterFactory",
ContextType = DependencyInjectionContextType.NewInstancePerTarget)]
[Serializable]
public class GenericConcurrencyFilterFactory : IConcurrencyPredicateFactory
{
Still getting a null exception at the red line
case PredicateType.FieldCompareValuePredicate:
FieldCompareValuePredicate compareValuePredicate = (FieldCompareValuePredicate)currentPredicate;
if(compareValuePredicate.PersistenceInfo==null)
{
** compareValuePredicate.PersistenceInfo = GetFieldPersistenceInfo((IEntityField2)compareValuePredicate.FieldCore);** } break;
Joined: 02-Oct-2008
I could use the deleted entity collection with a grid to see what it contains.
Maybe, its a only small possibility, but the could the RemovedEntitiesTracker collection not be getting a complete copy of each entity that is deleted (or removed).
I will have a look tomorrow. Its time to go home for me.
Joined: 02-Oct-2008
I created a new form just to hold two grids to display the two entity collections.
See attachment
The top grid displays the Dictionary entity collection after two new entities have been added and two entities have been removed.
The bottom is a grid that displays the contents of the RemovedEntitiesTracker entity collection. This contains the two entites removed from Dictionary Entity Collection.
From what I can observe this is behaving 100% as expected.
Its only when the statement in red is executed that the null exception occurs
** if (deletedWords.Count > 0) adaptor.DeleteEntityCollection(deletedWords);**
using (var adaptor = new DataAccessAdapter(connection_String))
{
adaptor.StartTransaction(IsolationLevel.ReadCommitted, "Dictionary");
try
{
if(wordList.Count > 0)
adaptor.SaveEntityCollection(wordList);
if (deletedWords.Count > 0)
adaptor.DeleteEntityCollection(deletedWords);
adaptor.Commit();
userDictionarySaved = true;
return true;
}
catch (ORMConcurrencyException)
{
// someone else has updated the same record as us.
// or some one has deleted one that we have deleted or updated.
return false;
}
catch (Exception e)
{
// We have an error, reverse any changes we have made
adaptor.Rollback();
throw new Exception("User dictionary update error for " + user_Account, e);
}
}
**
As far as I can tell it should work but it is not.**
What else can I check?
Filename | File size | Added on | Approval |
---|---|---|---|
entities.zip | 16,018 | 01-Dec-2009 02:29.47 | Approved |
Joined: 02-Oct-2008
Ah multiple expletives and many nasty bad words.
I thought I would try something smart but found it cost me a lot of time.
Virtually every application we will be writing will be using the dictionary code that I have written in a separate class library.
So I thought, why not generate a set of LLBLGen Pro classes just for the tables and stored procs for handling dictionary stuff.
Then for database access to handle other tables, views etc create a second set of LLBLGen Pro classes.
Give each set of projects (generic and database specific) a different name space.
Looked ok, the Build was ok, it ran ok except for when I updated the dictionary tables and used a common concurrencypredicate factory. It then bombed with a null exception.
I discovered the reason why when I was looking at the code and said why not move all my entity collections to generic collections while I am waiting for an update on the forum. While I was doing this I got an ambiguous reference warning (I think it was with EntityCollection<DictionaryEntity>).
That stopped me in my tracks and it sudenly dawned on me that my idea was possibly not quite appropriate.
I excluded the special LLBLGen Pro class library projects built for my dictionary. Ran the LLBLGen Pro generator and included the required tables and stored procs for the dictionary. Cleaned up the code and rebuilt everything.
It worked just fine.
**Lesson learn. **If I want to do this I will need a more sophisticated approach like creating a separate domain assembly and then load it into my application domain when I need to use it.
I am sorry if you put too much effort into helping me in solving this self inflicted debacle.
Joined: 02-Oct-2008
Thanks for the suggestion.
I had started doing that but there was so much re-work to do and with me the chances of making a mistake would be fairly high. Any such mistake would result in another difficult to track down error.
It was easier to collapse everything into the two class libraries and let the IDE tell me where I needed to fix things.