大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的一块垫脚石,我们一起精进。

EF Core集成

EF Core是微软的ORM,可以使用它与主流的数据库提供商合作,如SQL Server、Oracle、MySQL、PostgreSQL和Cosmos DB。当您使用ABP命令行界面(CLI)创建新的ABP解决方案时,它是默认的数据库提供程序。

默认情况下,启动模板使用SQL Server。如果您更喜欢其他的数据库管理系统(DBMS),可以在创建新解决方案时指定-DBMS参数,如下所示:

abp new DemoApp -dbms MySQL

您可以参考ABP的文档,了解最新支持的数据库选项,以及如何切换到其他现成数据库提供程序。

在接下来您将了解到:

  • 如何配置DBMS;
  • 如何定义DbContext类;
  • 如何注册到依赖注入(DI)系统;
  • 如何将实体映射到数据库表;
  • 如何使用Code First和为实体创建自定义存储库;
  • 如何为实体加载相关数据的不同方式。

3.1 配置 DBMS

我们使用AbpDbContextOptions在模块的ConfigureServices方法中配置DBMS。以下示例使用SQL Server作为DBMS进行配置:

Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer();
});

当然,如果希望配置不同的DBMS,那么UseSqlServer()方法调用将有所不同。我们不需要设置连接字符串,因为它是从ConnectionString:Default配置自动获得的。你可以查看appsettings.json文件,以查看和更改连接字符串。

配置了DBMS,但还没有定义DbContext对象,这是在EF Core中使用数据库所必需的,我接下来看看如何配置:

3.2 定义 DbContext

DbContext是EF Core中与数据库交互的主要对象。通常创建一个继承自DbContext的类来创建自己的DbContext。使用ABP框架,我们将继承AbpDbContext。

下面是一个使用ABP框架的DbContext类定义示例:

using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace FormsApp
{
public class FormsAppDbContext : AbpDbContext<FormsAppDbContext>
{
public DbSet<Form> Forms { get; set; }
public FormsAppDbContext(DbContextOptions<FormsAppDbContext> options)
: base(options)
{
}
}
}

FormsAppDbContext继承自AbpDbContext<FormsAppDbContext>AbpDbContext是一个泛型类,将DbContext类型作为泛型参数。它还迫使我们创建一个构造函数。然后,我们就可以为实体添加DbSet属性。

一旦定义了DbContext,我们就应该向DI系统注册它,以便在应用程序中使用它。

3.3 向 DI 注册 DbContext

AddAbpDbContext扩展方法用于向DI系统注册DbContext类。您可以在模块的ConfigureServices方法中使用此方法(它位于启动解决方案的EntityFrameworkCore项目中),如以下代码块所示:

public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<FormsAppDbContext> (options =>
{
//启用默认通用存储库,DDD应始终通过聚合根访问子实体
options.AddDefaultRepositories(); //开启后,非聚合根实体也支持IRepository注入
//options.AddDefaultRepositories(includeAllEntities: true);
});
}

AddDefaultRepositories()用于为与DbContext相关的实体启用默认通用存储库。默认情况下,它仅为聚合根实体启用通用存储库,因为在域驱动设计(DDD)中,子实体应始终通过聚合根进行访问。如果还想将存储库用于其他实体类型,可以将可选的includealentities参数设置为true

options.AddDefaultRepositories(includeAllEntities: true);

使用此选项,意味着您可以为应用程序的任何实体注入IRepository服务。

注意:因为从事关系数据库的开发人员习惯于从所有数据库表中查询,如果要严格应用 DDD 原则,则应始终使用聚合根来访问子实体。

我们已经了解了如何注册DbContext类,我们可以为DbContext类中的所有实体注入和使用IRepository接口。接下来,我们应该首先为实体配置EF Core映射。

3.4 配置实体映射

EF Core是一个对象到关系的映射器,它将实体映射到数据库表。我们可以通过以下两种方式配置这些映射的详细信息:

  • 在实体类上使用数据注释属性
  • 通过重写OnModelCreating方法在内部使用 Fluent API(推荐)

使用数据注释属性会领域层依赖于EF Core,如果这对您来说不是问题,您可以遵循EF Core的文档使用这些属性。为了解脱依赖,同时也为了保持实体类的纯洁度,推荐使用Fluent API方法。

要使用Fluent API方法,可以在DbContext类中重写OnModelCreating方法,如以下代码块所示:

public class FormsAppDbContext : AbpDbContext<FormsAppDbContext>
{
...
//1.override覆盖后,依然会调用父类的base.OnModelCreating(),因为内置审计日志和数据过滤
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder); 2.Fluent API,这里可以继续封装(TODO)
builder.Entity<Form>(b =>
{
b.ToTable("Forms");
b.ConfigureByConvention(); //3.重要,默认配置预定义的Entity或AggregateRoot,无需再配置,剩下的配置就显得整洁而规范了。
b.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
b.HasIndex(x => x.Name);
}); //4.一对多的配置
builder.Entity<Question>(b =>
{
b.ToTable("FormQuestions");
b.ConfigureByConvention();
b.Property(x => x.Title)
.HasMaxLength(200)
.IsRequired();
b.HasOne<Form>() //5.一个问题对应一个表单,一个表单有多个问题。
.WithMany(x => x.Questions)
.HasForeignKey(x => x.FormId)
.IsRequired();
});
}
}

重写OnModelCreating方法时,始终调用base.OnModelCreating(),因为该方法内执行默认配置(如审核日志和数据过滤器)。然后,使用builder对象执行配置。

例如,我们可以为本章中定义的表单类配置映射,如下所示:

builder.Entity<Form>(b => {
b.ToTable("Forms");
b.ConfigureByConvention();
b.Property(x => x.Name).HasMaxLength(100) .IsRequired();
b.HasIndex(x => x.Name);
});

在这里调用b.ConfigureByConvention方法很重要。如果实体派生自ABP的预定义实体或AggregateRoot类,它将配置实体的基本属性。剩下的配置代码非常干净和标准,您可以从EF Core的文档中了解所有细节。

下面是另一个配置实体之间关系的示例:

builder.Entity<Question>(b => {
b.ToTable("FormQuestions");
b.ConfigureByConvention();
b.Property(x => x.Title).HasMaxLength(200).IsRequired();
b.HasOne<Form>().WithMany(x => x.Questions).HasForeignKey(x => x.FormId).IsRequired();
});

在这个例子中,我们定义了表单和问题实体之间的关系:一个表单可以有许多问题,而一个问题属于一个表单。

EF的 Code First Migrations系统提供了一种高效的方法来增量更新数据库,使其与实体保持同步。

Code First相比较传统迁移的好处:

  • 高效快速
  • 增量更新
  • 版本管理

3.5 实现自定义存储库

我们在“自定义存储库”部分创建了一个IFormRepository接口。现在,是时候使用EF Core实现这个存储库接口了。

在解决方案的EF Core集成项目中实现存储库,如下所示:

//1.集成自EfCoreRepository,传入三个泛型参数,继承了所有标准存储库的方法。
public class FormRepository : EfCoreRepository<FormsAppDbContext, Form, Guid>,IFormRepository
{
public FormRepository(IDbContextProvider<FormsAppDbContext> dbContextProvider)
: base(dbContextProvider){ } public async Task<List<Form>> GetListAsync(string name, bool includeDrafts = false)
{
var dbContext = await GetDbContextAsync();
var query = dbContext.Forms.Where(f => f.Name.Contains(name));
if (!includeDrafts)
{
query = query.Where(f => !f.IsDraft);
}
return await query.ToListAsync();
}
}

该类派生自ABP的EfCoreRepository类。通过这种方式,我们继承了所有标准的存储库方法。EfCoreRepository类获得三个通用参数:DbContext类型、实体类型和实体类的PK类型。

FormRepository还实现了IFormRepository,它定义了一个GetListAsync方法,DbContext实例在这个方法中可以使用EF Core API的所有功能。

关于WhereIf的提示:

条件过滤是一种广泛使用的模式,ABP提供了一种很好的WhereIf扩展方法,可以简化我们的代码。

我们可以重写GetListAsync方法,如下代码块所示:

var dbContext = await GetDbContextAsync();
return await dbContext.Forms
.Where(f => f.Name.Contains(name))
.WhereIf(!includeDrafts, f => !f.IsDraft)
.ToListAsync();

因为我们有DbContext实例,所以可以使用它执行结构化查询语言(SQL)命令或存储过程。下面是执行“删除所有表单”命令:

public async Task DeleteAllDraftsAsync()
{
var dbContext = await GetDbContextAsync();
//执行SQL查询
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM Forms WHERE IsDraft = 1");
}

