Entity Framework Repositories (Part 1)

Is often useful to use the Repository pattern with EF. Unfortunalely EF doen’t behave as we could expect when interacting for the basic CRUD operations. For example when we delete an entity it doesn’t delete its dependencies on cascade (it try to set their FK to NULL instead, causing DB errors at save time). In addition is difficult to insert/update complex entities (for example an entire object together with its dependencies at the same time), when we don’t just load an entity, change one or two fields, and save it again, like in the 99% of the provided examples. For instance several solutions attempt to directly insert detached POCO entities, without using the Create() method. Doing in this way the insert works, but the entity will not be converted to a EF proxy object and the navigation properties will not be updated. This example tries to show a complete repository that can be easily used on real scenarios. The repository works with City entities on this data structure:

The generic Interface

public interface IRepository<T, in TK>
{
    IEnumerable<T> GetAll();
    T GetById(TK id);
    T AddNew(T entity, bool saveChanges = true);
    T Update(T entity, bool saveChanges = true);
    void Delete(TK id, bool saveChanges = true);
    void SaveChanges();
}

The Implementation

public class CityRepository : IRepository<City, int>
{
    private readonly DbContext _context;
 
    public CityRepository(DbContext context)
    {
        _context = context;
    }
 
    public IEnumerable<City> GetAll()
    {
        return _context.Set<City>().ToList();
    }
 
    public City GetById(int id)
    {
        return _context.Set<City>().Find(id);
    }
 
    public City AddNew(City entity, bool saveChanges = true)
    {
        return AddUpdate(entity, saveChanges);
    }
 
    public City Update(City entity, bool saveChanges = true)
    {
        return AddUpdate(entity, saveChanges);
    }
 
    public void Delete(int id, bool saveChanges = true)
    {
        var city = GetById(id);
 
        //Delete dependencies before deleting
        _context.Set<Car>().RemoveRange(city.Persons
            .SelectMany(person => person.Cars));
        _context.Set<Person>().RemoveRange(city.Persons);
        _context.Set<City>().Remove(city);
        if (saveChanges)
            _context.SaveChanges();
    }
 
    public void SaveChanges()
    {
        _context.SaveChanges();
    }
 
    private City AddUpdate(City entity, bool saveChanges)
    {
        City city;
        if (entity.IdCity == 0)
        {
            city = _context.Set<City>().Create();
            _context.Set<City>().Add(city);
        }
        else
            city = GetById(entity.IdCity);
        _context.Entry(city).CurrentValues.SetValues(entity);
 
        //Delete dependencies before updating
        _context.Set<Car>().RemoveRange(city.Persons
            .SelectMany(person => person.Cars)
            .Where(car => entity.Persons
                .SelectMany(entityPerson => entityPerson.Cars)
                .All(entityCar => entityCar.IdCar != car.IdCar)));
        _context.Set<Person>().RemoveRange(city.Persons
            .Where(person => entity.Persons
                .All(entityPerson =>
                    entityPerson.IdPerson != person.IdPerson)));
 
        //Add/Update dependecies before updating
        entity.Persons.ToList().ForEach(entityPerson =>
        {
            //Ensures that the FK is set
            entityPerson.IdCity = entity.IdCity;
 
            Person person;
            if (entityPerson.IdPerson == 0)
            {
                person = _context.Set<Person>().Create();
                city.Persons.Add(person);
            }
            else
                person = _context.Set<Person>().Find(entityPerson.IdPerson);
            _context.Entry(person).CurrentValues.SetValues(entityPerson);
            entityPerson.Cars.ToList().ForEach(entityCar =>
            {
                //Ensures that the FK is set
                entityCar.IdPerson = entityPerson.IdPerson;
 
                Car car;
                if (entityCar.IdCar == 0)
                {
                    car = _context.Set<Car>().Create();
                    person.Cars.Add(car);
                }
                else
                    car = _context.Set<Car>().Find(entityCar.IdCar);
                _context.Entry(car).CurrentValues.SetValues(entityCar);
            });
        });
        if (saveChanges)
            _context.SaveChanges();
        return city;
    }
}

The Test Class (NUnit)

[TestFixture]
public class When_A_City_Repository_Is_Created
{
    private DbContext _context;
    private DbContextTransaction _transaction;
    private IRepository<City, int> _repository;
 
    [SetUp]
    public void SetUp()
    {
        _context = new TestDBEntities();
        _transaction = _context.Database.BeginTransaction();
        _repository = new CityRepository(_context);
    }
 
    [Test]
    public void It_Should_Get_All_The_Cities()
    {
        var loadedCities = _repository.GetAll();
 
        Assert.IsNotNull(loadedCities);
        Assert.IsTrue(loadedCities.Any());
        Assert.IsTrue(loadedCities.SelectMany(c => c.Persons).Any());
    }
 
    [Test]
    public void It_Should_Get_A_City_By_Id()
    {
        const int id = 1;
        var loadedCity = _repository.GetById(id);
 
        Assert.IsNotNull(loadedCity);
        Assert.AreEqual(loadedCity.IdCity, id);
        Assert.IsTrue(loadedCity.Persons.Any());
    }
 
    [Test]
    public void It_Should_Add_A_New_City()
    {
        var city = new City
        {
            CityName = "TestCity",
        };
        var addedCity = _repository.AddNew(city);
 
        Assert.AreNotEqual(addedCity.IdCity, 0);
        Assert.AreEqual(addedCity.CityName, city.CityName);
    }
 
    [Test]
    public void It_Should_Add_A_New_City_With_Dependencies()
    {
        var city = new City
        {
            CityName = "TestCity",
            Persons = new List<Person>
            {
                new Person
                {
                    Name = "TestPersonName1",
                    Surname = "TestPersonSurname1",
                    IdGender = 1,
                    Cars = new List<Car>
                    {
                        new Car{ CarDescription = "Person1Car1"},
                        new Car{ CarDescription = "Person1Car2"}
                    }
                },
                new Person
                {
                    Name = "TestPersonName2",
                    Surname = "TestPersonSurname2",
                    IdGender = 2,
                    Cars = new List<Car>
                    {
                        new Car{ CarDescription = "Person2Car1"},
                        new Car{ CarDescription = "Person2Car2"}
                    }
                }
            }
        };
        var addedCity = _repository.AddNew(city);
 
        Assert.AreNotEqual(addedCity.IdCity, 0);
        Assert.AreEqual(addedCity.CityName, city.CityName);
        Assert.IsTrue(addedCity.Persons.Count() == 2);
 
        var firstPerson = addedCity.Persons.First();
        Assert.AreNotEqual(firstPerson.IdPerson, 0);
        Assert.AreEqual(firstPerson.IdCity, addedCity.IdCity);
        Assert.AreEqual(firstPerson.Name, "TestPersonName1");
        Assert.IsNotNull(firstPerson.Gender);
        Assert.IsTrue(firstPerson.Cars.Count() == 2);
 
        var firstCar = firstPerson.Cars.First();
        Assert.AreNotEqual(firstCar.IdCar, 0);
        Assert.AreEqual(firstCar.IdPerson, firstPerson.IdPerson);
        Assert.AreEqual(firstCar.CarDescription, "Person1Car1");
    }
 
    [Test]
    public void It_Should_Update_A_City()
    {
        const int id = 1;
        var city = new City
        {
            IdCity = id,
            CityName = "UpdatedCity"
        };
        var uploadedCity = _repository.Update(city);
 
        Assert.AreEqual(uploadedCity.CityName, city.CityName);
    }
 
    [Test]
    public void It_Should_Update_A_City_With_Dependencies()
    {
        const int id = 1;
        var city = new City()
        {
            IdCity = id,
            CityName = "UpdatedCity",
            Persons = new List<Person>
            {
                new Person
                {
                    IdPerson = 2,
                    Name = "UpdatedPersonName",
                    Surname = "UpdatedPersonSurname",
                    IdGender = 2,
                    Cars = new List<Car>
                    {
                        new Car
                        {
                            IdCar = 3,
                            CarDescription = "UpdatedCar"
                        },
                        new Car
                        {
                            CarDescription = "AddedCar"
                        }
                    }
                }
            }
        };
        var uploadedCity = _repository.Update(city);
 
        Assert.AreEqual(uploadedCity.CityName, city.CityName);
        Assert.IsTrue(uploadedCity.Persons.Count() == 1);
 
        var firstPerson = uploadedCity.Persons.First();
        Assert.AreEqual(firstPerson.IdPerson, 2);
        Assert.AreEqual(firstPerson.Name, "UpdatedPersonName");
        Assert.IsTrue(firstPerson.Cars.Count() == 2);
 
        var car1 = firstPerson.Cars.ElementAt(0);
        Assert.AreEqual(car1.IdCar, 3);
        Assert.AreEqual(car1.CarDescription, "UpdatedCar");
        var car2 = firstPerson.Cars.ElementAt(1);
        Assert.AreNotEqual(car2.IdCar, 0);
        Assert.AreEqual(car2.CarDescription, "AddedCar");
    }
 
    [Test]
    public void It_Should_Delete_A_City()
    {
        const int id = 1;
        _repository.Delete(id);
        var loadedCities = _repository.GetAll();
 
        Assert.IsTrue(loadedCities.All(c => c.IdCity != id));
    }
 
    [TearDown]
    public void TearDown()
    {
        _transaction.Rollback();
        _transaction.Dispose();
        _context.Dispose();
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s