如何避免必须为使用DbContext的每个项目安装Entity Framework

时间:2018-03-26 11:46:09

标签: c# entity-framework architecture data-access-layer

我有一个包含多个UI项目和一堆常见(共享)DLL项目的解决方案。还有一个基于EF的“数据库访问项目”,它包含模型和DbContext,并由UI项目引用。

现在的问题是,对于使用“数据库访问项目”中的DbContext的每个UI项目,我必须安装Entity Framework NuGet包。当我必须更新EF时,问题变得更糟。然后我总是要注意使用EF的所有项目都安装了相同的EF版本。

有更好的解决方案吗?对我来说,正确的方法似乎只需要在一个地方安装EF,即“数据库访问项目”。

我还考虑过隐藏EF和代理对象背后的DbSets,以避免引用EF。

当我省略EF参考时,我会收到类似

的错误
Error   CS0012  The type 'DbContext' is defined in an assembly that is not referenced. You must add a reference to assembly 'EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

Error   CS0012  The type 'DbSet<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

3 个答案:

答案 0 :(得分:0)

为什么要从DAL层公开DbContext?理想情况下,DAL层应该公开一个使用Dbcontext或Dbset的类。您的UI层上没有任何EF后悔。有多种方法可以实现这一目标。一种常用的模式称为存储库模式。

你可以在这里看到E,g

https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

答案 1 :(得分:0)

您的问题通常是一个示例,说明为什么将数据库的内部暴露给外部世界是不明智的。

您应该公开您的用户希望能够执行Linq语句的数据序列。通常,将DbContext的DbSets类公开为IQueryable集合就足够了。这通常被称为存储库模式。

如果您没有存储库,则用户将创建DbContext类的对象。然后他们将使用DbContext的DbSets以IQueryable的形式访问您的表。使用这些IQueryables来创建和执行查询,可以使用SaveChanges。最后他们将Dispose DbContext,。

使用类似的存储库。您的Repository的用户将创建一个Repository对象。访问代表您的表的IQueryables。执行linq,可能的SaveChanges和Dispose the Repository。

如果您只想允许查询,请公开IQueryable对象,如果您希望能够添加/更新/删除提取的项目,还可以添加Add / Remove / SaveChanges函数。

如果将这些函数公开为接口,则只需通过内存单元测试数据库替换DbContext,或者有时仅使用列表来单元测试代码。

例如,学校,教师,成绩的学校背景:教师有很多学生;学生有很多老师(多对多);学生的成绩为零或以上,每个成绩只属于一名学生(一对多)。

学校上下文中的所有项目都由Id标识:它们都实现了IId。这用于使通用类能够通过Id

获取项目

仅查询

带有接口的DbContext:

interface IId
{
     public int Id {get;}
}

class Teacher : IId {...}
class Student: IId {...}
class Grade: IId {...}

interface ISchoolQuerier
{
    IQueryable<Teacher> Teachers {get; }
    IQueryable<Student> Students {get;}
    IQueryable<Grade> Grades {get;}
}

class SchoolDbContext : DbContext
{
    public DbSet<Teacher> Teachers {get; set;}
    public DbSet<Student> Students {get; set;}
    public DbSet<Grades> {get; set;}

    // explicit interface implementation
    IQueryable<Teacher> ISchoolQuerier.Teachers {get{return this.Teachers;}}
    IQueryable<Student> ISchoolQuerier.Students {get{return this.Students;}}
    IQueryable<Grade> ISchoolQuerier.Grades{get{return this.Grades;}}
}

使用此SchoolDbContext的SchoolRepository

class SchoolRepository : IDisposable, ISchoolQuerier
{
    private SchoolRepository() : this (ISchoolQuerier)(new SchoolDbContext())
    {   // default constructor used default SchoolDbContext
    }

    private SchoolRepository(ISchoolQuerier querier)
    {   // use the provided querier
        // this constructor is useful for unit tests
        this.querier = querier;
    }

    private bool isDisposed = false;
    private readonly ISchoolQuerier;