执行存储过程和函数,请参考EF的核心文档学习如何执行存储过程和函数。

一旦实现了IFormRepository,就可以注入并使用它,而不是IRepository<Form,Guid>,如下所示:

1)自定义存储库的调用

public class FormService : ITransientDependency
{
private readonly IFormRepository _formRepository;//自定义仓储库
public FormService(IFormRepository formRepository)
{
_formRepository = formRepository;
} public async Task<List<Form>> GetFormsAsync(string name)
{
return await _formRepository.GetListAsync(name, includeDrafts: true);
}
}

FormService类使用IFormRepository的自定义GetListAsync方法。即使为表单实现了自定义存储库类,仍然可以为该实体注入并使用默认的通用存储库(例如,IRepository<Form,Guid>),尤其是刚开始不熟悉,可以从通用存储库上手,等熟悉后就可以使用自定义存储库。

2)自定义存储库的配置

如果重写EfCoreRepository类中的基方法并,可能会出现一个潜在问题:使用通用存储库的服务将继续使用非重写方法。要防止这种情况,请在向DI注册DbContext时使用AddRepository方法,如下所示:

context.Services.AddAbpDbContext<FormsAppDbContext>(options =>
{
options.AddDefaultRepositories();
//实现仓储库后,建议进行注入
options.AddRepository<Form, FormRepository>();
});

通过这种配置,AddRepository方法将通用存储库重定向到自定义存储库类。

3.7 数据加载

如果您的实体具有指向其他实体的导航属性或具有其他实体的集合,则在使用主实体时,您经常需要访问这些相关实体。例如,前面介绍的表单实体有一组问题实体,您可能需要在使用表单对象时访问这些问题集。

访问相关实体有多种方式,包括:

  • 显式加载
  • 延迟加载
  • 即时加载

1)显式加载

存储库提供了EnsureRepropertyLoadedAsyncEnsureRecollectionLoadedAsync扩展方法,以显式加载导航属性或子集合。

例如,我们可以显式加载表单的问题,如以下代码块所示:

public async Task<IEnumerable<Question>> GetQuestionsAsync(Form form)
{
//
await _formRepository.EnsureCollectionLoadedAsync(form, f => f.Questions);
return form.Questions;
}

如果不用EnsureCollectionLoadedAsyncQuestions可能是空的,如果已经加载过,不会重复加载,所以多次调用对性能没有影响。

2)延迟加载

延迟加载是EF Core的一项功能,它在您首次访问相关属性和集合时加载它们。默认情况下不启用延迟加载。如果要为DbContext启用它,请执行以下步骤:

  1. 在 EF Core 层中安装Microsoft.EntityFrameworkCore.Proxies
  2. 配置时使用 UseLazyLoadingProxies方法
Configure<AbpDbContextOptions>(options =>
{
options.PreConfigure<FormsAppDbContext>(opts =>
{
opts.DbContextOptions.UseLazyLoadingProxies();
});
options.UseSqlServer();
});
  • 确保导航属性和集合属性在实体中是virtual
public class Form : BasicAggregateRoot<Guid>
{
...
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<FormManager> Owners { get; set; }
}

当您启用延迟加载时,您无需再使用显式加载。

延迟加载是一个被讨论过的ORM概念。一些开发人员发现它很实用,而其他人则建议不要使用它。我之所以不使用它,是因为它有一些潜在的问题,比如:

  • 无法使用异步

延迟加载不能使用异步编程,无法使用async/await模式访问属性。因此,它会阻止调用线程,这对于吞吐量和可伸缩性来说是一种糟糕的做法。

  • 1+N性能问题

如果在使用foreach循环之前没有预先加载相关数据,则可能会出现1+N加载问题。1+N加载意味着通过单个数据库操作1次(比如,从数据库中查询实体列表),然后执行一个循环来访问这些实体的导航属性(或集合)。在这种情况下,它会延迟加载每个循环内的相关属性(N=第一次数据库操作中查询的实体数)。因此,进行1+N数据库调用,会显著降低应用程序性能。

  • 断言和代码优化问题

因为您可能不容易看到相关数据何时从数据库加载。我建议采用一种更可控的方法,尽可能使用即时加载

3)即时加载

顾名思义,即时加载是在首先查询主实体时加载相关数据的一种方式。假设您已经创建了一个自定义存储库,以便在从数据库获取表单对象时加载相关问题,如下所示:

  • EF Core层,在自定义仓储库中使用EF Core API
