Thursday, May 10, 2012

Clean Repository pattern in EF4


  1. The View
    1. only post the key fields and changed fields
  2. The controller action
    1. transform the post into a DTO and ensure key is set
    2. validate changed fields
  3. The domain layer
    1. accept the DTO and the list of changed properties
    2. validate all business logic
  4. The Service layer
    1. accept the DTO
    2. update ONLY the changed columns.
      1. without fetching a new copy of the object
Goal completed.

1. In the View every input has a wrapper div marked with `data-original="@Model.oldValue"`
OnClick for the save button(s): javascript marks all input fields that have changed to `disabled='disabled'`

2. In the controller action the params required are simply the key fields. I new-up a DTO, and TryUpdateModel on it. For some reason I had to manually set the key property.

3. Domain layer is a simple pass through for now, no business logic is present yet.

4. Service Layer takes in the DTO (as an interface), maps it into a concrete type, and iterates the list of changed property names, setting the ObjectStateManager to modified for each property.

1. For Instance:
 <div class="editor-label">
            @Html.LabelFor(model => model.Threshold3)
        </div>
        <div class="editor-field" data-original="@Model.Threshold3">
            @Html.EditorFor(model => model.Threshold3)
            @Html.ValidationMessageFor(model => model.Threshold3)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>


 and
<script type="text/javascript">
    $(document).ready(function () {
        $('input[type="submit"]').on('click', function () {
            $('.editor-field').each(function (index,e) {
                var input = $('input', e);
                
                var old = $(e).attr('data-original');
                if (typeof (old) !== 'undefined' && old !== false && old == $(input).val()) {
                    input.attr('disabled', 'disabled');
                }
                
            });
        });
    });
    </script>

More after the break...
2.
  public virtual ActionResult DealActivityEdit(long id)
    {
      ViewBag.Edit = true;
      if (Request.HttpMethod == "POST")
        return DealActivityEditPost(id);
      var model = dealActivity.Find(f => f.ID == id).FirstOrDefault();
      return View(MVC.EF.Views.DealActivityCreate, model);
    }

     ActionResult DealActivityEditPost(long id)
    {
       var posted=new DealActivityDTO();
       TryUpdateModel(posted);
       posted.ID = id;
         var keys=Request.Form.Keys.Cast<string>().Distinct();
      
       this.dealActivity.Update(posted, keys);
      //this.dealActivityUpdates.Update(Tuple.Create(model.ID),model);
      return Content("Would have edited deal activity " + id.ToString());
    }

3. Pass through not yet created, I went straight to the service layer for testing =/

4.
/// <summary>
      /// Update
      /// </summary>
      IDealActivity Core.Behaviors.IRepository<IDealActivity>.Update(IDealActivity item, IEnumerable<string> propertyList)
      {
        var concrete=Core.SyntaxSugar.Helpers.MapUsingSerializer<DealActivity>(item);
        this.DealActivities.Attach(concrete);
        foreach(var p in propertyList)
          ObjectStateManager.GetObjectStateEntry(concrete).SetModifiedProperty(p);
        this.SaveChanges();
        return concrete;
      }
Thoughts:
 The post needs to be Ajax so that if the post fails, the user doesn't lose anything, and we don't have to return a new copy of the item from the database for editing. Also, after the post to the client is done, we're re-inflating to a DTO, which is a problem for serialization and transfer to other layers, unless we use JSON for that then all the empties don't have to be included.


Other Helpers:
  • Ninject
  • T4 all over
    • T4Mvc
    • Ef's .tt file modifications
    • My own T4 DTO generator which copies attributes down to the DTO from the interface
  • Static Reflection
  • Query Interception
    • Rewrites the queries from the domain into EF friendly types
    • For Example:
      /// <summary>
            /// Class to enable business logic to query a source without knowing implementation of the source
            /// </summary>
              [System.CodeDom.Compiler.GeneratedCode("T4","1.0.0.0")]
              IQueryable<IDealActivity> Core.Behaviors.IRepository<IDealActivity>.Query()
            {
                  var generalExpWriter=new ExpressionWriter(new DebugTextWriter());
                  var provider=InterceptingProvider.Intercept(new ExpressionWriter(new DebugTextWriter("Underlying IDealActivity")),
                      this.DealActivities.Select<DealActivity,IDealActivity>(c=>c),
                      new ExpressionWriter(new DebugTextWriter()),_TypeChangeVisitor ,new ExpressionWriter(new DebugTextWriter()));
                  
                  return provider;
            }
      
  • public interface IRepository<T> where T : class
      {
        IQueryable<T> Query();
        IEnumerable<T> Find(Expression<Func<T, bool>> where);
        T Create(T item);
        void Delete(T item);
        T Update(T item, IEnumerable<string> modifiedList);
    
      }
    
    

No comments:

Post a Comment