SqlSugar的开发框架本身主要是基于常规关系型数据库设计的框架,支持多种数据库类型的接入,如SqlServer、MySQL、Oracle、PostgreSQL、SQLite等数据库,非关系型数据库的MongoDB数据库也可以作为扩展整合到开发框架里面,通过基类的继承关系很好的封装了相关的基础操作功能,极大的减少相关处理MongoDB的代码,并提供很好的开发效率。本篇随笔介绍如何在SqlSugar的开发框架整合MongoDB数据库的开发。

1、MongDB的简单介绍

MongoDB是一款由C++编写的高性能、开源、无模式的常用非关系型数据库产品,是非关系数据库当中功能最丰富、最像关系数据库的数据库。它扩展了关系型数据库的众多功能,例如:辅助索引、范围查询、排序等。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似Json的Bson格式,因此可以存储比较复杂的数据类型。
MongoDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。并且MongoDB-4.2版本开始已经支持分布式事务功能。

MongoDB数据库有几个简单的概念需要了解一下。

1)MongoDB中的 database 有着和我们熟知的"数据库"一样的概念 (对 Oracle 来说就是 schema)。一个 MongoDB 实例中,可以有零个或多个数据库,每个都作为一个高等容器,用于存储数据。

2)数据库中可以有零个或多个 collections (集合)。集合和传统意义上的 table 基本一致,可以简单的把两者看成是一样的东西。

3)集合是由零个或多个 documents (文档)组成。同样,一个文档可以看成是一 row

4)文档是由零个或多个 fields (字段)组成。,对应的就是关系数据库的 columns

5)Indexes (索引)在 MongoDB 中扮演着和它们在 RDBMS 中一样的角色,都是为了提高查询的效率。

6)Cursors (游标)和上面的五个概念都不一样,但是它非常重要,并且经常被忽视,其中最重要的你要理解的一点是,游标是当你问 MongoDB 拿数据的时候,它会给你返回一个结果集的指针而不是真正的数据,这个指针我们叫它游标,我们可以拿游标做我们想做的任何事情,比如说计数或者跨行之类的,而无需把真正的数据拖下来,在真正的数据上操作。

它们的对比关系图如下所示。

数据在Mongodb里面都是以Json格式方式进行存储的,如下所示是其中的一个记录内容。

BSON格式

Bson是一种类Json的一种二进制形式的存储格式,简称Binary Json,它和Json一样,支持内嵌的文档对象和数组对象,但是Bson有Json没有的一些数据类型,如Date和BinData类型。

Bson可以做为网络数据交换的一种存储形式,这个有点类似于Google的Protocol Buffer,但是Bson是一种schema-less的存储形式,它的优点是灵活性高,但它的缺点是空间利用率不是很理想,Bson有三个特点:轻量性、可遍历性、高效性,

