Create new entity on Fetch failure

Posts   
 
    
jovball
User
Posts: 435
Joined: 23-Jan-2005
# Posted on: 02-Sep-2016 04:11:11   

I must have blinders on but I've tried various ways to do this and they're not working.

I'd like to return a blank new entity when FetchEntity fails. No matter what I've tried, the fields are always showing ORMOutOfSyncException.

For example, I'm passing in this object:


     var customer = new CustomerEntity("ALFKI");

If the FetchEntity method below fails, I'd like to have the a new entity of the same type as above. I should be able to proceed with setting field values and saving it.


public bool FetchEntity(IEntity2 entityToFetch, IPrefetchPath2 prefetch = null, ExcludeIncludeFieldsList fieldsList = null)
        {
            using (var dataAdapter = GetConfiguredDataAdapter())
            {
                if (dataAdapter.FetchEntity(entityToFetch, prefetch, null, fieldsList))
                {
                    return true;
                }
                else
                {
                    //create a blank new entity here
                    var entityTypeName = entityToFetch.GetType().Name;
                    var typeOfEntity = (EntityType)Enum.Parse(typeof(EntityType), entityTypeName);
                    entityToFetch = GeneralEntityFactory.Create(typeOfEntity);  
                    entityToFetch.IsNew = true;
                    entityToFetch.Fields.State = EntityState.New;
                    return false;
                }
            }
        }

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 02-Sep-2016 07:23:17   

In what part exactly you are getting the ORMOutOfSyncException? Your method isn't returning the entity. What does that method do exactly?

David Elizondo | LLBLGen Support Team
jovball
User
Posts: 435
Joined: 23-Jan-2005
# Posted on: 02-Sep-2016 13:53:20   

I thought the code was clear enough. The bool return shows success or failure for the fetch but the method will populate the entity that is passed in. It's just a wrapper around the DbSpecific FetchEntity method.

Try this slightly modified version as a repro.


//same method as shown above
public bool FetchEntity(IEntity2 entityToFetch, IPrefetchPath2 prefetch = null, ExcludeIncludeFieldsList fieldsList = null)
        {
            using (var dataAdapter = new DataAccessAdapter())
            {
                if (dataAdapter.FetchEntity(entityToFetch, prefetch, null, fieldsList))
                {
                    return true;
                }
                else
                {
                    //create a blank new entity here
                    var entityTypeName = entityToFetch.GetType().Name;
                    var typeOfEntity = (EntityType)Enum.Parse(typeof(EntityType), entityTypeName);
                    entityToFetch = GeneralEntityFactory.Create(typeOfEntity); 
                    entityToFetch.IsNew = true;
                    entityToFetch.Fields.State = EntityState.New;
                    return false;
                }
            }
        }

var customer = new CustomerEntity("ALFKI");
//this will work correctly because the entity will be fetched from the database and populated
if(FetchEntity(customer))
{
  Console.WriteLine("Customer: {0} - {1}", customer.CustomerId, customer.CompanyName);
}


customer = new CustomerEntity("ZZZZZ");
if(!FetchEntity(customer))
{
   //OK, it didn't exist so I want to save a new one
   // attempting to work with the customer object will fail here
   customer.CompanyName = "Big Deal Inc.";
}





jovball
User
Posts: 435
Joined: 23-Jan-2005
# Posted on: 02-Sep-2016 22:21:20   

Solved! Perhaps I should go read my old code or old threads first. rage

I just need to clone the entity fields before doing the fetch. If the fetch fails, I replace the fields with the cloned fields. Here's the code.


/// <summary>
/// Fetches a single entity along with any specified prefetch path data and Excluded/Included fields.
/// If the fetch does not succeed, entityToFetch will be returned as the field values cloned from the input.
/// In that situation, properties will also be set to IsNew = true and Fields.State = EntityState.New.
/// </summary>
/// <param name="entityToFetch">The entity to fetch.</param>
/// <param name="prefetch">The Prefetch Path.</param>
/// <param name="fieldsList">The fields list.</param>
/// <returns></returns>
public bool FetchEntity(IEntity2 entityToFetch, IPrefetchPath2 prefetch = null, ExcludeIncludeFieldsList fieldsList = null)
{
    var entityFieldsClone = entityToFetch.Fields.Clone();
    using (var dataAdapter = GetConfiguredDataAdapter())
    {
        if (dataAdapter.FetchEntity(entityToFetch, prefetch, null, fieldsList))
        {
            return true;
        }
        else
        {
            //restore the entity with the same field values and set it to new.
            entityToFetch.IsNew = true;
            entityToFetch.Fields = entityFieldsClone;
            entityToFetch.Fields.State = EntityState.New;
            return false;
        }
    }
}

