1

This is how we implement a generic Save() service in WCF for our EF entities. A TT does the work for us. Even though we don't have any problems with it, I hate to assume this is the best approach (even if it might be). You guys seem pretty darn bright and helpful, so I thought I would pose the question:

Is there a better way?

[OperationContract]
public User SaveUser(User entity)
{
    bool _IsDeleted = false;
    using (DatabaseEntities _Context = new DatabaseEntities())
    {
        switch (entity.ChangeTracker.State)
        {
            case ObjectState.Deleted:
                //delete
                _IsDeleted = true;
                _Context.Users.Attach(entity);
                _Context.DeleteObject(entity);
                break;
            default:
                //everything else
                _Context.Users.ApplyChanges(entity);
                break;
        }
        // now, to the database
        try
        {
            // try to save changes, which may cause a conflict.
            _Context.SaveChanges(System.Data.Objects.SaveOptions.None);
        }
        catch (System.Data.OptimisticConcurrencyException)
        {
            // resolve the concurrency conflict by refreshing 
            _Context.Refresh(System.Data.Objects.RefreshMode.ClientWins, entity);
            // Save changes.
            _Context.SaveChanges();
        }
    }
    // return
    if (_IsDeleted)
        return null;
    entity.AcceptChanges();
    return entity;
}
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
Jerry Nixon
  • 31,313
  • 14
  • 117
  • 233

2 Answers2

6

Why are you doing this with Self tracking entities? What was wrong with this:

[OperationContract]
public User SaveUser(User entity)
{
    bool isDeleted = false;
    using (DatabaseEntities context = new DatabaseEntities())
    {
        isDeleted = entity.ChangeTracker.State == ObjectState.Deleted;
        context.Users.ApplyChanges(entity); // It deletes entities marked for deletion as well

        try
        {
            // no need to postpone accepting changes, they will not be accepted if exception happens
            context.SaveChanges(); 
        }
        catch (System.Data.OptimisticConcurrencyException)
        {
            context.Refresh(System.Data.Objects.RefreshMode.ClientWins, entity);
            context.SaveChanges();
        }
    }

    return isDeleted ? null : entity;
}
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • 1
    Hi, how do you know if entity state is deleted or not? How you track that on the client. Because I supposed that you send the Entity from WCF to the Client but the entity doesnt have any property to set the state isnt it? Thanks a lot – VAAA Nov 04 '13 at 14:45
2

If I'm not mistaken, people typically don't expose their Entity Framework objects directly in a WCF service. Entity Framework is typically thought of as a data-access layer, and WCF is more of a front-end layer, so they are put on different tiers.

A Data-Transfer Object (DTO) is used in the WCF methods. This is typically a POCO which doesn't have any state-tracking on it whatsoever. The DTO is then mapped to an Entity either by hand or via a framework like AutoMapper.

Typically clients should know whether they are "adding" or "updating" an object, and I would personally prefer these to be two separate operations on the service interface. Also, I would definitely require them to use a separate method for deleting an object. However, if you absolutely need a generic "Save", you should be able to tell whether the object you've been given is "new" or not based on the presence (or absence) of a primary key value.

A lot of the code can be put into a generic utility. For example, supposing your T4 template produces attributes on the key values of your entities, you could automatically determine whether the key values are present and perform an Insert/Update accordingly. Also, the try SaveChanges catch retry block you're using--while probably unnecessary--could easily be put into a simple utility method to be more DRY.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • We don't do it the way you describe and it works awesome. Self tracking entities in a shared assembly across tiers is seamless. – Jerry Nixon Apr 13 '12 at 15:25
  • @JerryNixon: So you send your WCF client Entities directly, and the client modifies the entities and sends them back to the service? That's pretty cool that it works so seamlessly. Thanks for sharing. – StriplingWarrior Apr 13 '12 at 16:17
  • Yeah! not only that, but I get full fidelity in the entities because they are external and not proxies on the client. AKA they can have methods! – Jerry Nixon May 15 '12 at 18:42
  • @StriplingWarrior I have this approach where WCF sends a POCO class (actually a EF entity) to the client. This POCO doesnt have any state-tracking, etc. How can I know if the entity was deleted or changed in the WCF service when saving? Is there any sample I can look at? Thanks for sharing.+ – VAAA Nov 04 '13 at 14:51
  • @VAAA: I'm a little confused by your question. Presumably if you're using a POCO with no state tracking, you're not deleting it by calling `.Delete()` on it, right? So deleting it should be a matter of invoking a Delete method on the WCF service and passing the object (or its ID) to the method as a parameter. Because it's not tracking state, you cannot know whether the object has been "changed" either--you can only compare it with what's in your data store and see if it's not the same. – StriplingWarrior Nov 04 '13 at 16:54
  • You are totally right, that's the way. Maybe I didn't explain my self. :) Any web resource on how this POCO + EF + WCF Service should work in the best way? – VAAA Nov 04 '13 at 17:20
  • @VAAA: I don't know of any. Best I can do is point you to Google. – StriplingWarrior Nov 05 '13 at 00:31