ABP官方文檔翻譯 9.2 Entity Framework Core


Entity Framework Core

介紹

  Abp.EntityFrameworkCore nuget包用來集成EntityFramework(EF)Core 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>的參數。

配置

在Startup類中

  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的action都會執行。所以,你有機會可以根據條件返回不同的連接字符串。

  所以,在什么地方設置默認字符串呢?

在模塊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>)來使用預定義的方法。

自定義倉儲

  如果標准的倉儲方法不滿足需求,你可以為實體創建自定義倉儲類。

應用程序特定基礎倉儲類

  ABP提供了一個基類EfCoreRepositoryBase來簡單的實現倉儲。為了實現IRepository接口,你可以從這個類繼承。但是最好創建自己的基礎類並擴展EfRepositoryBase。這樣,你可以容易的在自己的倉儲中添加shared/common方法。下面為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 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)
}

  注意,我們從EfCoreRepositoryBase<SimpleTaskSystemDbContext,TEntity,TPrimaryKey>繼承。這表明在我們的倉儲中ABP使用SimpleTaskSystemDbContext。

  默認,給定DbContext(在這個例子中為SimpleTaskSystemDbContext)的所有倉儲使用EfCoreRepositoryBase實現。你可以通過在DbContext上添加AutoRepositoryTypes特性來使用自己的倉儲基類來取代默認的倉儲基類。如下所示:

[AutoRepositoryTypes( typeof(IRepository<>), typeof(IRepository<,>), typeof(SimpleTaskSystemEfRepositoryBase<>), typeof(SimpleTaskSystemEfRepositoryBase<,>)
)]
public class SimpleTaskSystemDbContext : AbpDbContext
{
    ...
}

自定義倉儲示例

  為了實現自定義倉儲,需要繼承我們上面定義的特定倉儲基礎類。

  假定,我們有一個Task實體,它可以被分配給一個Person(實體)並且任務有一個狀態(new,assigned,completed...等等)。我們需要編寫一個自定義方法基於一些條件並基於AssisgnedPerson屬性預獲取person實體,在一個數據庫查詢語句中來獲取任務列表。參見示例代碼:

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()方法來獲取Tasks列表。

  你也可以在倉儲方法中使用Context對象引用你的DbContext並直接使用Entity Framework APIs。

  注意:對於分層應用,在domain/core層定義自定義倉儲接口,在EntityFrameworkCore工程中實現它。這樣,你可以從任何工程中注入這個接口而不用引用EFCore。

取代默認倉儲

  即使你有一個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。

倉儲最佳實踐

  • 在任何可能的地方使用默認倉儲。即使你已經有一個實體的默認倉儲(如果你將使用標准倉儲方法)也可以使用默認的倉儲。
  • 總是在應用程序中為自定義倉儲創建倉儲基類,如上定義。
  • 如果你想從domain/application中抽象出EF Core,那么在domain層定義自定義倉儲的接口(在啟動模板中為.Core工程),在.EntityFrameworkCore工程中定義自定義倉儲類。

 

返回主目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM