Entity Framework与Repository模式插入多对多关系。

4

我继承了一个使用实体框架和仓储模式的解决方案。在这个解决方案中,之前的开发人员忘记了实现多对多关系,所以现在我必须代替他们来完成。

我对EF和这个模式都不太熟悉,所以我就不能让我尝试实际工作的功能起作用,那就是插入到多对多关系中。我可以让EF在数据库中创建关系表,但是无论如何我都无法插入数据。

我看过类似于此问题的其他问题,但没有一个与这里实现的模式完全匹配,而且我对所有内容都不熟悉,所以无法理解它。

请有人看一下代码并查看我错过了什么吗?(代码已经简化了一点,并且这不是真正的Student/Course项目,但我正在使用这些名称,因为它们在以前的示例中已使用)

这是我的代码。非常简单,没有成千上万的接口。这个运行得很好,没有异常。当我在“Main”类中调试并快速观察studentUow时,Students.Courses集合确实包含一个值,但它从未保存到数据库中。此外,它只包含一个值,尽管它应该包含多个课程。

实体类

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
}

代码库

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;
    }
}

工作单元

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();
        }
    }
}

如果您缺少某些功能,请留下评论,我会添加它或让您知道它在哪里。

1
please http://www.sscce.org/ - Hamid Pourjam
1
实际项目中充斥着大量的UOWs、Repositories、Interfaces,并使用一些MEF来实例化各种东西。我甚至不知道从哪里开始创建一个SSCCE来解决这个混乱的问题。 - GTHvidsten
你能发布CreateRepository<T>的代码吗?我的初步想法是每个仓库都会创建一个新的DbContext,因此它们之间没有关系。因此,当您在这两个上使用Queryable方法时,它们仍然是代理对象,因此创建多对多关系将不起作用。确保DbContext引用相同的对象,或尝试在两个Queryable上使用ToList,以便它们不是代理对象。 - ManOVision
@ManOVision 代码已更新。正如您所看到的,一些内容是通过 MEF 实例化的。 - GTHvidsten
MyDbContext的生命周期是什么?每次都会实例化一个新的,还是它是单例模式?无论哪种方式都可能在代码的不同区域带来问题。我建议打破存储库模式,并让您的“Main”类直接与MyDbContext交互。这样,您就可以消除EF或其他地方存在问题的疑虑。如果您的多对多对象被正确添加,那么您可能需要查看大量代码。如果它们没有被添加,那么可能只需要进行简单的MyDbContext代码更改或数据库更改即可解决问题。 - ManOVision
@ManOVision 我的DbContext是由MEF实例化的。它还有一个静态构造函数,因此应该是单例模式。 - GTHvidsten
1个回答

1

默认情况下,EF不会将相关实体包含在查询中。为了实现这一点,当需要时您需要手动包含Courses。此外,每次迭代时都会重新获取学生,因此课程集合会丢失。应该按照以下方式操作:

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();
}

我强烈建议您尽可能重构您的代码。

我尝试过这个,但是关系表中仍然没有插入任何东西。重构所有内容可能是解决问题的方法。有没有好的模式建议,最好是模块化的? - GTHvidsten
首先,尝试直接使用DbContext来查看是否有效。然后,我会通过DI将DbContext实例传递给存储库。UoW也将具有DbContext实例,因此Commit调用DbContext.SaveChanges()。此外,您需要在IoC容器中指定DbContext的生命周期为每个请求。 - ranquild

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接