不同的存储库(Repository)模式实现

目录

介绍

命名、术语和实践

Repo

数据存储

测试工厂和上下文

经典存储库模式实现

实现

请求和结果

命令

项请求

列出查询

处理程序

命令处理程序

项请求处理程序

列表请求处理程序

存储库类替换

测试数据代理

总结

附录

数据存储


介绍

经典存储库模式是在任何应用程序中实现数据库访问的简单方法。它满足小型应用的许多正常设计目标。另一方面,CQSCQRS为更大更复杂的应用程序提供了更复杂但结构良好的设计模式。

在本文中,我将应用CQS中使用的一些基本良好做法开发基本存储库模式,并实现完全泛型提供程序。

这不是DotNetCore中带有一些装饰的重复IRepository实现。

1、没有每个实体类的实现。你不会看到这个:

public class WeatherForecastRepository : GenericRepository<WeatherForecast>, 
                                         IWeatherForcastRepository
{
    public WeatherForecastRepository(DbContextClass dbContext) : base(dbContext) {}
}

public interface IProductRepository : IGenericRepository<WeatherForecast> { } 

2、没有单独的UnitOfWork类:它是内置的。

3、所有标准数据I/O都使用单个数据代理。

4、设计中使用CQS请求、结果和处理程序模式。

命名、术语和实践

  • DI依赖注入
  • CQS:命令/查询分离

代码为:

  • Net 7.0
  • C# 10
  • 启用Nullable

Repo

本文的存储库和最新版本在这里:Blazr.IRepository

数据存储

该解决方案需要一个真实的数据存储进行测试:它实现实体框架内存中数据库。

我是Blazor开发人员,所以我的测试数据类是WeatherForecast。数据提供程序的代码在附录中。

这是DBContext工厂使用的DbContext

public sealed class InMemoryWeatherDbContext : DbContext
{
    public DbSet<WeatherForecast> WeatherForecast { get; set; } = default!;
    public InMemoryWeatherDbContext
       (DbContextOptions<InMemoryWeatherDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => modelBuilder.Entity<WeatherForecast>().ToTable("WeatherForecast");
}

测试工厂和上下文

以下XUnit测试演示了DI中的基本数据存储设置。它:

  1. 设置DI容器。
  2. 从测试提供程序加载数据。
  3. 测试记录计数是否正确。
  4. 测试任意记录是否正确。

[Fact]
public async Task DBContextTest()
{
    // Gets the control test data
    var testProvider = WeatherTestDataProvider.Instance();

    // Build our services container
    var services = new ServiceCollection();

    // Define the DbSet and Server Type for the DbContext Factory
    services.AddDbContextFactory<InMemoryWeatherDbContext>(options
        => options.UseInMemoryDatabase($"WeatherDatabase-{Guid.NewGuid().ToString()}"));

    var rootProvider = services.BuildServiceProvider();

    //define a scoped container
    var providerScope = rootProvider.CreateScope();
    var provider = providerScope.ServiceProvider;

    // get the DbContext factory and add the test data
    var factory = provider.GetService<IDbContextFactory<InMemoryWeatherDbContext>>();
    if (factory is not null)
        WeatherTestDataProvider.Instance().LoadDbContext
                                <InMemoryWeatherDbContext>(factory);

    // Check the data has been loaded
    var dbContext = factory!.CreateDbContext();
    Assert.NotNull(dbContext);

    var count = dbContext.Set<WeatherForecast>().Count();
    Assert.Equal(testProvider.WeatherForecasts.Count(), count);

    // Test an arbitrary record
    var testRecord = testProvider.GetRandomRecord()!;
    var record = await dbContext.Set<WeatherForecast>().SingleOrDefaultAsync
                 (item => item.Uid.Equals(testRecord.Uid));
    Assert.Equal(testRecord, record);

    // Dispose of the resources correctly
    providerScope.Dispose();
    rootProvider.Dispose();
}

经典存储库模式实现

这是我在互联网上找到的一个很好的简洁实现。

public abstract class Repository<T> : IRepository<T> where T : class
    {
        protected readonly DbContextClass _dbContext;

        protected GenericRepository(DbContextClass context)
            => _dbContext = context;

        public async Task<T> GetById(int id)
            => await _dbContext.Set<T>().FindAsync(id);

        public async Task<IEnumerable<T>> GetAll()
            => await _dbContext.Set<T>().ToListAsync();

        public async Task Add(T entity)
             => await _dbContext.Set<T>().AddAsync(entity);

        public void Delete(T entity)
            => _dbContext.Set<T>().Remove(entity);

        public void Update(T entity)
           =>  _dbContext.Set<T>().Update(entity);
    }
}

把它拆开:

  1. 返回null时会发生什么,这意味着什么?
  2. add/update/delete真的成功了吗?我怎么知道?
  3. 您如何处理取消令牌?大多数async方法现在都接受取消令牌。
  4. 当您的DBSet包含一百万条记录时会发生什么(也许DBA昨晚出了点问题)?
  5. 应用程序中的每个数据存储实体都有一个我。

实现

请求和结果

请求对象封装我们请求的内容,结果对象封装我们期望返回的数据和状态信息。它们是records:定义一次,然后消费。

命令

命令是对数据存储进行更改的请求:Create/Update/Delete操作。我们可以这样定义一个:

public record CommandRequest<TRecord>
{
    public required TRecord Item { get; init; }
    public CancellationToken Cancellation { get; set; } = new ();
}

命令仅返回状态信息:不返回数据。我们可以定义这样的结果:

public record CommandResult
{
    public bool Successful { get; init; }
    public string Message { get; init; } = string.Empty;

    private CommandResult() { }

    public static CommandResult Success(string? message = null)
        => new CommandResult { Successful = true, Message= message ?? string.Empty };

    public static CommandResult Failure(string message)
        => new CommandResult { Message = message};
}

在这一点上,值得注意的是返回规则的一个小例外:Id对于插入的记录。如果不使用Guid为记录提供唯一标识符,则数据库生成的Id是状态信息。

项请求

查询是从数据存储中获取数据的请求:无突变。我们可以定义一个项查询,如下所示:

public sealed record ItemQueryRequest
{
    public required Guid Uid { get; init; }
    public CancellationToken Cancellation { get; set; } = new();
}

并返回结果:请求的数据和状态。

public sealed record ItemQueryResult<TRecord>
{
    public TRecord? Item { get; init;} 
    public bool Successful { get; init; }
    public string Message { get; init; } = string.Empty;

    private ItemQueryResult() { }

    public static ItemQueryResult<TRecord> 
           Success(TRecord Item, string? message = null)
        => new ItemQueryResult<TRecord> 
           { Successful=true, Item= Item, Message= message ?? string.Empty };

    public static ItemQueryResult<TRecord> Failure(string message)
        => new ItemQueryResult<TRecord> { Message = message};
}

列出查询

列表查询带来了一些额外的挑战:

  1. 他们永远不应该要求一切。在边缘条件下,表中可能有1,000,000+行。每个请求都应该受到限制。该请求定义StartIndexPageSize约束数据并提供分页。如果将页面大小设置为1,000,000,数据管道和前端是否会正常处理它?
  2. 他们需要处理排序和过滤。请求将这些表达式定义为Linq表达式。

public sealed record ListQueryRequest<TRecord>
{
    public int StartIndex { get; init; } = 0;
    public int PageSize { get; init; } = 1000;
    public CancellationToken Cancellation { get; set; } = new ();
    public bool SortDescending { get; } = false;
    public Expression<Func<TRecord, bool>>? FilterExpression { get; init; }
    public Expression<Func<TRecord, object>>? SortExpression { get; init; }
}

结果返回项、项总数(用于分页)和状态信息。Items始终返回为IEnumerable

public sealed record ListQueryResult<TRecord>
{
    public IEnumerable<TRecord> Items { get; init;} = Enumerable.Empty<TRecord>();  
    public bool Successful { get; init; }
    public string Message { get; init; } = string.Empty;
    public long TotalCount { get; init; }

    private ListQueryResult() { }

    public static ListQueryResult<TRecord> Success
      (IEnumerable<TRecord> Items, long totalCount, string? message = null)
        => new ListQueryResult<TRecord> {Successful=true,  Items= Items, 
           TotalCount = totalCount, Message= message ?? string.Empty };

    public static ListQueryResult<TRecord> Failure(string message)
        => new ListQueryResult<TRecord> { Message = message};
}

处理程序

处理程序是处理请求和返回结果的小型单一用途类。他们从更高级别的数据代理中抽象出细节执行。

命令处理程序

接口提供抽象。

public interface ICreateRequestHandler
{
    public ValueTask<CommandResult> ExecuteAsync<TRecord>
                                    (CommandRequest<TRecord> request)
        where TRecord : class, new();
}

并且实现完成实际工作。

  1. 注入DBContext工厂。
  2. 通过DbContext工厂实现工作单元Db上下文。
  3. 使用上下文上的Add方法将记录添加到EF。
  4. 调用SaveChangesAsync,传入取消令牌,并期望报告单个更改。
  5. 在出现问题时提供状态信息。

public sealed class CreateRequestHandler<TDbContext>
    : ICreateRequestHandler
    where TDbContext : DbContext
{
    private readonly IDbContextFactory<TDbContext> _factory;

    public CreateRequestHandler(IDbContextFactory<TDbContext> factory)
        => _factory = factory;

    public async ValueTask<CommandResult> ExecuteAsync<TRecord>
                                          (CommandRequest<TRecord> request)
        where TRecord : class, new()
    {
        if (request == null)
            throw new DataPipelineException
            ($"No CommandRequest defined in {this.GetType().FullName}");

        using var dbContext = _factory.CreateDbContext();

        dbContext.Add<TRecord>(request.Item);
        return await dbContext.SaveChangesAsync(request.Cancellation) == 1
            ? CommandResult.Success("Record Updated")
            : CommandResult.Failure("Error updating Record");
    }
}

UpdateDelete处理程序是相同的,但使用不同的dbContext方法:UpdateRemove

项请求处理程序

接口。

public interface IItemRequestHandler
{
    public ValueTask<ItemQueryResult<TRecord>> ExecuteAsync<TRecord>
                                               (ItemQueryRequest request)
        where TRecord : class, new();
}

和服务器实现。注意:

  1. 注入DBContext工厂。
  2. 通过DbContext工厂实现工作单元Db上下文。
  3. 关闭跟踪。此事务不涉及任何突变。
  4. 检查它是否可以使用Id来获取项——记录实现IGuidIdentity
  5. 如果没有,请尝试FindAsync,它使用内置的Key方法来获取记录。
  6. 在出现问题时提供状态信息。

public sealed class ItemRequestHandler<TDbContext>
    : IItemRequestHandler
    where TDbContext : DbContext
{
    private readonly IDbContextFactory<TDbContext> _factory;

    public ItemRequestHandler(IDbContextFactory<TDbContext> factory)
        => _factory = factory;

    public async ValueTask<ItemQueryResult<TRecord>> 
                 ExecuteAsync<TRecord>(ItemQueryRequest request)
        where TRecord : class, new()
    {
        if (request == null)
            throw new DataPipelineException
            ($"No ListQueryRequest defined in {this.GetType().FullName}");

        using var dbContext = _factory.CreateDbContext();
        dbContext.ChangeTracker.QueryTrackingBehavior = 
                                QueryTrackingBehavior.NoTracking;

        TRecord? record = null;

        // first check if the record implements IGuidIdentity. 
        // If so, we can do a cast and then do the query via the Uid property directly.
        if ((new TRecord()) is IGuidIdentity)
            record = await dbContext.Set<TRecord>().SingleOrDefaultAsync
            (item => ((IGuidIdentity)item).Uid == request.Uid, request.Cancellation);

        // Try and use the EF FindAsync implementation
        if (record is null)
            record = await dbContext.FindAsync<TRecord>(request.Uid);

        if (record is null)
            return ItemQueryResult<TRecord>.Failure
                       ("No record retrieved");

        return ItemQueryResult<TRecord>.Success(record);
    }
}

列表请求处理程序

接口。

public interface IListRequestHandler
{
    public ValueTask<ListQueryResult<TRecord>> ExecuteAsync<TRecord>
                              (ListQueryRequest<TRecord> request)
        where TRecord : class, new();
}

和实现。

请注意,有两种内部方法:

  • _getItemsAsync获取项。这将构建一个IQueryable对象并返回一个具体化的IEnumerable。必须先执行查询,然后工厂才会释放DbContext
  • _getCountAsync获取基于筛选器的所有记录的计数。

private async ValueTask<IEnumerable<TRecord>> _getItemsAsync<TRecord>
                                   (ListQueryRequest<TRecord> request)
    where TRecord : class, new()
{
    if (request == null)
        throw new DataPipelineException
              ($"No ListQueryRequest defined in {this.GetType().FullName}");

    using var dbContext = _factory.CreateDbContext();
    dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

    IQueryable<TRecord> query = dbContext.Set<TRecord>();
    if (request.FilterExpression is not null)
        query = query
            .Where(request.FilterExpression)
            .AsQueryable();

    if (request.SortExpression is not null)

        query = request.SortDescending
            ? query.OrderByDescending(request.SortExpression)
            : query.OrderBy(request.SortExpression);

    if (request.PageSize > 0)
        query = query
            .Skip(request.StartIndex)
            .Take(request.PageSize);

    return query is IAsyncEnumerable<TRecord>
        ? await query.ToListAsync()
        : query.ToList();
}

存储库类替换

首先是接口。

非常重要的一点是每个方法上的泛型TRecord定义,而不是接口上的通用定义。这消除了对实体特定实现的需求。

public interface IDataBroker
{
    public ValueTask<ListQueryResult<TRecord>> GetItemsAsync<TRecord>
           (ListQueryRequest<TRecord> request) where TRecord : class, new();
    public ValueTask<ItemQueryResult<TRecord>> GetItemAsync<TRecord>
           (ItemQueryRequest request) where TRecord : class, new();
    public ValueTask<CommandResult> UpdateItemAsync<TRecord>
           (CommandRequest<TRecord> request) where TRecord : class, new();
    public ValueTask<CommandResult> CreateItemAsync<TRecord>
           (CommandRequest<TRecord> request) where TRecord : class, new();
    public ValueTask<CommandResult> DeleteItemAsync<TRecord>
           (CommandRequest<TRecord> request) where TRecord : class, new();
}

和实现。每个处理程序都在DI中注册并注入到代理中。

public sealed class RepositoryDataBroker : IDataBroker
{
    private readonly IListRequestHandler _listRequestHandler;
    private readonly IItemRequestHandler _itemRequestHandler;
    private readonly IUpdateRequestHandler _updateRequestHandler;
    private readonly ICreateRequestHandler _createRequestHandler;
    private readonly IDeleteRequestHandler _deleteRequestHandler;

    public RepositoryDataBroker(
        IListRequestHandler listRequestHandler,
        IItemRequestHandler itemRequestHandler,
        ICreateRequestHandler createRequestHandler,
        IUpdateRequestHandler updateRequestHandler,
        IDeleteRequestHandler deleteRequestHandler)
    {
        _listRequestHandler = listRequestHandler;
        _itemRequestHandler = itemRequestHandler;
        _createRequestHandler = createRequestHandler;
        _updateRequestHandler = updateRequestHandler;
        _deleteRequestHandler = deleteRequestHandler;
    }

    public ValueTask<ItemQueryResult<TRecord>> GetItemAsync<TRecord>
           (ItemQueryRequest request) where TRecord : class, new()
        => _itemRequestHandler.ExecuteAsync<TRecord>(request);

    public ValueTask<ListQueryResult<TRecord>> GetItemsAsync<TRecord>
           (ListQueryRequest<TRecord> request) where TRecord : class, new()
        => _listRequestHandler.ExecuteAsync<TRecord>(request);

    public ValueTask<CommandResult> CreateItemAsync<TRecord>
           (CommandRequest<TRecord> request) where TRecord : class, new()
        => _createRequestHandler.ExecuteAsync<TRecord>(request);

    public ValueTask<CommandResult> UpdateItemAsync<TRecord>
           (CommandRequest<TRecord> request) where TRecord : class, new()
        => _updateRequestHandler.ExecuteAsync<TRecord>(request);

    public ValueTask<CommandResult> DeleteItemAsync<TRecord>
           (CommandRequest<TRecord> request) where TRecord : class, new()
        => _deleteRequestHandler.ExecuteAsync<TRecord>(request);
}

测试数据代理

