Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Monday, 24 February 2014
# Sunday, 23 February 2014
( EF6 | Humour )

Added EF to Repository and Integration tests project.

namespace Humour.Respository
{
    public class HumourContext : DbContext
    {
        public HumourContext() : base("Humour") { }
        public DbSet<Story> Stories { get; set; }
    }
}

Making the db name Humour

image
EF Uses .\SQLEXPRES by default if no connection string is specified.

However want to use repositories and not call the context directly from external code.

To implement Unit of Work (that enabled you to make multiple changes to your data and submit them to the database all at once) the repos shouldn’t created instances of HumourContext themselves in each public method.  Useful to have a factory.

Additionally it would be useful if the same HumourContext instance was used for the entire HTTP request ie can share same instance across multiple pieces of code running in the same request.  Giving opportunity to treat multiple db updates as a single unit.

Building a Context Storage Mechanism

Implement a factory class that can create and return instances of HumourContext.

  • DataContextFactory – static class with a static GetDataContext method (EF repositories project)
  • DataContextStorageFactory - (infrastructure project as not tied to specific EF implementation)
  • IDataConextStorageContainer
  • HttpDataContextStorageContainer – web related projects
  • ThreadDataContextStorageContainer – desktop apps and unit test projects
/// <summary>
/// Manages instances of the ContactManagerContext and stores them in an appropriate storage container.
/// </summary>
public static class DataContextFactory
{
    /// <summary>
    /// Clears out the current ContactManagerContext.
    /// </summary>
    public static void Clear()
    {
        var dataContextStorageContainer = DataContextStorageFactory<HumourContext>.CreateStorageContainer();
        dataContextStorageContainer.Clear();
    }

    /// <summary>
    /// Retrieves an instance of ContactManagerContext from the appropriate storage container or
    /// creates a new instance and stores that in a container.
    /// </summary>
    /// <returns>An instance of ContactManagerContext.</returns>
    public static HumourContext GetDataContext()
    {
        var dataContextStorageContainer = DataContextStorageFactory<HumourContext>.CreateStorageContainer();
        var humourContext = dataContextStorageContainer.GetDataContext();
        if (humourContext == null)
        {
            humourContext = new HumourContext();
            dataContextStorageContainer.Store(humourContext);
        }
        return humourContext;
    }
}
[TestMethod]
public void CanExecuteQueryAgainstDataContext()
{
    string randomString = Guid.NewGuid().ToString().Substring(0, 25);
    var context = DataContextFactory.GetDataContext();
    var story = new Story
    {
        Title = "test",
        Content = randomString,
        DateCreated = DateTime.Now,
        DateModified = DateTime.Now
    };

    context.Stories.Add(story);
    context.SaveChanges();

    var storyCheck = context.Stories.First(st => st.Content == randomString);
    storyCheck.Should().NotBeNull();
}

Interesting way to generate a random string!

Configuring Model’s Business Rules

  • Property level eg required field, max length etc..
  • Object level eg copare 2 fields with each other

Property Level

Attributes or fluent API.

Could use OnModelCreating in EF to put on fluent API rules.  A better way is EntityTypeConfiguration class.

/// <summary>
/// Configures the behavior for a person in the model and the database.
/// </summary>
public class StoryConfiguration : EntityTypeConfiguration<Story>
{
    /// <summary>
    /// Initializes a new instance of the PersonConfiguration class.
    /// </summary>
    public StoryConfiguration()
    {
        Property(x => x.Title).IsRequired().HasMaxLength(25);
        Property(x => x.Content).IsRequired().HasMaxLength(2048);
    }
}

and wired up through:

public class HumourContext : DbContext
{
    public HumourContext() : base("Humour") { }
    public DbSet<Story> Stories { get; set; }

    /// <summary>
    /// Configures the EF context.
    /// </summary>
    /// <param name="modelBuilder">The model builder that needs to be configured.</param>
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new StoryConfiguration());
    }
}

Object Level Validation

/// <summary>
/// Determines whether this object is valid or not.
/// </summary>
/// <param name="validationContext">Describes the context in which a validation check is performed.</param>
/// <returns>A IEnumerable of ValidationResult. The IEnumerable is empty when the object is in a valid state.</returns>
public abstract IEnumerable<ValidationResult> Validate(ValidationContext validationContext);

/// <summary>
/// Determines whether this object is valid or not.
/// </summary>
/// <returns>A IEnumerable of ValidationResult. The IEnumerable is empty when the object is in a valid state.</returns>
public IEnumerable<ValidationResult> Validate()
{
    var validationErrors = new List<ValidationResult>();
    var ctx = new ValidationContext(this, null, null);
    Validator.TryValidateObject(this, ctx, validationErrors, true);
    return validationErrors;
}

DomainEntity implements DataAnnotations.IValidatableObject.  So each entity must override Validate

Can’t do dynamic rules on attributes eg DateOfBirth can’t be later than today.

Calling validate on the Collections eg StoryCollection

/// <summary>
/// Validates this object. It validates dependencies between properties and also calls Validate on child collections;
/// </summary>
/// <param name="validationContext"></param>
/// <returns>A IEnumerable of ValidationResult. The IEnumerable is empty when the object is in a valid state.</returns>
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    yield break;
    if (StoryType == StoryType.None)
    {
        yield return new ValidationResult("Type can't be None.", new[] { "Type" });
    }

    //if (DateOfBirth < DateTime.Now.AddYears(Constants.MaxAgePerson * -1))
    //{
    //    yield return new ValidationResult("Invalid range for DateOfBirth; must be between today and 130 years ago.", new[] { "DateOfBirth" });
    //}
    //if (DateOfBirth > DateTime.Now)
    //{
    //    yield return new ValidationResult("Invalid range for DateOfBirth; must be between today and 130 years ago.", new[] { "DateOfBirth" });
    //}

    foreach (var result in Votes.Validate())
    {
        yield return result;
    }

Custom Validate method is not called as long as one of the Required or other attributes, or property-based rules set using the Fluent API are causing an objec to be invalid.

Add more tests to StoryTests class to test this validation

**problem – tests only work with attribute level validation.. if I put isRequired in StoryConfiguration tests don’t work

[TestMethod]
public void StoryWithTypeStoryTypeNoneIsInvalid()
{
    var story = new Story();
    story.StoryType = StoryType.None;
    story.Validate().Count(x => x.MemberNames.Contains("StoryType")).Should().BeGreaterThan(0);
}
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    yield break;
    if (StoryType == StoryType.None)
    {
        yield return new ValidationResult("StoryType can't be None.", new[] { "StoryType" });
    }

Dealing With Database Initialisation

  • Use a specialised version of DropCreateDatabaseIfModelChanges at dev time
  • At prod time turn off database initialiser.. ie model is not checked

Have setup so that default data is there for integration tests.  Can also override the seed method and put in own default data for tests.

/// <summary>
/// Used to initialize the HumourContext from Integration Tests
/// </summary>
public static class HumourContextInitializer
{
    /// <summary>
    /// Sets the IDatabaseInitializer for the application.
    /// </summary>
    /// <param name="dropDatabaseIfModelChanges">When true, uses the MyDropCreateDatabaseIfModelChanges to recreate the database when necessary.
    /// Otherwise, database initialization is disabled by passing null to the SetInitializer method.
    /// </param>
    public static void Init(bool dropDatabaseIfModelChanges)
    {
        if (dropDatabaseIfModelChanges)
        {
            Database.SetInitializer(new MyDropCreateDatabaseIfModelChanges());
            using (var db = new HumourContext())
            {
                db.Database.Initialize(false);
            }
        }
        else
        {
            Database.SetInitializer<HumourContext>(null);
        }
    }
}
/// <summary>
/// A custom implementation of HumourContext that creates a new Story and Vote.
/// </summary>
public class MyDropCreateDatabaseIfModelChanges : DropCreateDatabaseIfModelChanges<HumourContext>
{
    /// <summary>
    /// Creates a new Story and Vote
    /// </summary>
    /// <param name="context">The context to which the new seed data is added.</param>
    protected override void Seed(HumourContext context)
    {
        var story = new Story
        {
            Title = "Banana",
            StoryType = StoryType.Joke,
            Content = "asdf",
        };
        story.Votes.Add(CreateVote());
        context.Stories.Add(story);
    }

