实体框架(Core)

介绍

所述  Abp.EntityFrameworkCore  NuGet包被用于集成实体框架(EF)核心ORM框架。安装此软件包后,还需要为 AbpEntityFrameworkCoreModule 添加  DependsOn属性。

DbContext

EF Core要求您定义从DbContext派生的类。在ABP中,我们需要从AbpDbContext派生,如下所示:

public class MyDbContext : AbpDbContext
{
    public DbSet<Product> Products { get; set; }

    public MyDbContext(DbContextOptions<MyDbContext> options)
        : base(options)
    {
    }
}

构造函数应该得到一个DbContextOptions <T>,如上所示。参数名称必须是选项。由于ABP将其作为匿名对象参数提供,因此无法更改它。

组态

启动类

在ConfigureServices方法中对服务集合使用AddAbpDbContext方法,如下所示:

services.AddAbpDbContext<MyDbContext>(options =>
{
    options.DbContextOptions.UseSqlServer(options.ConnectionString);
});

对于非Web项目,我们不会有Startup类。在这种情况下,我们可以在我们的模块  类中使用Configuration.Modules.AbpEfCore()。AddDbContext方法  来配置DbContext,如下所示:

Configuration.Modules.AbpEfCore().AddDbContext<MyDbContext>(options =>
{
    options.DbContextOptions.UseSqlServer(options.ConnectionString);
});

我们使用给定的连接字符串和Sql Server作为数据库提供程序。通常,options.ConnectionString是默认的连接字符串(请参阅下一节)。但是,ABP可以使用IConnectionStringResolver来确定它。可以更改此行为,并可以动态确定连接字符串。只要创建DbContext实例,就会调用传递给AddDbContext的操作。您还有机会有条件地返回不同的连接字符串。

我们在哪里设置默认连接字符串?

在模块PreInitialize方法中

您可以在模块的PreInitialize方法中执行此操作,如下所示:

public class MyEfCoreAppModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.DefaultNameOrConnectionString = GetConnectionString("Default");
        ...
    }
}

您可以定义GetConnectionString方法,该方法只返回配置文件中的连接字符串。这通常在appsettings.json文件中。

存储库用于从较高层抽象数据访问。有关 详细信息,请参阅  存储库文档。 

默认存储库

Abp.EntityFrameworkCore  为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>)并使用预定义的方法。

自定义存储库

如果标准存储库方法不足,则可以为实体创建自定义存储库类。

特定于应用程序的库存储库类

ASP.NET Boilerplate提供了一个基类EfCoreRepositoryBase,可以轻松实现存储库。要实现IRepository接口,您只需从此类派生存储库即可。最好创建自己的扩展EfRepositoryBase的基类。这样,您可以轻松地将共享和常用方法添加到您的存储库。这是SimpleTaskSystem  应用程序的所有存储库的示例基类  

//Base class for all repositories in my application
public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<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 which have an integer Id
public class SimpleTaskSystemRepositoryBase<TEntity> : SimpleTaskSystemRepositoryBase<TEntity, int>
    where TEntity : class, IEntity<int>
{
    public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
        : base(dbContextProvider)
    {
    }

    //do not add a method here, add it to the class above (because this class inherits it)
}

请注意,我们继承自EfCoreRepositoryBase <SimpleTaskSystemDbContext,TEntity,TPrimaryKey>。这将ASP.NET Boilerplate设置为在我们的存储库中使用SimpleTaskSystemDbContext。

默认情况下,您使用EfCoreRepositoryBase实现给定DbContext(本示例中为SimpleTaskSystemDbContext)的所有存储库。您可以通过将AutoRepositoryTypes属性添加到DbContext来将其替换为您自己的存储库基类,如下所示:

[AutoRepositoryTypes(
    typeof(IRepository<>),
    typeof(IRepository<,>),
    typeof(SimpleTaskSystemEfRepositoryBase<>),
    typeof(SimpleTaskSystemEfRepositoryBase<,>)
)]
public class SimpleTaskSystemDbContext : AbpDbContext
{
    ...
}
自定义存储库示例

要实现自定义存储库,只需从特定于应用程序的基本存储库类派生它,就像我们在上面创建的那样。

假设我们有一个可以分配给Person(实体)的Task实体。我们还有一个具有状态的任务(新的,已分配的,已完成的......等等)。我们可能需要编写一个自定义方法来获取具有某些条件的Tasks列表,并在单个数据库查询中包含预先获取(包含)的AssisgnedPerson属性。请参阅以下代码:

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

我们首先定义ITaskRepository然后实现它。GetAll()方法返回IQueryable <Task>。然后我们使用给定的参数添加一些Where过滤器。最后,我们调用ToList()来获取任务列表。

您还可以使用存储库方法中的Context对象来访问DbContext并直接使用Entity Framework API。 

注意:在域/核心层中定义自定义存储库接口,在EntityFrameworkCore项目中为分层应用程序实现它。这样,您可以从任何项目注入接口,而无需引用EF Core。

替换默认存储库

即使您创建了如上所示的TaskRepository,任何类仍然可以  注入  IRepository <Task,long>并使用它。在大多数情况下,这不是问题。但是,如果您对自定义存储库中的基本方法进行了覆盖,该怎么办?假设您在自定义存储库中覆盖了Delete方法,以便在删除时添加自定义行为。如果类注入IRepository <Task,long>并使用默认存储库来删除任务,则自定义行为将不起作用。要解决此问题,您可以使用默认存储库实现替换自定义存储库实现,如下所示:

Configuration.ReplaceService<IRepository<Task, Guid>>(() =>
{
    IocManager.IocContainer.Register(
        Component.For<IRepository<Task, Guid>, ITaskRepository, TaskRepository>()
            .ImplementedBy<TaskRepository>()
            .LifestyleTransient()
    );
});

我们为IRepository <Task,Guid>,ITaskRepository和TaskRepository注册了TaskRepository。这样,可以注入其中任何一个来使用TaskRepository。

存储库最佳实践

其他数据库集成

本文档和示例基于使用MS SQL Server。对于不同的数据库集成,可以遵循以下文档。

nidie.com.cn - 用心与你沟通