{“hello":"world"} 这是一个Bson的例子,其中"hello"是key name,它一般是cstring类型,字节表示是cstring::= (byte*) "/x00" ,其中*表示零个或多个byte字节,/x00表示结束符;后面的"world"是value值,它的类型一般是string,double,array,binarydata等类型。

MongDB数据库本身支持多种开发语言的驱动,MongoDB有官方的驱动如下:

我们框架基于C#开发,使用的时候,安装MongoDB的C#的驱动 MongoDB.Driver 即可。

在MongoDB数据库的集合里面,都要求文档有一个_id字段,这个是强制性的,而且这个字段的存储类型为ObjectId类型,这个值考虑了分布式的因素,综合了机器码,进程,时间戳等等方面的内容,它的构造如下所示。

ObjectId是一个12字节的  BSON 类型字符串。按照字节顺序,依次代表:

  • 4字节:UNIX时间戳
  • 3字节:表示运行MongoDB的机器
  • 2字节:表示生成此_id的进程
  • 3字节:由一个随机数开始的计数器生成的值

实体基类一般包含了一个属性Id,这个是一个字符串型的对象(也可以使用ObjectId类型,但是为了方便,我们使用字符型,并声明为ObjectId类型即可),由于我们声明了该属性对象为ObjectId类型,那么我们就可以在C#代码里面使用字符串的ID类型了。

2、基于MongoDB数据库的封装处理

以前介绍过,针对常规关系型数据库的开发,在SqlSugar开发框架上,我们设计一些基类,以便重用相关的逻辑代码,通过泛型的约束,可以提供强类型的数据接口,非常方便。

其中MyCrudService里面封装了很多CRUD以及常用的处理方法。类似的处理方式,我们专门为MongoDB数据库的访问操作,设计了一个功能强大的基类即可。

在数据库表的实体对应关系上,我们依旧遵循则相应的设计规则,基类实体采用IEntity<string>的接口类型,因此他们具有一个字符串的Id类型。其他业务对象继承该基类对象即可。

  1. /// <summary>
  2. /// 基于MongoDB的实体类基类
  3. /// </summary>
  4. public class BaseMongoEntity : Entity<string>
  5. {
  6. [BsonId]
  7. [BsonRepresentation(BsonType.ObjectId)]
  8. public override string Id { get; set; }
  9. }

相应的,我们根据常规数据库的基类接口名称,在处理MongoDB数据库的操作接口的时候,名称保持一致性。

其中TEntity为强类型实体类型,而TGetListInput 是定义的一个分页接口。定义的基类接口代码如下所示。

其中接口对象 CurrentApiUser是我们用户上下文的信息,包含一些驻留在ClainPrincipal中的信息,用于记录访问接口的用户信息的。

其他接口定义类似的处理即可。

基类接口的实现类,就是我们需要设计的MongoDB数据库操作类了,初始化类的代码如下所示。

  1. /// <summary>
  2. /// MongoDB基础仓储实现
  3. /// </summary>
  4. /// <typeparam name="TEntity"></typeparam>
  5. public abstract class BaseMongoService<TEntity, TGetListInput> : IBaseMongoService<TEntity, TGetListInput>
  6. where TEntity : class, IEntity<string>, new()
  7. where TGetListInput : IPagedAndSortedResultRequest
  8. {
  9. protected readonly IMongoDBContext mongoContext = NullMongoDBContext.Instance;//空实现
  10. protected IMongoCollection<TEntity> collection; //强类型对象集合
  11. protected IMongoCollection<BsonDocument> bsonCollection; //弱类型集合BsonDocument集合
  12. /// <summary>
  13. /// 当前Api用户信息
  14. /// </summary>
  15. public IApiUserSession CurrentApiUser { get; set; } = NullApiUserSession.Instance;//空实现
  16.  
  17. /// <summary>
  18. /// 构造函数
  19. /// </summary>
  20. protected BaseMongoService()
  21. {
  22. //如果SerivcePovider已经设置值,则获得注入的接口对象
  23. if (ServiceLocator.SerivcePovider != null)
  24. {
  25. CurrentApiUser = ServiceLocator.GetService<IApiUserSession>();
  26. mongoContext = ServiceLocator.GetService<IMongoDBContext>();
  27. collection = mongoContext.GetCollection<TEntity>(typeof(TEntity).Name);//强类型对象集合
  28. bsonCollection = mongoContext.GetCollection<BsonDocument>(typeof(TEntity).Name);//弱类型集合BsonDocument集合
  29. }
  30. }
  31. /// <summary>
  32. /// 获取所有记录
  33. /// </summary>
  34. /// <returns></returns>
  35. public virtual async Task<ListResultDto<TEntity>> GetAllAsync()
  36. {
  37. var all = await collection.FindAsync(Builders<TEntity>.Filter.Empty);
  38. var list = await all.ToListAsync();
  39. return new ListResultDto<TEntity>()
  40. {
  41. Items = list
  42. };
  43. }

我们通过构建对应的强类型Collection和弱类型Collection,来操作实体类和BsonDocument的相关操作的。其中的上下文对象,参考随笔《NoSQL – MongoDB Repository Implementation in .NET Core with Unit Testing example》进行的处理。

  1. /// <summary>
  2. /// MongoDB 上下文对象
  3. /// </summary>
  4. public class MongoDBContext : IMongoDBContext
  5. {
  6. private IMongoDatabase _db { get; set; }
  7. private MongoClient _mongoClient { get; set; }
  8. public IClientSessionHandle Session { get; set; }
  9.  
  10. public MongoDBContext(IOptions<Mongosettings> configuration)
  11. {
  12. _mongoClient = new MongoClient(configuration.Value.Connection);
  13. _db = _mongoClient.GetDatabase(configuration.Value.DatabaseName);
  14. }
  15.  
  16. /// <summary>
  17. /// 获取强类型集合对象
  18. /// </summary>
  19. /// <typeparam name="T">对象类型</typeparam>
  20. /// <param name="name"></param>
  21. /// <returns></returns>
  22. public IMongoCollection<T> GetCollection<T>(string name) where T : class, new()
  23. {
  24. return _db.GetCollection<T>(name);
  25. }
  26. }
  27.  
  28. public interface IMongoDBContext
  29. {
  30. IMongoCollection<T> GetCollection<T>(string name) where T : class, new();
  31. }

通过IOptions 方式我们注入对应的MongoDB数据库配置信息,在appsettings.json中添加根节点内容。

  1. "MongoSettings": {
  2. "Connection": "mongodb://localhost:27017/", //MongoDB连接字符串
  3. "DatabaseName": "iqidi" //MongoDB数据库名称
  4. },

我们在启动Web API的时候,在Program.cs 代码中配置好就可以了。

  1. //MongoDB配置
  2. builder.Services.Configure<Mongosettings>(builder.Configuration.GetSection("MongoSettings"));

默认初始化的IMongoDBContext是一个空接口,我们可以在Web API启动的时候,指定一个具体的实现就可以了

  1. //添加IMongoContext实现类
  2. builder.Services.AddSingleton<IMongoDBContext, MongoDBContext>();

对于基类接口,分页查询获取对应列表数据,是常规的处理方式,默认需要排序、分页,返回对应的数据结构,如下代码所示。

  1. /// <summary>
  2. /// 根据条件获取列表
  3. /// </summary>
  4. /// <param name="input">分页查询条件</param>
  5. /// <returns></returns>
  6. public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input)
  7. {
  8. var query = CreateFilteredQueryAsync(input);
  9. var totalCount = await query.CountAsync();
  10.  
  11. //排序处理
  12. query = ApplySorting(query, input);
  13. //分页处理
  14. query = ApplyPaging(query, input);
  15. //获取列表
  16. var list = await query.ToListAsync();
  17.  
  18. return new PagedResultDto<TEntity>(
  19. totalCount,
  20. list
  21. );
  22. }

其中PagedResultDto 是我们SqlSugar开发框架参照ABP框架定义一个数据结构,包含一个TotalCount数量和一个Items的对象集合。而其中 CreateFilteredQueryAsync 是定义的一个可供业务子类重写的函数,用来处理具体的查询条件。在基类BaseMongoService中只是提供一个默认的可查询对象。

  1. /// <summary>
  2. /// 留给子类实现过滤条件的处理
  3. /// </summary>
  4. /// <returns></returns>
  5. protected virtual IMongoQueryable<TEntity> CreateFilteredQueryAsync(TGetListInput input)
  6. {
  7. return collection.AsQueryable();
  8. }

例如,对于一个具体的业务对象操作类,CustomerService的定义如下所示,并且具体化查询的条件处理,如下代码所示。

  1. namespace SugarProject.Core.MongoDB
  2. {
  3. /// <summary>
  4. /// 基于MongoDB数据库的应用层服务接口实现
  5. /// </summary>
  6. public class CustomerService : BaseMongoService<CustomerInfo, CustomerPagedDto>, ICustomerService
  7. {
  8. /// <summary>
  9. /// 构造函数
  10. /// </summary>
  11. public CustomerService()
  12. {
  13. }
  14. /// <summary>
  15. /// 自定义条件处理
  16. /// </summary>
  17. /// <param name="input">查询条件Dto</param>
  18. /// <returns></returns>
  19. protected override IMongoQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
  20. {
  21. var query = base.CreateFilteredQueryAsync(input);
  22.  
  23. query = query
  24. .Where(t=> !input.ExcludeId.IsNullOrWhiteSpace() && t.Id != input.ExcludeId) //不包含排除ID
  25. .Where(t=> !input.Name.IsNullOrWhiteSpace() && t.Name.Contains(input.Name)) //如需要精确匹配则用Equals //年龄区间查询
  26. .Where(t=> input.AgeStart.HasValue && t.Age >= input.AgeStart.Value)
  27. .Where(t => input.AgeEnd.HasValue && t.Age <= input.AgeEnd.Value)
  28. //创建日期区间查询
  29. .Where(t => input.CreateTimeStart.HasValue && t.CreateTime >= input.CreateTimeStart.Value)
  30. .Where(t => input.CreateTimeEnd.HasValue && t.CreateTime <= input.CreateTimeEnd.Value)
  31. ;
  32.  
  33. return query;
  34. }

这个处理方式类似于常规关系型数据库的处理方式,就是对条件的判断处理。而具体的业务对象模型,和常规框架的实体类很类似。

  1. /// <summary>
  2. /// 客户信息
  3. /// 继承自BaseMongoEntity,拥有Id主键属性
  4. /// </summary>
  5. public class CustomerInfo : BaseMongoEntity
  6. {
  7. /// <summary>
  8. /// 默认构造函数(需要初始化属性的在此处理)
  9. /// </summary>
  10. public CustomerInfo()
  11. {
  12. this.CreateTime = System.DateTime.Now;
  13. }
  14.  
  15. #region Property Members
  16.  
  17. /// <summary>
  18. /// 姓名
  19. /// </summary>
  20. public virtual string Name { get; set; }
  21. /// <summary>
  22. /// 年龄
  23. /// </summary>
  24. public virtual int Age { get; set; }
  25. /// <summary>
  26. /// 创建人
  27. /// </summary>
  28. public virtual string Creator { get; set; }
  29. /// <summary>
  30. /// 创建时间
  31. /// </summary>
  32. public virtual DateTime CreateTime { get; set; }
  33.  
  34. #endregion
  35. }

对于插入和更新操作等常规操作,我们调用普通的Collection操作处理就可以了

  1. /// <summary>
  2. /// 创建对象
  3. /// </summary>
  4. /// <param name="input">实体对象</param>
  5. /// <returns></returns>
  6. public virtual async Task InsertAsync(TEntity input)
  7. {
  8. SetObjectIdIfEmpty(input);//如果Id为空,设置为ObjectId的值
  9. await collection.InsertOneAsync(input);
  10. }
  11.  
  12. /// <summary>
  13. /// 更新记录
  14. /// </summary>
  15. public virtual async Task<bool> UpdateAsync(TEntity input)
  16. {
  17. SetObjectIdIfEmpty(input);//如果Id为空,设置为ObjectId的值
  18.  
  19. //await _dbSet.ReplaceOneAsync(Builders<TEntity>.Filter.Eq("_id", input.Id), input);
  20.  
  21. //要修改的字段
  22. var list = new List<UpdateDefinition<TEntity>>();
  23. foreach (var item in input.GetType().GetProperties())
  24. {
  25. if (item.Name.ToLower() == "id") continue;
  26. list.Add(Builders<TEntity>.Update.Set(item.Name, item.GetValue(input)));
  27. }
  28. var updatefilter = Builders<TEntity>.Update.Combine(list);
  29. var update = await collection.UpdateOneAsync(Builders<TEntity>.Filter.Eq("_id", input.Id), updatefilter);
  30. var result = update != null && update.ModifiedCount > 0;
  31. return result;
  32. }

更新操作,有一种整个替换更新,还有一个是部分更新,它们两者是有区别的。如果对于部分字段的更新,那么操作如下所示 ,主要是利用UpdateDefinition对象来指定需要更新那些字段属性及值等信息。

  1. /// <summary>
  2. /// 封装处理更新的操作(部分字段更新)
  3. /// </summary>
  4. /// <example>
  5. /// var update = Builders<UserInfo>.Update.Set(s => s.Name, newName);
  6. /// </example>
  7. public virtual async Task<bool> UpdateAsync(string id, UpdateDefinition<TEntity> update)
  8. {
  9. var result = await collection.UpdateOneAsync(s => s.Id == id, update, new UpdateOptions() { IsUpsert = true });
  10. return result != null && result.ModifiedCount > 0;
  11. }

根据MongoDB数据库的特性,我们尽量细化对数据库操作的基类接口,定义所需的接口函数即可。

对于Web API的控制器设计,我们在之前的随笔也有介绍,为常规授权处理的BaseApiController,为常规业务CRUD等接口处理的BusinessController,如下所示。

其中ControllerBase是.net core Web API中的标准控制器基类,我们由此派生一个LoginController用于登录授权,而BaseApiController则处理常规接口用户身份信息,而BusinessController则是对标准的增删改查等基础接口进行的封装,我们实际开发的时候,只需要开发编写类似CustomerController基类即可。

而对于 MongoDB的Web API控制器,我们为了方便开发,也设计了同类型的Web API 控制器基类。

其中MongoBaseController基类具有常规的CRUD的接口定义处理,只要继承它就可以了,而如果只是继承BaseApiController这需要自定义控制器接口的方法。

最后我们启动Swagger进行测试对应的接口即可,实际还可以整合在UI中进行测试处理。我们安装MongoDB数据库的管理工具后,可以在MongoDBCompass中进行查询对应数据库的数据。

  1. /// <summary>
  2. /// 客户信息的控制器对象(基于MongoDB),基于BaseApiController,需要自定义接口处理
  3. /// </summary>
  4. [ApiController]
  5. [Route("api/MongoCustomer")]
  6. public class MongoCustomerController : BaseApiController
  7. {
  8. private ICustomerService _service;
  9.  
  10. /// <summary>
  11. /// 构造函数,并注入基础接口对象
  12. /// </summary>
  13. /// <param name="service"></param>
  14. public MongoCustomerController(ICustomerService service)
  15. {
  16. this._service = service;
  17. }
  18.  
  19. /// <summary>
  20. /// 获取所有记录
  21. /// </summary>
  22. [HttpGet]
  23. [Route("all")]
  24. public virtual async Task<ListResultDto<CustomerInfo>> GetAllAsync()
  25. {
  26. //检查用户是否有权限,否则抛出MyDenyAccessException异常
  27. base.CheckAuthorized(AuthorizeKey.ListKey);
  28.  
  29. return await _service.GetAllAsync();
  30. }

而如果继承自MongoBaseController ,那么就会具有基类MongoBaseController 公开的所有控制器方法。

  1. /// <summary>
  2. /// 客户信息的控制器对象(基于MongoDB),基于MongoBaseController,具有常规CRUD操作接口
  3. /// </summary>
  4. [ApiController]
  5. [Route("api/MongoCustomer2")]
  6. public class MongoCustomer2Controller : MongoBaseController<CustomerInfo, CustomerPagedDto>
  7. {
  8. /// <summary>
  9. /// 构造函数,并注入基础接口对象
  10. /// </summary>
  11. /// <param name="service"></param>
  12. public MongoCustomer2Controller(ICustomerService service) : base(service)
  13. {
  14. }
  15. }

早几年前曾经也介绍过该数据库的相关使用,随笔如下所示,有需要也可以了解下。

基于SqlSugar的开发框架循序渐进介绍(27)-- 基于MongoDB的数据库操作整合的更多相关文章

  1. 基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发

    我喜欢在一个项目开发模式成熟的时候,使用代码生成工具Database2Sharp来配套相关的代码生成,对于我介绍的基于SqlSugar的开发框架,从整体架构确定下来后,我就着手为它们量身定做相关的代码 ...

  2. 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理

    我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...

  3. 基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转

    在前面随笔,我们介绍过这个基于SqlSugar的开发框架,我们区分Interface.Modal.Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface ...

  4. 基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口

    在基于SqlSugar的开发框架中,我们设计了一些系统服务层的基类,在基类中会有很多涉及到相关的数据处理操作的,如果需要跟踪具体是那个用户进行操作的,那么就需要获得当前用户的身份信息,包括在Web A ...

  5. 基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录

    在我们对数据进行重要修改调整的时候,往往需要跟踪记录好用户操作日志.一般来说,如对重要表记录的插入.修改.删除都需要记录下来,由于用户操作日志会带来一定的额外消耗,因此我们通过配置的方式来决定记录那些 ...

  6. 基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理

    在早期的随笔就介绍过,把常规页面的内容拆分为几个不同的组件,如普通的页面,包括列表查询.详细资料查看.新增资料.编辑资料.导入资料等页面场景,这些内容相对比较独立,而有一定的代码量,本篇随笔介绍基于V ...

  7. 基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用

    在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于El ...

  8. 基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用

    刚完成一些前端项目的开发,腾出精力来总结一些前端开发的技术点,以及继续完善基于SqlSugar的开发框架循序渐进介绍的系列文章,本篇随笔主要介绍一下基于Vue3+TypeScript的全局对象的注入和 ...

  9. 基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成

    在前面随笔<基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理>中我们已经介绍过,对于相关的业务表的界面代码,我们已经尽可能把不同的业务逻辑 ...

  10. 基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理

    在一个应用系统的开发框架中,往往很多地方需要用到缓存的处理,有些地方是为了便于记录用户的数据,有些地方是为了提高系统的响应速度,如有时候我们在发送一个短信验证码的时候,可以在缓存中设置几分钟的过期时间 ...

随机推荐

  1. 学生管理系统CLI版

    学生管理系统CLI版 学生类 package com.itheima_03; public class Student { String sid; String name; String age; S ...

  2. 08 分布式计算MapReduce--词频统计

    def getText(): txt=open("D:\\test.txt","r").read() txt=txt.lower() punctuation = ...

  3. openvas在centos中扫描单项的python实现

    使用gvm_cli命令来实现 先创建一个空的配置 copy_id = '085569ce-73ed-11df-83c3-002264764cea' new_config = ''' <creat ...

  4. php 验证身份证合法性

    function checkIdcard($num = '') { $length = strlen($num); if ($length == 15) { //如果是15位身份证 //15位身份证没 ...

  5. vue中router.resolve

    resolve是router的一个方法, 返回路由地址的标准化版本.该方法适合编程式导航. let router = this.$router.resolve({ path: '/home', que ...

  6. .net 反射简单介绍

    1.什么是反射 反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类.结构.委托.接口和枚举等)的成员和成员的信息.有了反射,即可对每一个类型了如指掌.另外我还可以直 ...

  7. 决策树(DecisionTree)(附源码)

    决策树(DecisionTree)   决策树所属类别:监督学习,分类 优点:直观易懂,算法简单 缺点:容易过拟合,对连续型数据不太容易实现 实现方案:ID3,CART,C4.5 详细的资料见连接:别 ...

  8. WPF dxe:ComboBoxEdit 选择项后并可编辑值

    有个需要,在ComboBoxEdit中选择多个值,并且要求可以直接在后面添加新的值. 开始的时候绑定列表,设置DisplayMember,不能修改. 然后绑定List<string>,不设 ...

  9. C/S 架构 和 B/S 架构

    C/S架构的理解: 官方称:clinet-server 客户端需要下载的软件: 今日头条,爱奇艺等  能在手机和浏览器打开的软件 B/S架构的理解: 官方称:web-server 客户端为浏览器的: ...

  10. ASP.NET利用JQuery实现AJAX(前台脚本代码)调用后台静态方法

    前台页面的script代码 PS: 如果不需要参数的话,就把data那一行删除 $(function () { //AJAX同步后台 var orderid = parseInt($(this).pa ...