public async Task<Form> GetWithQuestions(Guid formId)
{
var dbContext = await GetDbContextAsync();
return await dbContext.Forms
.Include(f => f.Questions)
.SingleAsync(f => f.Id == formId);
}

自定义存储库方法,可以使用完整的EF Core API。但是,如果您使用的是ABP的存储库,并且不想在应用程序层依赖EF Core,那么就不能使用EF CoreInclude 扩展方法(用于快速加载相关数据)。

假如你不想在应用层依赖EF Core API该怎么办?

在本例中,您有两个选项:

1)IRepository.WithDetailsAsync

IRepositoryWithDetailsSync方法通过包含给定的属性或集合来返回IQueryable实例,如下所示:

public async Task EagerLoadDemoAsync(Guid formId)
{
var queryable = await _formRepository.WithDetailsAsync(f => f.Questions);
var query = queryable.Where(f => f.Id == formId);
var form = await _asyncExecuter.FirstOrDefaultAsync(query);
foreach (var question in form.Questions)
{
//...
}
}

WithDetailsAsync(f=>f.Questions)返回IQueryable<Form>,其中包含form.Questions,因此我们可以安全地循环表单。IAsyncQueryableExecuter在本章的“通用存储库”部分进行了介绍。如果需要,WithDetailsSync方法可以获取多个表达式以包含多个属性。如果需要嵌套包含(EF Core中的ThenClude扩展方法),则不能使用WithDetailsAsync

2)聚合模式

聚合模式将在第10章DDD——领域层中详细介绍。可以简单地理解:一个聚合被认为是一个单一的单元,它与所有子集合一起作为单个单元进行读取和保存。这意味着您在加载Form时总是加载相关Questions

ABP很好地支持聚合模式,并允许您在全局点为实体配置即时加载。我们可以在模块类的ConfigureServices方法中编写以下配置(在解决方案的EntityFrameworkCore项目中):

Configure<AbpEntityOptions>(options =>
{
options.Entity<Form>(orderOptions =>
{
//全局点为实体配置预加载
orderOptions.DefaultWithDetailsFunc = query => query
.Include(f => f.Questions)
.Include(f => f.Owners);
});
});

建议包括所有子集合。如上所示配置DefaultWithDetailsFunc方法后,将发生以下情况

  • 默认情况下,返回单个实体(如GetAsync)的存储库方法将加载相关实体,除非通过在方法调用中将includeDetails参数指定为false来明确禁用该行为。
  • 返回多个实体(如GetListAsync)的存储库方法将允许相关实体的即时加载,而默认情况下它们不会即时加载。

下面是一些例子,获取包含子集合的单一表单,如下所示:

//获取一个包含子集合的表单
var form = await _formRepository.GetAsync(formId); //获取一个没有子集合的表单
var form = await _formRepository.GetAsync(formId, includeDetails: false); //获取没有子集合的表单列表
var forms = await _formRepository.GetListAsync(f => f.Name.StartsWith("A")); //获取包含子集合的表单列表
var forms = await _formRepository.GetListAsync(f => f.Name.StartsWith("A"), includeDetails: true);

聚合模式在大多数情况下简化了应用程序代码,而在需要性能优化的情况下,您可以进行微调。请注意,如果真正实现聚合模式,则不会使用导航属性(指向其他聚合),我们将在第10章DDD——领域层中再次回到这个主题。

了解UoW

UoW是ABP用来启动、管理和处理数据库连接和事务的主要系统。UoW采用环境上下文模式(Ambient Context pattern)设计。这意味着,当我们创建一个新的UoW时,它会创建一个作用域上下文,该上下文中共享所有数据库操作=。UoW中完成的所有操作都会一起提交(成功时)或回滚(异常时)。

配置UoW选项

ASP.NET Core中,默认设置下,HTTP请求被视为一个UoW。ABP在请求开始时启动UoW,如果请求成功完成,则将更改保存到数据库中。如果请求因异常而失败,它将回滚。

ABP根据HTTP请求类型确定数据库事务使用情况。HTTP GET请求不会创建数据库事务。UoW仍然可以工作,但在这种情况下不使用数据库事务。如果您没有对所有其他HTTP请求类型(POSTPUTDELETE和其他)进行配置,则它们将使用数据库事务

HTTP请求 是否创建事务
GET 不创建事务
PUT 创建事务
POST 创建事务