    #region IDisposable
    public void Dispose()
    {
         this.Dispose(true);
         GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
         if (disposing && !this.isDisposed)
         {
             IDisposable disposableSchool = this.SchoolQuerier as IDisposable;
             if (disposableSchool != null)
             {
                 this.schoolDbContext.Dispose();
             }
             this.isDisposed = true;
         }
    }
    #endregion IDispose

    #region ISchoolQuerier
    public IQueryable<Teacher> Teachers {get{return this.SchoolDbContext.Teachers;}}
    public IQueryable<Student> Students {get{return this.SchoolDbContext.Students;}}

public IQueryable Grades {get {return this.SchoolDbContext.Grades;}}         #endregion ISchoolQuerier     }

用户将使用它与使用SchoolContext的方式类似:

using (var schoolRepository = new SchoolRepository())
{
     var youngStudents = schoolRepository.Students
         .Where(student => student.Birthday > ...)
         .Select(student => new {...});
}

SchoolRepository有两个构造函数。默认构造函数使用默认的DbContext。另一个构造函数使用提供的ISchoolQuerier。这个其他构造函数可用于单元测试,您可以在其中提供具有三个集合的类来测试简单查询

如果您只将ISchoolQuerier接口公开给用户,那么他们不必安装实体框架。您也可以在内部更改访问数据库的方式。不再想要实体框架吗?来吧,从现在开始使用Dapper,用户不会注意到差异!

更改数据库

如果要添加/更新/删除项目,请考虑为其创建单独的界面。只有拥有该界面的人才能更新/删除项目。

这里我使用了之前定义的IId

interface IChangeableSet<T> : IQueryable<T> : where T: IId
{
    public T Get(int id)
    public T Add(T item);
    public T Remove(int Id);
    public T Remove(T item);
}

class DbSet<T> : IChangeableSet<T> : where T: IId
{
    public DbSet(System.Data.Entity.IDbset<T> dbSet)
    {
        this.dbset = dbSet
    }

    private readonly System.Data.Entity.IDbSet<T> dbSet;

    public T Get(int id)
    {
        return this.DbSet.Where(item => item.Id == id).SingleOrDefault();
        // here is where I uses that all items implement IId
    }
    public T Add(T item)
    {
         return this.dbSet.Add(item);
    }
    ... // TODO: fill the other functions
}

SchoolRepository实现了这个接口(最好是显式的实现)

class ChangeableSchoolRepository : IDisposable, ISchoolQuerier
{
    public ChangeableSchoolRepository(SchooldDbContext dbContext)
    {
        this.schoolDbContext = dbContext;
    }
    private readonly SchoolDbContext;

    public IChangeableSet<Teacher> Teachers
    {
        get {return new DbSet(dbContext.Teachers);}
    }
    ... // etc for Students / Grades

    public void SaveChanges()
    {
        return this.SchoolDbcontext.SaveChanges();
    }
}

答案 2 :(得分:0)

我现在使用以下解决方案。通常,我在三个代理对象中封装了我需要的EF和IQueryable的功能。

我现在使用代理对象(DataStore类)来代替直接使用EF DbContext,它为我提供了DbSets(存储库)。

由DbContext公开的EF DbSets包装到一个通用的存储库中,它公开了处理数据所需的功能。 Repository本身派生自代理E​​F DbQuery的DbQueryProxy。

现在我可以用熟悉的方式轻松处理数据:

using (var db = new DataStore())
{
    var students = db.Students.DoSomeLinq(...);
    var teachers = db.Teachers.SomeLinq(...);

    // do some work ...
    // note: The entities in students and teachers are still bound to the DbContext internal to DataStore!

    // optional 
    db.SaveChanges();
}

<强>数据存储

public class DataStore : IDisposable
{
    private readonly DbContext _dbContext;

    #region constructors

    public DataStore()
    {
        _dbContext = new DbContext();
    }

    #endregion constructors

    internal DbContext Context => _dbContext;

    public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : EntityBase => _dbContext.RemoveRange(entities);

    #region db sets

