TheHvidsten
TheHvidsten

Reputation: 4458

Entity Framework with Repository Pattern insert many-to-many

I have inherited a solution that's using Entity Framework with Repository Pattern. In this solution the previous developers have forgotten to implement a many-to-many relationship, so now I have to do it instead.

I'm not too familiar with either EF or the pattern so I just can't get what I'm trying to accomplish to actually work, which is to insert into a many-to-many relationship. I can get EF to create the relationship table in the database, but somehow I can't insert into it.

I have seen other questions similar to this one, but none of them match exactly how the pattern is implemented here, and then me being unfamiliar with everything can't wrap my head around it.

Can someone please take a look at the code and see what I'm missing? (Code has been simplified a bit and this is no actual Student/Course project, but I'm using those names as they have been used in previous examples)

Here's my code. Very simplified and without thousands of interfaces. This runs just fine with no exceptions. When I debug and quickwatch the studentUow in the "Main" class, the Students.Courses collection does indeed contain a value, but it is never saved to the database. Also, it contains only one value, even though it should contain several courses.

Entity classes

public class Student { // This class already existed
    public int StudentId { get; protected set; }
    public virtual ICollection<Course> Courses { get; set; } // I added this property
}

public class Course { // This class already existed
    public int CourseId { get; protected set; }
    public virtual ICollection<Student> Students { get; set; } // I added this property
}

Repository

public class StudentRepository {
    protected DbContext DbContext { get; private set; }
    protected DbSet<Student> DbSet { get; private set; }
    public StudentRepository(DbContext dbContext) {
        DbContext = dbContext;
        DbSet = dbContext.Set<Student>();
    }

    public virtual void AddOrUpdate(Student entity) {
        if (Exists(entity)) {
            Update(entity);
        } else {
            Add(entity);
        }
    }

    public virtual void Update(Student entity) {
        var dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State == EntityState.Detached) {
            DbSet.Attach(entity);
        }
        dbEntityEntry.State = EntityState.Modified;
    }

    public virtual Student Add(Student entity) {
        var dbEntityEntry = DbContext.Entry(entity);
        if (dbEntityEntry.State != EntityState.Detached) {
            dbEntityEntry.State = EntityState.Added;
        } else {
            return DbSet.Add(entity);
        }
        return null;
    }

    public IQueryable<Student> Queryable() {
        return DbSet;
    }

    public bool Exists(Student entity) {
        var objContext = ((IObjectContextAdapter)DbContext).ObjectContext;
        object existingEntity;
        var exists = objContext.TryGetObjectByKey(GetEntityKey(entity), out existingEntity);
        if (exists) objContext.Detach(existingEntity);

        return exists;
    }

    private EntityKey GetEntityKey(Student entity) {
        var objContext = ((IObjectContextAdapter)DbContext).ObjectContext;
        var objSet = objContext.CreateObjectSet<T>();
        var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity);
        return entityKey;
    }
}

Unit of work

public class StudentUow : UnitOfWork<MyDbContext> {
    public StudentRepository Students { get { return CreateRepository<StudentRepository>(); } }
    public CourseRepository Courses { get { return CreateRepository<CourseRepository>(); } }
}

public class UnitOfWork<TContext> where TContext : System.Data.Entity.DbContext {
    private readonly TContext _dbContext;
    private IRepositoryProvider _repositoryProvider;

    protected UnitOfWork(IRepositoryProvider provider) {
        _repositoryProvider = provider;
    }

    protected TRepository CreateRepository<TRepository>() {
        return _repositoryProvider.Create<TRepository>(_dbContext, "default");
    }

    public void Commit() {
        _dbContext.SaveChanges();
    }
}

[Export(typeof(IUnitOfWorkProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class UnitOfWorkProvider {
    [Import] private IRepositoryProvider _repositoryProvider;

    public StudentUow GetStudentUow() {
        return new StudentUow(_repositoryProvider);
    }
}

[Export(typeof(IRepositoryProvider))]
public class MyRepositoryProvider {
    public MyRepositoryProvider() {
        Register<RepositoryFactory<IProductRepository, StudentRepository>>();
    }

    public TRepository Create<TRepository>(DbContext dbContext, string conntextKey)
    {
        var type = typeof (TRepository);
        if (!_factories.ContainsKey(type))
            throw new UnknownFactoryException(type);

        return (TRepository)_factories[type].Create(dbContext);
    }

    public void Register<TRepositoryFactory>() where TRepositoryFactory : IRepositoryFactory, new()
    {
        var factory = new TRepositoryFactory();
        if (_factories.ContainsKey(factory.Type)) throw new FactoryTypeAlreadyRegisteredException(factory.Type);
        _factories[factory.Type] = factory;
    }
}

"Main" class

public class MyClass {
    public AddCourse(int studentId, List<int> courses) {
        using (var studentUow = new StudentUow()) {
            foreach (int courseId in courses) {
                Student s = studentUow.Student.Queryable().First(x => x.StudentId == studentId);
                Course c = studentUow.Course.Queryable().First(x => x.CourseId == courseId);
                s.Courses.Add(c);

                studentUow.Student.AddOrUpdate(s);
            }
            studentUow.Commit();
        }
    }
}

If you're missing some functionality, leave a comment and I'll add it, or let you know where it is.

Upvotes: 4

Views: 3256

Answers (1)

Alexander Polyankin
Alexander Polyankin

Reputation: 1887

EF is not including related entities in queries by default. In order to to this, you need to include Courses manually when needed. Also there is a problem that on each iteration you are fetching student again and again so the collection of courses is lost. This should work:

using System.Data.Entity;
...
using (var studentUow = new StudentUow()) {
    Student s = studentUow.Student.Queryable().Include(x => x.Courses).First(x => x.StudentId == studentId);
    foreach (int courseId in courses) {
        Course c = studentUow.Course.Queryable().First(x => x.CourseId == courseId);
        s.Courses.Add(c);
        c.Students.Add(s);

        studentUow.Course.Update(c);
    }
    studentUow.Student.Update(s);
    studentUow.Commit();
}

I would highly recommend refactoring your code if possible.

Upvotes: 1

Related Questions