最好不要在GET请求中更改数据库。如果在一个GET请求中进行了多个写操作,但请求以某种方式失败,那么数据库状态可能会处于不一致的状态,因为ABP不会为GET请求创建数据库事务。在这种情况下,可以使用AbpUnitOfWorkDefaultOptionsGET请求启用事务,也可以手动控制UoW。

为GET启用请求事务的配置:

在模块(在数据库集成项目中)的ConfigureServices方法中使用AbpUnitOfWorkDefaultOptions,如下所示:

public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Enabled;
options.Timeout = 300000; // 5 minutes
options.IsolationLevel = IsolationLevel.Serializable;
});
}

TransactionBehavior的三个值:

  • Auto(默认):自动使用事务(为非GET HTTP请求启用事务)
  • Enabled:始终使用事务,即使对于HTTP GET请求
  • Disabled: 从不使用事务

Auto是默认值,对于大多数应用推荐使用。IsolationLevel仅对关系数据库有效。如果未指定,ABP将使用基础提供程序的默认值。最后,Timeout选项允许将事务的默认超时值设置为毫秒,如果UoW操作未在给定的超时值内完成,将引发超时异常。

以上,我们学习了如何在全局配置UOW默认选项,也可以为单个UoW手动配置这些值。

手动控制UoW

对于web应用,一般很少需要手动控制UoW。但是,对于后台作业或非web应用程序,您可能需要自己创建UoW作用域。

使用特性

创建UoW作用域的一种方法是在方法上使用[UnitOfWork]属性,如下所示:

[UnitOfWork(isTransactional: true)]
public async Task DoItAsync()
{     
await _formRepository.InsertAsync(new Form() { ... });     
await _formRepository.InsertAsync(new Form() { ... });
}

如果周围的UoW已经就位,那么UnitOfWork特性将被忽略。否则,ABP会在进入DoItAsync方法之前启动一个新的事务UoW,并在不引发异常的情况下提交事务。如果该方法引发异常,事务将回滚。

使用注入服务

如果要精细控制UoW,可以注入并使用IUnitOfWorkManager服务,如以下代码块所示:

public async Task DoItAsync()
{
using (var uow = _unitOfWorkManager.Begin(requiresNew: true,isTransactional: true, timeout: 15000))
{
await _formRepository.InsertAsync(new Form() { });
await _formRepository.InsertAsync(new Form() { });
await uow.CompleteAsync();
}
}

在本例中,我们将启动一个新的事务性UoW作用域,timeout参数的值为15秒。使用这种用法(requiresNew: true),ABP总是启动一个新的UoW,即使周围已经有一个UoW。如果一切正常,会调用uow.CompleteAsync()方法。如果要回滚当前事务,请使用uow.RollbackAsync()方法。

如前所述,UoW使用环境作用域。您可以使用IUnitOfWorkManager.Current访问此范围内的任何位置的当前UoW。如果没有正在进行的UoW,则可以为null

下面的代码段将SaveChangesAsync方法与IUnitOfWorkManager.Current属性一起使用:

await _unitOfWorkManager.Current.SaveChangesAsync();

我们将所有挂起的更改保存到数据库中。但是,如果这是事务性UoW,那么如果回滚UoW或在UoW范围内引发任何异常,这些更改也会回滚。

小结 & 思考

  • 小结:ABP 框架可以与任何数据库系统一起工作,同时它提供了与EF Core和MongoDB的内置集成包。
  • 思考:假如你不想在应用层依赖EF Core API,或者用的是ABP仓储库该怎么办?