Usage:


var customer = new CustomerEntity("BDEAL");
if(!FetchEntity(customer))
{
 //OK, it didn't exist so now we'll populate it and then save it.
 customer.CompanyName = "Big Deal Inc.";
 customer.City = "Richmond";
 customer.City = "USA";
 //a normal LLBLGen save now does the insert.
// e.g. dataAdapter.SaveEntity(customer);
}

Two points might be worth making here.

1) I wasn't sure what kind of impact the Fields.Clone() method would have. I had to move it before the FetchEntity so I'm doing it even though the normal result will not use the cloned fields. On my (mediocre, government-issued) notebook, it takes < 500ms for 1 million repetitions. I can look somewhere else for things to optimize. smile

2) I didn't state the business case for this. We have several places where this will be useful to us. We currently have a process that runs against flat files provided by another government agency. For each record from that flat file, we will either update an existing record in the database (FetchEntity succeeds) or insert a new one (FetchEntity fails). Of course, there is additional processing along with that.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 03-Sep-2016 04:00:47   

jovball wrote:


//same method as shown above
public bool FetchEntity(IEntity2 entityToFetch, IPrefetchPath2 prefetch = null, ExcludeIncludeFieldsList fieldsList = null)
        {
            using (var dataAdapter = new DataAccessAdapter())
            {
                if (dataAdapter.FetchEntity(entityToFetch, prefetch, null, fieldsList))
                {
                    return true;
                }
                else
                {
                    //create a blank new entity here
                    var entityTypeName = entityToFetch.GetType().Name;
                    var typeOfEntity = (EntityType)Enum.Parse(typeof(EntityType), entityTypeName);
                    entityToFetch = GeneralEntityFactory.Create(typeOfEntity); 
                    entityToFetch.IsNew = true;
                    entityToFetch.Fields.State = EntityState.New;
                    return false;
                }
            }
        }

I didn't catch your code, that's why I asked some questions. IMHO, these lines are unnecesary:

entityToFetch.IsNew = true;
entityToFetch.Fields.State = EntityState.New;

... since you are already initializing the entity from scratch here:

entityToFetch = GeneralEntityFactory.Create(typeOfEntity); 

I think the real problem is that the entity.Fields.State didn't persist in your method's input field. So I think this will work if you change this:

public bool FetchEntity(IEntity2 entityToFetch, IPrefetchPath2 prefetch = null, ExcludeIncludeFieldsList fieldsList = null)

into this:

public bool FetchEntity(ref IEntity2 entityToFetch, IPrefetchPath2 prefetch = null, ExcludeIncludeFieldsList fieldsList = null)

You will have to change the way you use it:

IEntity2 customer = new CustomerEntity("ALFKI");
//this will work correctly because the entity will be fetched from the database and populated
if (FetchEntity(ref customer))
{
    Console.WriteLine("Customer: {0} - {1}", ((CustomerEntity)customer).CustomerId, ((CustomerEntity)customer).ClientName);
}


customer = new CustomerEntity("ZZZZZ");
if (!FetchEntity(ref customer))
{
    //OK, it didn't exist so I want to save a new one
    // attempting to work with the customer object will fail here
    var isNew = customer.IsNew;
    var name = ((CustomerEntity)customer).ClientName;

    ((CustomerEntity)customer).ClientName = "Big Deal Inc.";
}
David Elizondo | LLBLGen Support Team
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 03-Sep-2016 04:14:32   

jovball wrote:

1) I wasn't sure what kind of impact the Fields.Clone() method would have. I had to move it before the FetchEntity so I'm doing it even though the normal result will not use the cloned fields. On my (mediocre, government-issued) notebook, it takes < 500ms for 1 million repetitions. I can look somewhere else for things to optimize. smile

About this, cloning the fields is not the problem. But I think you don't want to clone the fields per-se since you are initializing the entity so maybe you just want to return an empty entity.

jovball wrote:

2) I didn't state the business case for this. We have several places where this will be useful to us. We currently have a process that runs against flat files provided by another government agency. For each record from that flat file, we will either update an existing record in the database (FetchEntity succeeds) or insert a new one (FetchEntity fails). Of course, there is additional processing along with that.

From what I see here, the method is not needed, as this piece of code is so simple:

var customer = new CustomerEntity();
dataAdapter.FetchEntity(customer, prefetch, null, fieldsList)
if (customer.IsNew)
{
    customer = new CustomerEntity();
}

But that is just my point of view. Indeed your case could be more complicated and you want to reuse the fetch logic.

David Elizondo | LLBLGen Support Team