    private static Vote CreateVote()
    {
        return new Vote { IPAddress = "192.168.1.1" };
    }
}

then wired up to tests via:

dev web.config:

<contexts>
  <context type="Humour.Repository.HumourContext, Humour.Repository" disableDatabaseInitialization="false">
    <databaseInitializer type="Humour.Repository.MyDropCreateDatabaseIfModelChanges, Humour.Repository" />
  </context>
</contexts>

prod:

<contexts>
  <context type="Humour.Repository.HumourContext, Humour.Repository" disableDatabaseInitialization="true">
    <!--<databaseInitializer type="Humour.Repository.MyDropCreateDatabaseIfModelChanges, Humour.Repository" />-->
  </context>
</contexts>

Implementing a Base Repository Class

Abstract generic repository methods into a base

Search

public class StoryRepository : Repository<Story>, IStoryRepository
{
    public IEnumerable<Story> FindByTitle(string title)
    {
        return DataContextFactory.GetDataContext().Set<Story>()
            .Where(st => st.Title == title).ToList();
    }
}

Unit Of Work

/// <summary>
/// Represents a unit of work
/// </summary>
public interface IUnitOfWork : IDisposable
{
    /// <summary>
    /// Commits the changes to the underlying data store.
    /// </summary>
    /// <param name="resetAfterCommit">When true, all the previously retrieved objects should be cleared from the underlying model / cache.</param>
    void Commit(bool resetAfterCommit);

    /// <summary>
    /// Undoes all changes to the entities in the model.
    /// </summary>
    void Undo();
}
/// <summary>
/// Creates new instances of a unit of Work.
/// </summary>
public interface IUnitOfWorkFactory
{
    /// <summary>
    /// Creates a new instance of a unit of work
    /// </summary>
    IUnitOfWork Create();

    /// <summary>
    /// Creates a new instance of a unit of work
    /// </summary>
    /// <param name="forceNew">When true, clears out any existing in-memory data storage / cache first.</param>
    IUnitOfWork Create(bool forceNew);
}

Concrete:

/// <summary>
/// Defines a Unit of Work using an EF DbContext under the hood.
/// </summary>
public class EFUnitOfWork : IUnitOfWork
{
    /// <summary>
    /// Initializes a new instance of the EFUnitOfWork class.
    /// </summary>
    /// <param name="forceNewContext">When true, clears out any existing data context first.</param>
    public EFUnitOfWork(bool forceNewContext)
    {
        if (forceNewContext)
        {
            DataContextFactory.Clear();
        }
    }

    /// <summary>
    /// Saves the changes to the underlying DbContext.
    /// </summary>
    public void Dispose()
    {
        DataContextFactory.GetDataContext().SaveChanges();
    }

    /// <summary>
    /// Saves the changes to the underlying DbContext.
    /// </summary>
    /// <param name="resetAfterCommit">When true, clears out the data context afterwards.</param>
    public void Commit(bool resetAfterCommit)
    {
        DataContextFactory.GetDataContext().SaveChanges();
        if (resetAfterCommit)
        {
            DataContextFactory.Clear();
        }
    }

    /// <summary>
    /// Undoes changes to the current DbContext by removing it from the storage container.
    /// </summary>
    public void Undo()
    {
        DataContextFactory.Clear();
    }
}
/// <summary>
/// Creates new instances of an EF unit of Work.
/// </summary>
public class EFUnitOfWorkFactory : IUnitOfWorkFactory
{
    /// <summary>
    /// Creates a new instance of an EFUnitOfWork.
    /// </summary>
    public IUnitOfWork Create()
    {
        return Create(false);
    }

    /// <summary>
    /// Creates a new instance of an EFUnitOfWork.
    /// </summary>
    /// <param name="forceNew">When true, clears out any existing data context from the storage container.</param>
    public IUnitOfWork Create(bool forceNew)
    {
        return new EFUnitOfWork(forceNew);
    }
}

and it working:

Because the UoW is based on interfaces it is easy to use in unit testable environments eg

public class PeopleController : BaseController
{
    private readonly IPeopleRepository _peopleRepository;
    private readonly IUnitOfWorkFactory _unitOfWorkFactory;
    const int PageSize = 10;