ABP框架之——数据访问基础架构(下)的更多相关文章

  1. 探索ABP基础架构-下

    配置应用程序 ASP.NET Core 的配置系统提供了一个基于键值对的配置方法.它是一个可扩展的系统,可以从各种资源中读取键值对,例如 JSON 设置文件.环境变量.命令行参数等等. 设置配置值 默 ...

  2. PHP数据访问基础知识(20161028)

    数据访问 动态页面的特征:能够读取数据库,网页的内容都是从数据库读出来的,而不是写死的 所有的程序归根结底都是对数据的增删改查 如何用服务器的PHP来操作服务器的MySQL,Apache则是用来管理, ...

  3. .Net ABP 框架 service 无法访问

    最近在看ABP框架,按照文档写好service后,怎么也访问不到,后来发现,忘记把service类设置为public的了! 不写public ABP框架就不能将这个service映射为controll ...

  4. .NET中微软实体框架的数据访问方法

    介绍 本文的目的是解释微软的实体框架提供的三种数据访问方法.网上有好几篇关于这个话题的好文章,但是我想以一个教程的形式更详细地介绍这个话题,这个教程对于开始学习实体框架及其方法的人来说是个入门.我们将 ...

  5. 【ABP框架系列学习】N层架构(3)

    目录 0.引言 1.DDD分层 2.ABP应用构架模型 客户端应用程序(Client Applications) 表现层(Presentation Layer) 分布式服务层(Distributed ...

  6. webapi框架搭建-数据访问ef code first

    webapi框架搭建系列博客 为什么用ef? 我相信很多博友和我一样都有这种“选择困难症”,我曾经有,现在也有,这是技术人的一个通病——总想用“更完美”的方式去实现,导致在技术选择上犹豫不决,或总是推 ...

  7. php数据访问基础

    建一个连接(造一个连接对象) $db = new MySqli("IP地址或者域名,若果是本地则用localhost","用户名","数据库密码&qu ...

  8. EF – 3.EF数据查询基础(下)数据关联

    5.5.1 <关于“数据关联”,你不一定清楚的事> 这讲视频比较全面地介绍了“一对一”.“一对多”和“多对多”三种数据关联类型在关系数据库和Entity Framework数据模型中的实现 ...

  9. php 数据访问基础

    <?php // 创建数据库连接 $con = mysql_connect("localhost",'root','') or die('error:'.mysql_erro ...

  10. ABP框架初始化数据(自定义)

    找到目录:AbpFramework.EntityFramework>Migrations>SeedData,这目录下创建类:DefaultDataCreator.cs using Syst ...

随机推荐

  1. mysql LAST_INSERT_ID 使用与注意事项

    在使用MySQL时,若表中含自增字段(auto_increment类型),则向表中insert一条记录后,可以调用last_insert_id()来获得最近insert的那行记录的自增字段值 $mdb ...

  2. 加密和ssl机制细节

    1.1 背景知识 对称加密:加密解密使用同一密钥,加解密速度快.随着人数增多,密钥数量急增n(n-1)/2 非对称加密:使用公私钥配对加解密,速度慢.公钥是从私钥中提取出来的,一般拿对方公钥加密来保证 ...

  3. hdu 1171 Big Event in HDU(多重背包+二进制优化)

    题目链接:hdu1171 思路:将多重背包转为成完全背包和01背包问题,转化为01背包是用二进制思想,即件数amount用分解成若干个件数的集合,这里面数字可以组合成任意小于等于amount的件数 比 ...

  4. Dev gridControl 按回车增加一行

    将NewItemRowPosition属性设置为Top或Bottom, 在这样的新行中输入数据后,会自动添加到绑定的数据源中的, 如果你希望在按回车时焦点跳至下一列, 只需要设置GridView的Op ...

  5. Windows Service 之 安装失败后的删除

    一个windows服务在卸载之后并不会马上从服务列表中消失掉,而是在服务列表中会显示服务被禁用:这样在你需要再次安装同名服务时,就装不了了,会被提示同名的服务已经存在.如果是在本地安装,这种情况很容易 ...

  6. 腾讯2014在广州站实习生offer经验(TEG-开发背景)

    研究在过去的一年是linux 什么系统编程和网络编程.比较熟悉的语言c/c++,python只写一些测试client.是后台开发类,比方前面笔面的网易CC(面完hr后挂).大概3月15号就在腾讯 jo ...

  7. 【转】UniGUI Session管理說明

    [转]UniGUI Session管理說明 (2015-12-29 15:41:15) 转载▼   分类: uniGUI 台中cmj朋友在uniGUI中文社区QQ群里发布的,转贴至此. UniGUI ...

  8. Luogu P3616 【富金森林公园】

    我们首先考虑一块石头高度变化对每个高度的查询的答案的影响, 即我们要记录,对于每个高度的查询的答案 所以要离散化高度(不然哪开的下数组啊) 不难发现,一次变化的对于不同高度的影响,对于一段连续高度是相 ...

  9. python3爬虫-使用requests爬取起点小说

    import requests from lxml import etree from urllib import parse import os, time def get_page_html(ur ...

  10. proxyTable设置跨域

    如何设置跨域 1.在config--index.js 中配置 proxyTable: { '/api': { target: 'http://www.xxx.com', //目标接口域名 change ...