现在,我们可以为数据代理定义一组测试。我在这里包括了两个。其余的都在存储库中。

创建根DI容器并填充数据库的前两种方法。

private ServiceProvider BuildRootContainer()
{
    var services = new ServiceCollection();

    // Define the DbSet and Server Type for the DbContext Factory
    services.AddDbContextFactory<InMemoryWeatherDbContext>(options
      => options.UseInMemoryDatabase($"WeatherDatabase-{Guid.NewGuid().ToString()}"));
    // Define the Broker and Handlers
    services.AddScoped<IDataBroker, RepositoryDataBroker>();
    services.AddScoped<IListRequestHandler, 
             ListRequestHandler<InMemoryWeatherDbContext>>();
    services.AddScoped<IItemRequestHandler, 
             ItemRequestHandler<InMemoryWeatherDbContext>>();
    services.AddScoped<IUpdateRequestHandler, 
             UpdateRequestHandler<InMemoryWeatherDbContext>>();
    services.AddScoped<ICreateRequestHandler, 
             CreateRequestHandler<InMemoryWeatherDbContext>>();
    services.AddScoped<IDeleteRequestHandler, 
             DeleteRequestHandler<InMemoryWeatherDbContext>>();

    // Create the container
    return services.BuildServiceProvider();
}

private IDbContextFactory<InMemoryWeatherDbContext> 
        GetPopulatedFactory(IServiceProvider provider)
{
    // get the DbContext factory and add the test data
    var factory = provider.GetService<IDbContextFactory<InMemoryWeatherDbContext>>();
    if (factory is not null)
        WeatherTestDataProvider.Instance().LoadDbContext
                                <InMemoryWeatherDbContext>(factory);

    return factory!;
}

GetItems测试:

[Fact]
public async Task GetItemsTest()
{
    // Get our test provider to use as our control
    var testProvider = WeatherTestDataProvider.Instance();

    // Build the root DI Container
    var rootProvider = this.BuildRootContainer();

    //define a scoped container
    var providerScope = rootProvider.CreateScope();
    var provider = providerScope.ServiceProvider;

    // get the DbContext factory and add the test data
    var factory = this.GetPopulatedFactory(provider);

    // Check we can retrieve the first 1000 records
    var dbContext = factory!.CreateDbContext();
    Assert.NotNull(dbContext);

    var databroker = provider.GetRequiredService<IDataBroker>();

    var request = new ListQueryRequest<WeatherForecast>();
    var result = await databroker.GetItemsAsync<WeatherForecast>(request);

    Assert.NotNull(result);
    Assert.Equal(testProvider.WeatherForecasts.Count(), result.TotalCount);

    providerScope.Dispose();
    rootProvider.Dispose();
}

AddItem测试:

[Fact]
public async Task AddItemTest()
{
    // Get our test provider to use as our control
    var testProvider = WeatherTestDataProvider.Instance();

    // Build the root DI Container
    var rootProvider = this.BuildRootContainer();

    //define a scoped container
    var providerScope = rootProvider.CreateScope();
    var provider = providerScope.ServiceProvider;

    // get the DbContext factory and add the test data
    var factory = this.GetPopulatedFactory(provider);

    // Check we can retrieve the first 1000 records
    var dbContext = factory!.CreateDbContext();
    Assert.NotNull(dbContext);

    var databroker = provider.GetRequiredService<IDataBroker>();

    // Create a Test record
    var newRecord = new WeatherForecast { Uid = Guid.NewGuid(), 
                    Date = DateOnly.FromDateTime(DateTime.Now), 
                    TemperatureC = 50, Summary = "Add Testing" };

    // Add the Record
    {
        var request = new CommandRequest<WeatherForecast>() { Item = newRecord };
        var result = await databroker.CreateItemAsync<WeatherForecast>(request);

        Assert.NotNull(result);
        Assert.True(result.Successful);
    }

    // Get the new record
    {
        var request = new ItemQueryRequest() { Uid = newRecord.Uid };
        var result = await databroker.GetItemAsync<WeatherForecast>(request);

        Assert.Equal(newRecord, result.Item);
    }

    // Check the record count has incremented
    {
        var request = new ListQueryRequest<WeatherForecast>();
        var result = await databroker.GetItemsAsync<WeatherForecast>(request);

        Assert.NotNull(result);
        Assert.Equal(testProvider.WeatherForecasts.Count() + 1, result.TotalCount);
    }

    providerScope.Dispose();
    rootProvider.Dispose();
}