    /// <summary>
    /// Initializes a new instance of the PeopleController class.
    /// </summary>
    public PeopleController(IPeopleRepository peopleRepository, IUnitOfWorkFactory unitOfWorkFactory)
    {
        _peopleRepository = peopleRepository;
        _unitOfWorkFactory = unitOfWorkFactory;
    }
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateAndEditPerson createAndEditPerson)
{
    if (ModelState.IsValid)
    {
        try
        {
            using (_unitOfWorkFactory.Create())
            {
                Person person = new Person();
                Mapper.Map(createAndEditPerson, person);
                _peopleRepository.Add(person);
                return RedirectToAction("Index");
            }
        }
        catch (ModelValidationException mvex)
        {
            foreach (var error in mvex.ValidationErrors)
            {
                ModelState.AddModelError(error.MemberNames.FirstOrDefault() ?? "", error.ErrorMessage);
            }
        }
    }
    return View();
}

The controller receives an instance of IPeopleRepository and IUnitOfWorkFactory.  Both parameters are based on interfaces so it’s easy to pass other types in during testing.  At runtime we’ll use IoC.

This makes our app a lot easier to change as:

  • Add property to Model class
  • Use the field in the UI

Compared to having to modify SP’s.

Managing Relationships

Problem with orphaned records if we delete a Story – Votes wouldn’t be deleted.

Do this on an override at HumourContext

/// <summary>
/// Hooks into the Save process to get a last-minute chance to look at the entities and change them. Also intercepts exceptions and
/// wraps them in a new Exception type.
/// </summary>
/// <returns>The number of affected rows.</returns>
public override int SaveChanges()
{
    // Need to manually delete all "owned objects" that have been removed from their owner, otherwise they'll be orphaned.
    var orphanedObjects = ChangeTracker.Entries().Where(
      e => (e.State == EntityState.Modified || e.State == EntityState.Added) &&
        e.Entity is IHasOwner &&
          e.Reference("Owner").CurrentValue == null);

    foreach (var orphanedObject in orphanedObjects)
    {
        orphanedObject.State = EntityState.Deleted;
    }

    try
    {
        var modified = ChangeTracker.Entries().Where(e => e.State == EntityState.Modified || e.State == EntityState.Added);
        foreach (DbEntityEntry item in modified)
        {
            var changedOrAddedItem = item.Entity as IDateTracking;
            if (changedOrAddedItem != null)
            {
                if (item.State == EntityState.Added)
                {
                    changedOrAddedItem.DateCreated = DateTime.Now;
                }
                changedOrAddedItem.DateModified = DateTime.Now;
            }
        }
        return base.SaveChanges();
    }
    catch (DbEntityValidationException entityException)
    {
        var errors = entityException.EntityValidationErrors;
        var result = new StringBuilder();
        var allErrors = new List<ValidationResult>();
        foreach (var error in errors)
        {
            foreach (var validationError in error.ValidationErrors)
            {
                result.AppendFormat("\r\n  Entity of type {0} has validation error \"{1}\" for property {2}.\r\n", error.Entry.Entity.GetType().ToString(), validationError.ErrorMessage, validationError.PropertyName);
                var domainEntity = error.Entry.Entity as DomainEntity<int>;
                if (domainEntity != null)
                {
                    result.Append(domainEntity.IsTransient() ? "  This entity was added in this session.\r\n" : string.Format("  The Id of the entity is {0}.\r\n", domainEntity.Id));
                }
                allErrors.Add(new ValidationResult(validationError.ErrorMessage, new[] { validationError.PropertyName }));
            }
        }
        throw new Humour.Infrastructure.ModelValidationException(result.ToString(), entityException, allErrors);
    }
}

Implementing IDateTracking

See code above!

Improving Error Messages Generated

image
Rather cryptic error message.

image
Better message – showing type, ID and the reason.

See code above too for this!

| | # 
# Thursday, 20 February 2014
( Glimpse )

Using Glimpse on webforms .net35 (IIS .NET2 managed pipeline – classic)

image

Glimpse.ADO

However need to use DbProviderFactory.
http://forums.asp.net/t/1839633.aspx?How+to+create+a+flipcart+like+panel+for+showing+products+in+gridview

or http://getglimpse.com/Help/ADO-Integration

image

    protected void Page_Load(object sender, EventArgs e)
    {
        int rowcount = 0;
        DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient"); //factory: {Glimpse.Ado.AlternateType.GlimpseDbProviderFactory<System.Data.SqlClient.SqlClientFactory>}
        using (IDbConnection con = factory.CreateConnection()) //con: {Glimpse.Ado.AlternateType.GlimpseDbConnection}
        {
            con.ConnectionString = @"server=.\SQLEXPRESS;database=TestDB;Trusted_Connection=True;";
            using (IDbCommand cmd = con.CreateCommand()) //cmd: {Glimpse.Ado.AlternateType.GlimpseDbCommand}
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = "dbo.GetCountries";
                //IDbDataParameter parm = cmd.CreateParameter();
                //parm.ParameterName = "@Error_Code";
                //parm.Direction = ParameterDirection.Output;
                //parm.DbType = DbType.Int32;
                //cmd.Parameters.Add(parm);
                con.Open();

                using (IDataReader reader = cmd.ExecuteReader()) //reader: {Glimpse.Ado.AlternateType.GlimpseDbDataReader}
                {
                    while (reader.Read())
                    {
                        var text = reader["Name"];
                        TextBox1.Text += text + ", ";
                        rowcount++;
                    }
                }
            }
        }

        //using (var connection = new SqlConnection(connectionString))
        //{
        //    connection.Open();
        //    using (var command = new SqlCommand("SELECT Name FROM Countries", connection))
        //    {
        //        using (var reader = command.ExecuteReader())
        //        {
        //            while (reader.Read())
        //            {
        //                var text = reader["Name"];
        //                //var text = reader[0];
        //                //var text = reader.GetString(reader.GetOrdinal("Name"));
        //                //TextBox1.Text += reader.GetString(0) + ", ";
        //                TextBox1.Text += text + ", ";
        //            }
        //        }
        //    }
        //}
    }

Cache

Useful to see what is being put into a cache.

What I really want to do is see where the bottlenecks are in the actual code.. maybe LDAP calls. Maybe http://miniprofiler.com/

