Mathieu
Mathieu

Reputation: 661

EF4.1 DBContext: Insert/update in one Save() function without identity PK

I have a streets table, which has a combo of two string columns acting as the PK, being postalcode and streetcode.

With EF4.1 and DBContext, I'd like to write a single "Save" method than takes a street (coming in in an unattached state), checks if it already exists in the database. If it does, it issues an UPDATE, and if it doesn't, it issues an INSERT.

FYI, the application that saves these streets, is reading them from a textfile and saves them (there a few tens of thousands of these "streetlines" in that file).

What I've come up with for now is:

    public void Save(Street street)
    {
        var existingStreet = (
                                from s in streetContext.Streets
                                where s.PostalCode.Equals(street.PostalCode)
                                && s.StreetCode.Equals(street.StreetCode)
                                select s
                             ).FirstOrDefault();

        if (existingStreet != null)
            this.streetContext.Entry(street).State = System.Data.EntityState.Modified;
        else
            this.streetContext.Entry(street).State = System.Data.EntityState.Added;

        this.streetContext.SaveChanges();
    }

Is this good practice ? How about performance here ? Cause for every street it first does a roundtrip to the db to see if it exists.

Wouldn't it be better performance-wise to try to insert the street (state = added), and catch any PK violations ? In the catch block, I can then change the state to modified and call SaveChanges() again. Or would that not be a good practice ?

Any suggestions ?

Thanks

Upvotes: 0

Views: 3277

Answers (3)

Mathieu
Mathieu

Reputation: 661

Thanks for the replies but none were really satisfying, so I did some more research and rewrote the method like this, which satisfies my need.

ps: renamed the method to Import() because I find that a more appropriate name for a method that is used for (bulk) importing entities from an outside source (like a textfile in my case)

ps2: I know it's not really best practice to catch an exception and let it die silently by not doing anything with it, but I don't have the need to do anything with it in my particular case. It just serves as a method to find out that the row already exists in the database.

public void Import(Street street)
{
        try
        {
            this.streetContext.Entry(street).State = System.Data.EntityState.Added;

            this.streetContext.SaveChanges();
        }
        catch (System.Data.Entity.Infrastructure.DbUpdateException dbUex)
        {
            this.streetContext.Entry(street).State = System.Data.EntityState.Modified;

            this.streetContext.SaveChanges();
        }
        finally
        {
            ((IObjectContextAdapter)this.streetContext).ObjectContext.Detach(street);
        }
} 

Upvotes: 1

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364379

Your code must result in exception if street already exists in the database because you will load it from the context and after that you will try to attach another instance with the same primary key to the same context instance.

If you really have to do this use this code instead:

public void Save(Street street)
{
    string postalCode = street.PostalCode;
    string streetCode = steet.StreetCode;
    bool existingStreet = streetContext.Streets.Any(s =>
                            s.PostalCode == postalCode
                            && s.StreetCode = steetCode);

    if (existingStreet)
        streetContext.Entry(street).State = System.Data.EntityState.Modified;
    else
        streetContext.Entry(street).State = System.Data.EntityState.Added;

    streetContext.SaveChanges();
}

Anyway it is still not reliable solution in highly concurrent system because other thread can insert the same street between you check and subsequent insert.

Upvotes: 0

jrb
jrb

Reputation: 1728

Select all the streets then make a for each loop that compares and change states. After the loop is done call saveChanges. This way you only make a few calls to the db instead of several thousends

Upvotes: 1

Related Questions