总结

我在这里介绍的是一个混合存储库模式。它保持了存储库模式的简单性,并添加了一些最佳的CQS模式功能。

将细节EFLinq代码抽象到各个处理程序可以使类保持小、简洁和单一用途。

单个数据代理简化了核心域和表示域的数据管道配置。

对于那些认为通过EF实现任何数据库管道都是一种反模式的人,我的答案是:我将EF用作另一个对象请求代理[ORB]。您可以将此管道插入DapperLinqToDb... 。我从不在我的数据/基础架构域中构建核心业务逻辑代码(数据关系):[个人观点]疯狂的想法。

附录

数据存储

测试系统实现实体框架内存中数据库。

我是Blazor开发人员,所以我的演示数据类自然是WeatherForecast。这是我的数据类。请注意,这是不可变性的记录,我设置了一些任意默认值以进行测试。

public sealed record WeatherForecast : IGuidIdentity
{
    [Key] public Guid Uid { get; init; } = Guid.Empty;
    public DateOnly Date { get; init; } = DateOnly.FromDateTime(DateTime.Now);
    public int TemperatureC { get; init; } = 60;
    public string? Summary { get; init; } = <span class="pl-pds">"Testing";
}

首先是一个生成数据集的类。这是一个单一实例模式类(不是DI单一实例)。诸如测试之类的GetRandomRecord方法。

public sealed class WeatherTestDataProvider
{
    private int RecordsToGenerate;

    public IEnumerable<WeatherForecast> WeatherForecasts { get; private set; } = 
                                        Enumerable.Empty<WeatherForecast>();

    private WeatherTestDataProvider()
        => this.Load();

    public void LoadDbContext<TDbContext>(IDbContextFactory<TDbContext> factory) 
                where TDbContext : DbContext
    {
        using var dbContext = factory.CreateDbContext();

        var weatherForcasts = dbContext.Set<WeatherForecast>();

        // Check if we already have a full data set
        // If not clear down any existing data and start again
        if (weatherForcasts.Count() == 0)
        {
            dbContext.AddRange(this.WeatherForecasts);
            dbContext.SaveChanges();
        }
    }

    public void Load(int records = 100)
    {
        RecordsToGenerate = records;

        if (WeatherForecasts.Count() == 0)
            this.LoadForecasts();
    }

    private void LoadForecasts()
    {
        var forecasts = new List<WeatherForecast>();

        for (var index = 0; index < RecordsToGenerate; index++)
        {
            var rec = new WeatherForecast
            {
                Uid = Guid.NewGuid(),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)],
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
            };
            forecasts.Add(rec);
        }

        this.WeatherForecasts = forecasts;
    }

    public WeatherForecast GetForecast()
    {
        return new WeatherForecast
        {
            Uid = Guid.NewGuid(),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)],
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(-1)),
            TemperatureC = Random.Shared.Next(-20, 55),
        };
    }

    public WeatherForecast? GetRandomRecord()
    {
        var record = new WeatherForecast();
        if (this.WeatherForecasts.Count() > 0)
        {
            var ran = new Random().Next(0, WeatherForecasts.Count());
            return this.WeatherForecasts.Skip(ran).FirstOrDefault();
        }
        return null;
    }

    private static WeatherTestDataProvider? _weatherTestData;

    public static WeatherTestDataProvider Instance()
    {
        if (_weatherTestData is null)
            _weatherTestData = new WeatherTestDataProvider();

        return _weatherTestData;
    }

    public static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", 
         "Hot", "Sweltering", "Scorching"
    };

}

DbContext

public sealed class InMemoryWeatherDbContext
    : DbContext
{
    public DbSet<WeatherForecast> WeatherForecast { get; set; } = default!;
    public InMemoryWeatherDbContext
    (DbContextOptions<InMemoryWeatherDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => modelBuilder.Entity<WeatherForecast>().ToTable("WeatherForecast");
}

https://www.codeproject.com/Articles/5350000/A-Different-Repository-Pattern-Implementation

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值