| | # 
( c# | SQL )

"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached."

Useful to see where the app pool connections are coming from:

--exec sp_who
exec sp_who2

http://stackoverflow.com/questions/670774/how-can-i-solve-a-connection-pool-problem-between-asp-net-and-sql-server

The issue I had was code not wrapped in a using..so it wasn’t calling a connection.close().

| | # 
( Git )

When deleting a large number of files

git commit –a –m “deleting”

reverting:

| | # 

Figure out the different classes needed for Humour project

Entities – identified by unique ID eg a person.  If have the same name, they are still different people

Value Objects (going to leave for now and just explore DoaminEntities) – identified by the value eg address.  If the address is the same, then the same

namespace Humour.Infrastructure
{
    /// <summary>
    /// Serves as the base class for all entities in the system.
    /// </summary>
    /// <typeparam name="T">The type of the key for the entity.</typeparam>
    public abstract class DomainEntity<T>
    {
        /// <summary>
        /// Gets or sets the unique ID of the entity in the underlying data store.
        /// </summary>
        public T Id { get; set; }

        /// <summary>
        /// Checks if the current domain entity has an identity.
        /// </summary>
        /// <returns>True if the domain entity is transient (i.e. has no identity yet), false otherwise.</returns>
        public bool IsTransient()
        {
            return Id.Equals(default(T));
        }
    }
}

A base class for all DomainEntities.  Using generics to allow any type for Id.  IsTransient to see if data has an Id yet.

public class Story : DomainEntity<int>
{
    public int Id { get; set; }
}

The Story entity

[TestMethod]
public void NewStoryShouldHaveEmptyId()
{
    var story = new Story();
    //Assert.AreEqual(0, story.Id);
    //story.Id.Should().Be(0);
    story.IsTransient().Should().BeTrue();
}

Enums

Added if EF5.  So trying them here for StoryType.  Will be interesting to see how hard raw SQL will be when working with Code First generated Enums.

public enum StoryType
{
    Joke = 1,
    Video = 2,
    Quote = 3,
    Picture = 4,
    AnimatedGIF = 5
}

 

public class Story : DomainEntity<int>
{
    [Required]
    public string Title { get; set; }
    public StoryType StoryType { get; set; }
    public string Content { get; set; }
    public string VideoURL { get; set; }
    public string ImageURL { get; set; }
    public DateTime AddedDate { get; set; }
    public int Rating { get; set; }
}

 

public class Vote : DomainEntity<int>
{
    [Required]
    public string IPAddress { get; set; }
    public DateTime DateAdded { get; set; }
    public Story Owner { get; set; }
    public int OwnerId { get; set; }
}

One Story can have zero to many instances of Vote.  By having Owner property on Vote, you can assign a person to this class and link back to person from Vote.

By having OwnerId property (a foreign key property) EF lets you assign one entity to another by using the ID

var vote = new Vote { };
vote.Owner = _storyRepository.FindById(123);

vote.OwnerId = 123;

Much easier to simply assign the Story we’ve voted for by Id.  This may save a trip to the Db.

Dealing with Collections

public class Vote : DomainEntity<int>, IHasOwner
{
    [Required]
    public string IPAddress { get; set; }
    public DateTime DateAdded { get; set; }
    public Story Owner { get; set; }
    public int OwnerId { get; set; }
}

namespace Humour.Model
{
    /// <summary>
    /// This interface is used to mark the owner of an object.
    /// Useful for helping when there are orphaned Votes if a Story is deleted
    /// </summary>
    public interface IHasOwner
    {
        /// <summary>
        /// The Story instance this object belongs to.
        /// </summary>
        Story Owner { get; set; }
    }
}

Equality Comparison

We want to know when a Story object is the same eg if adding the same joke!  Or if we’ve got an import console app and we run it twice – don’t want duplicates

[TestMethod]
public void TwoStoriesWithSameIdShouldBeTheSame()
{
    var story1 = new Story { Id = 1, Title = "asdf" };
    var story2 = new Story { Id = 1, Title = "asdf" };
    (story1 == story2).Should().BeTrue();
}

This test fails as default implementation for comparison of classes in .NET is to use reference equality.  story1 and story2 are referring to difference instances of Story.

So need to override Equals and == and !=

/// <summary>
/// Determines whether the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />.
/// </summary>
/// <returns>
/// true if the specified object is equal to the current object; otherwise, false.
/// </returns>
/// <param name="obj">
/// The object to compare with the current object.
/// </param>
public override bool Equals(object obj)
{
    if (obj == null || !(obj is DomainEntity<T>))
    {
        return false;
    }

    if (ReferenceEquals(this, obj))
    {
        return true;
    }

    var item = (DomainEntity<T>)obj;

    if (item.IsTransient() || IsTransient())
    {
        return false;
    }
    return item.Id.Equals(Id);
}

/// <summary>
/// Compares two instances for equality.
/// </summary>
/// <param name="left">The left instance to compare.</param>
/// <param name="right">The right instance to compare.</param>
/// <returns>True when the objects are the same, false otherwise.</returns>
public static bool operator ==(DomainEntity<T> left, DomainEntity<T> right)
{
    if (Equals(left, null))
    {
        return Equals(right, null);
    }
    return left.Equals(right);
}

/// <summary>
/// Compares two instances for inequality.
/// </summary>
/// <param name="left">The left instance to compare.</param>
/// <param name="right">The right instance to compare.</param>
/// <returns>False when the objects are the same, true otherwise.</returns>
public static bool operator !=(DomainEntity<T> left, DomainEntity<T> right)
{
    return !(left == right);
}

And here are the tests:

[TestMethod]
public void TwoStoriesWithSameIdShouldBeTheSame()
{
    var story1 = new Story { Id = 1, Title = "asdf" };
    var story2 = new Story { Id = 1, Title = "asdf" };
    (story1 == story2).Should().BeTrue();
}

Lots of tests in DomainEntity base class

Creating Collections

It’s quite common to do this:

public class Story : DomainEntity<int>
{
    [Required]
    public string Title { get; set; }
    public StoryType StoryType { get; set; }
    public string Content { get; set; }
    public string VideoURL { get; set; }
    public string ImageURL { get; set; }
    public DateTime AddedDate { get; set; }
    public int Rating { get; set; }

    public List<Vote> Votes { get; set; }
}

Or IQueryable

However not generally recommended to expose List<T> to the public API of a class as not mean for inheritence.  This means can’t override any of its members eg when Add/Find/Removing members.

Recommended to use Collection<T> however this doesn’t have Sort, add range.  Lets add a base class to do this.

/// <summary>
/// Represents a collection of Vote instances in the system.
/// </summary>
public class Votes : CollectionBase<Vote>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Votes"/> class.
    /// </summary>
    public Votes() { }

    /// <summary>
    /// Initializes a new instance of the <see cref="Votes"/> class.
    /// </summary>
    /// <param name="initialList">Accepts an IList of Votes as the initial list.</param>
    public Votes(IList<Vote> initialList) : base(initialList) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="Votes"/> class.
    /// </summary>
    /// <param name="initialList">Accepts a CollectionBase of Vote as the initial list.</param>
    public Votes(CollectionBase<Vote> initialList) : base(initialList) { }

    /// <summary>
    /// Adds a new instance of Vote to the collection.
    /// </summary>
    /// <param name="ipAddress">The ip address</param>
    public void Add(string ipAddress)
    {
        Add(new Vote { IPAddress = ipAddress });
    }

then in Story:

public class Story : DomainEntity<int>
{
    public Story()
    {
        Votes = new Votes();
    }

    [Required]
    public string Title { get; set; }
    public StoryType StoryType { get; set; }
    public string Content { get; set; }
    public string VideoURL { get; set; }
    public string ImageURL { get; set; }
    public DateTime AddedDate { get; set; }
    public int Rating { get; set; }

    public Votes Votes { get; set; }
}

And with the add method on Votes, can do this:

var story = new Story();
story.Votes.Add("192.168.1.1");

Sorting

Using a custom comparer and wiring in a Sort method on a Collection.

[TestMethod]
public void SortPeopleWithSpecifiedComparerSortsCorrectly()
{
    var stories = new Stories();
    stories.Add(new Story { Title = "asdf", Content = "hello" });
    stories.Add(new Story { Title = "asdf3", Content = "hello" });
    stories.Add(new Story { Title = "asdf2", Content = "hello" });
    stories.Add(new Story { Title = "asdf1", Content = "hello" });
    stories.Add(new Story { Title = "asdf9", Content = "hello" });

    stories.Sort(new StoryComparer());

    stories[0].Title.Should().Be("asdf");
    stories[1].Title.Should().Be("asdf1");
    stories[2].Title.Should().Be("asdf2");
    stories[3].Title.Should().Be("asdf3");
}

[TestMethod]
public void SortIntsSorts()
{
    var ints = new IntCollection { 3, 2, 1 };
    ints.Sort();
    ints[0].Should().Be(1);
    ints[1].Should().Be(2);
    ints[2].Should().Be(3);
}

public class StoryComparer : IComparer<Story>
{
    public int Compare(Story x, Story y)
    {
        return x.Title.CompareTo(y.Title);
    }
}

 

/// <summary>
/// Sorts the collection based on the specified comparer.
/// </summary>
/// <param name="comparer">The comparer.</param>
public void Sort(IComparer<T> comparer)
{
    var list = Items as List<T>;
    if (list != null)
    {
        list.Sort(comparer);
    }
}

/// <summary>
/// Sorts the collection based on the specified comparer. Uses equals on the objects being compared.
/// </summary>
public void Sort()
{
    var list = Items as List<T>;
    if (list != null)
    {
        list.Sort();
    }
}

Auto Tracking of Create and Modified Dates

A common thing we want to do on entities.

public class Story : DomainEntity<int>, IDateTracking
    {

 

/// <summary>
/// Defines an interface for objects whose creation and modified dates are kept track of.
/// </summary>
public interface IDateTracking
{
    /// <summary>
    /// Gets or sets the date and time the object was created.
    /// </summary>
    DateTime DateCreated { get; set; }

    /// <summary>
    /// Gets or sets the date and time the object was last modified.
    /// </summary>
    DateTime DateModified { get; set; }
}

wire up later

Infrastructure - Repository Interfaces

For generic repository.

/// <summary>
/// Defines various methods for working with data in the system.
/// </summary>
public interface IRepository<T, K> where T : class
{
    /// <summary>
    /// Finds an item by its unique ID.
    /// </summary>
    /// <param name="id">The unique ID of the item in the database.</param>
    /// <param name="includeProperties">An expression of additional properties to eager load. For example: x => x.SomeCollection, x => x.SomeOtherCollection.</param>
    /// <returns>The requested item when found, or null otherwise.</returns>
    T FindById(K id, params Expression<Func<T, object>>[] includeProperties);

    /// <summary>
    /// Returns an IQueryable of all items of type T.
    /// </summary>
    /// <param name="includeProperties">An expression of additional properties to eager load. For example: x => x.SomeCollection, x => x.SomeOtherCollection.</param>
    /// <returns>An IQueryable of the requested type T.</returns>
    IQueryable<T> FindAll(params Expression<Func<T, object>>[] includeProperties);

    /// <summary>
    /// Returns an IQueryable of items of type T.
    /// </summary>
    /// <param name="predicate">A predicate to limit the items being returned.</param>
    /// <param name="includeProperties">An expression of additional properties to eager load. For example: x => x.SomeCollection, x => x.SomeOtherCollection.</param>
    /// <returns>An IEnumerable of the requested type T.</returns>
    IEnumerable<T> FindAll(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties);

    /// <summary>
    /// Adds an entity to the underlying collection.
    /// </summary>
    /// <param name="entity">The entity that should be added.</param>
    void Add(T entity);

    /// <summary>
    /// Removes an entity from the underlying collection.
    /// </summary>
    /// <param name="entity">The entity that should be removed.</param>
    void Remove(T entity);

    /// <summary>
    /// Removes an entity from the underlying collection.
    /// </summary>
    /// <param name="id">The ID of the entity that should be removed.</param>
    void Remove(K id);
}

May want to refactor as potentially too complex – p59 of the pdf

var stories = repository.FindAll(st => st.StoryType == StoryType.Joke);
/// <summary>
/// Defines the various methods available to work with people in the system.
/// </summary>
public interface IStoryRepository : IRepository<Story, int>
{
    /// <summary>
    /// Gets a list of all the stories title exactly matches.
    /// </summary>
    /// <param name="lastName">The last name that the system should search for.</param>
    /// <returns>An IEnumerable of Story with the matching people.</returns>
    IEnumerable<Story> FindByTitle(string title);
}
[HttpDelete]
        public ActionResult Delete(intid)
        {
            using(_unitOfWorkFactory.Create())
                {
                    _peopleRepository.Remove(id);
                }
            return RedirectToAction("Index");
        }

In fact – fairly simple to use

Summary

  • Base class for collections
  • Custom collections inherit from base and don’t use List<T> as can’t override
  • Extensive tests for model classes

 

.

| | # 
# Wednesday, 19 February 2014
( Humour )

image
I’ve used a simple convention

Integration references Infrastructure, Model and Repository
Mvc references Mvc, Infrastructure, Model, and NuGet Mvc download
Unit references Infrastructure and Model

namespace Humour.Model
{
    public class Story
    {
        public int Id { get; set; }
    }
}

namespace Humour.Tests.Unit
{
    [TestClass]
    public class StoryTests
    {
        [TestMethod]
        public void NewStoryShouldHaveEmptyId()
        {
            var story = new Story();
            Assert.AreEqual(0, story.Id);
        }
    }
}

Ctrl R A - to use MS Test runner
Alt E I O A - to clean up and sort usings

namespace Humour.Tests.Mvc
{
    [TestClass]
    public class HomeControllerTests
    {
        [TestMethod]
        public void IndexHasNoModel()
        {
            var controller = new HomeController();
            var result = controller.Index() as ViewResult;
            Assert.AreEqual(null, result.Model);
        }
    }
}

Adding tests to each test project to check references.  Testing no model here. **page31

FluentAssertions

NuGet it into the 3 test projects

[TestMethod]
public void NewStoryShouldHaveEmptyId()
{
    var story = new Story();
    //Assert.AreEqual(0, story.Id);
    story.Id.Should().Be(0);
}
| | #