    public Repository<Teacher> Teachers => _dbContext.Teachers; // implicit type conversion from DbSet<TEntity> to Repository<TEntity>!
    public Repository<Student> Students => _dbContext.Students;
    public Repository<Grade> Grades => _dbContext.Grade;

    #endregion db sets

    #region DbContext

    public void Dispose() => _dbContext.Dispose();

    public int SaveChanges() => _dbContext.SaveChanges();

    public Repository<TEntity> Set<TEntity>() where TEntity : EntityBase => _dbContext.Set<TEntity>();

    public override bool Equals(object obj) => _dbContext.Equals(obj);

    public override int GetHashCode() => _dbContext.GetHashCode();

    public override string ToString() => _dbContext.ToString();

    #endregion DbContext
}

<强>库&LT; TEntity&GT;

public class Repository<TEntity> : DbQueryProxy<TEntity> where TEntity : class
{
    private readonly DbSet<TEntity> _DbSet;

    internal Repository(DbSet<TEntity> dbSet) : base(dbSet)
    {
        _DbSet = dbSet;
    }

    private Repository()
    {
    }

    public static implicit operator DbSet<TEntity>(Repository<TEntity> entry) => entry._DbSet;

    public static implicit operator Repository<TEntity>(DbSet<TEntity> entry) => new Repository<TEntity>(entry);

    #region DbSet<TEntity>

    public TEntity Add(TEntity entity) => _DbSet.Add(entity);

    public IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities) => _DbSet.AddRange(entities);

    public TEntity Attach(TEntity entity) => _DbSet.Attach(entity);

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity => _DbSet.Create<TDerivedEntity>();

    public override bool Equals(object obj) => _DbSet.Equals(obj);

    public override int GetHashCode() => _DbSet.GetHashCode();

    public TEntity Find(params object[] keyValues) => _DbSet.Find(keyValues);

    public Task<TEntity> FindAsync(params object[] keyValues) => _DbSet.FindAsync(keyValues);

    public Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues) => _DbSet.FindAsync(cancellationToken, keyValues);

    public TEntity Remove(TEntity entity) => _DbSet.Remove(entity);

    public IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities) => _DbSet.RemoveRange(entities);

    #endregion DbSet<TEntity>
}

<强> DbQueryProxy&LT; TEntity&GT;

public class DbQueryProxy<TResult> : IOrderedQueryable<TResult>, IListSource
{
    private readonly DbQuery<TResult> _DbQuery;

    internal DbQueryProxy(DbQuery<TResult> entry)
    {
        _DbQuery = entry;
    }

    protected DbQueryProxy()
    {
    }

    public static implicit operator DbQuery<TResult>(DbQueryProxy<TResult> entry) => entry._DbQuery;

    public static implicit operator DbQueryProxy<TResult>(DbQuery<TResult> entry) => new DbQueryProxy<TResult>(entry);

    #region Interfaces

    Type IQueryable.ElementType => ((IQueryable)_DbQuery).ElementType;

    Expression IQueryable.Expression => ((IQueryable)_DbQuery).Expression;

    IQueryProvider IQueryable.Provider => ((IQueryable)_DbQuery).Provider;

    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_DbQuery).GetEnumerator();

    IEnumerator<TResult> IEnumerable<TResult>.GetEnumerator() => ((IEnumerable<TResult>)_DbQuery).GetEnumerator();

    bool IListSource.ContainsListCollection => ((IListSource)_DbQuery).ContainsListCollection;

    IList IListSource.GetList() => ((IListSource)_DbQuery).GetList();

    #endregion Interfaces

    #region DbQuery<TResult>

    public string Sql => _DbQuery.Sql;

    public DbQueryProxy<TResult> AsNoTracking() => _DbQuery.AsNoTracking();

    public override bool Equals(object obj) => _DbQuery.Equals(obj);

    public override int GetHashCode() => _DbQuery.GetHashCode();

    public DbQueryProxy<TResult> Include(string path) => _DbQuery.Include(path);

    public override string ToString() => _DbQuery.ToString();

    #endregion DbQuery<TResult>
}