EntityFramework集成
ABP可以使用ORM框架,它内置集成EntityFramework。本文档将讲解ABP如何使用EntityFramework。假定你对EntityFramework已经有了初级水平。
Nuget包
在ABP中使用Abp.EntityFramework nuget包扩展了EntityFramework。需要将它添加到工程中。最好在一个单独的程序集(dll)中实现EntityFramework并在此程序集中引用Abp.EntityFramework包。
DbContext
如你所知,为了使用EntityFramework,你需要在应用中定义一个DbContext。一个实例DBContext如下所示:
public class SimpleTaskSystemDbContext : AbpDbContext
{
public virtual IDbSet<Person> People { get; set; }
public virtual IDbSet<Task> Tasks { get; set; } public SimpleTaskSystemDbContext()
: base("Default")
{ } public SimpleTaskSystemDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{ } public SimpleTaskSystemDbContext(DbConnection existingConnection)
: base(existingConnection, false)
{ } public SimpleTaskSystemDbContext(DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection)
{ } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); modelBuilder.Entity<Person>().ToTable("StsPeople");
modelBuilder.Entity<Task>().ToTable("StsTasks").HasOptional(t => t.AssignedPerson);
}
}
这是一个正规的DbContext类,除此之外它遵循以下规则:
- 从AbpDbContext集成而不是DbContext。
- 需要有构造函数如上面的示例(构造函数参数命名也需要一样)。说明:
- 默认构造函数传递“Default”到基类作为连接字符串。所以,在web.config/app.config文件中有一个名为“Default”的连接字符串。ABP不使用个这构造函数,但是EF命令行迁移工具和工具命令(如 update-database)使用它。
- 带nameOrConnectiongString参数的构造函数,ABP用来在运行时传递连接名称或字符串。
- 带existingConnection参数的构造函数用于单元测试,ABP不直接使用。
- 带existingConnection和contxtOwnsConnection的构造函数,当使用DbContextEfTransactionStrategy时(参见下面的事务管理部分),ABP用于单数据库多dbcontext的场景来共享同一个连接、事务。
EntityFramework使用常规的方式将类映射到数据库表。除非自定义了功能,你不需要做任何配置。在这个例子中,我们将实体映射到不同的表。Task实体默认映射到Tasks表。但是我们可以改变它映射到StsTasks表。比起使用数据注解,我更喜欢使用流配置。你可以选择自己喜欢的方式。
仓储
仓储用来从高层抽象数据访问。参见仓储文档了解更多。
默认仓储
Abp.EntityFramework为定义在DbContext中的所有实体实现默认仓储。你不必创建仓储类来使用预定义的仓储方法。示例:
public class PersonAppService : IPersonAppService
{
private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
} public void CreatePerson(CreatePersonInput input)
{
person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person);
}
}
PersonAppService构造注入了IRepository<Person>并使用了Insert方法。使用这种方式,你可以简单注入IRepository<TEntity>(或IRepository<TEntity,TPrimaryKey>)并使用预定义方法。
自定义仓储
如果标准的仓储方法不能满足,你可以为你的实体创建自定义仓储类。
应用特定的基础仓储类
ABP提供了一个基础类EfRepositoryBase来实现仓储。为了实现IRepository接口,可以将你的仓储类从这个类集成。但是最好创建自己的基类并扩展EfRepositoryBase。从而,你可以在自己的仓储中添加shared/common方法或重写已经存在的方法。下面是SimpleTaskSystem应用的所有仓储基类的示例:
//Base class for all repositories in my application
public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>
where TEntity : class, IEntity<TPrimaryKey>
{
public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
: base(dbContextProvider)
{
} //add common methods for all repositories
} //A shortcut for entities those have integer Id
public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int>
where TEntity : class, IEntity<int>
{
public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
: base(dbContextProvider)
{
} //do not add any method here, add to the class above (because this class inherits it)
}
注意,我们从EfRepositoryBase<SimpleTaskSystemDbContext,TEntity,TPrimaryKey>继承。这表明ABP在我们的仓储中使用SimpleTaskSystemDbContext。
默认,对于所有给定DbContext(在这个例子中为SimpleTaskDbContext)的仓储都使用EfRepositoryBase实现。你可以在你的DbContext中添加AutoRepository特性来使用自己的仓储基类,如下:
[AutoRepositoryTypes(
typeof(IRepository<>),
typeof(IRepository<,>),
typeof(SimpleTaskSystemEfRepositoryBase<>),
typeof(SimpleTaskSystemEfRepositoryBase<,>)
)]
public class SimpleTaskSystemDbContext : AbpDbContext
{
...
}
自定义仓储示例
为了实现自定义仓储,在你的应用中继承一个以上我们定义的基础仓储类。
假定,我们有一个Task实体,它可以分配给一个Person(实体)并Task有一个State(new,assigned,completed...等等)。我们需要编写一个自定义方法使用一些条件和AssisgnedPerson属性来预获取Tasks的列表。参见下面的示例代码:
public interface ITaskRepository : IRepository<Task, long>
{
List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state);
} public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
public TaskRepository(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
: base(dbContextProvider)
{
} public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
{
var query = GetAll(); if (assignedPersonId.HasValue)
{
query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
} if (state.HasValue)
{
query = query.Where(task => task.State == state);
} return query
.OrderByDescending(task => task.CreationTime)
.Include(task => task.AssignedPerson)
.ToList();
}
}
我们首先定义了ITaskRepostory接口并实现了它。GetAll()返回IQueryable<Task>,然后我们可以使用给定的参数添加一些Where过滤器。最后我们调用ToList()来获取Tasks列表。
你也可以在仓储方法中使用Context对象引用你自己的DbContext,然后直接使用Entity Framework APIs。
注意:对于分层应用,在domain/core层定义自定义仓储接口,在EntityFramework工程中实现它们。从而,你可以在任何工程中注入这个接口而不用引用EF。
仓储最佳实践
- 在可能的地方使用默认仓储。即使你有一个实体的自定义仓储(如果你将使用标准的仓储方法),也可以使用默认的仓储。
- 总是为自定义仓储创建仓储基类,如上面定义的那样。
- 在领域层定义自定义仓储的接口,如果你想将EF从你的domain/application中抽象出来,在EntityFramework工程中自定义仓储类。
事务管理
ABP有内置的工作单元系统来管理数据库连接和事务。Entity Framework有不同的事务管理方式。ABP默认使用TransactionScope方式,但是也有DbContext事务API的内置实现。如果你想切换到DbContext事务API,可以在模块的PreInitialize方法中配置它:
Configuration.ReplaceService<IEfTransactionStrategy, DbContextEfTransactionStrategy>(DependencyLifeStyle.Transient);
记住,在代码中添加"using Abp.Configuration.Startup"以便能使用ReplaceService的泛型扩展方法。
另外,如在DbContext部分所描述的那样,你的DbContext需要有构造函数。
数据存储
因为ABP内置集成EntityFramework,那么它就可以使用EntityFramework所支持的数据存储。我们免费的启动模板设计为使用Sql Server,但是你可以修改他们以便使用不同的数据存储。
例如,如果你想使用MySql,请参见这个文档。