基于ASP.NET MVC 5与EF,使用模板类实现通用控制器基类的需求
基于ASP.NET MVC 5和Entity Framework的通用基类实现方案
这思路太靠谱了!通过抽象基类封装重复的保存逻辑,再结合泛型约束处理带公共属性的实体,完全能解决多控制器代码冗余的问题。我之前在类似项目里这么干过,效果很好,咱们一步步来实现:
1. 先定义通用实体约束
因为所有EF实体都包含Id和ReferenceTable属性,先搞个接口来统一约束,这样后续的基类和GenericMapTable都能基于这个接口识别实体结构:
// 定义公共实体接口,包含所有实体都有的属性 public interface IEntityWithReference { int Id { get; set; } string ReferenceTable { get; set; } // 如果还有其他公共属性,也可以在这里添加 } // 让你的EF实体类都实现这个接口 public class Product : IEntityWithReference { public int Id { get; set; } public string ReferenceTable { get; set; } // 实体专属属性,比如产品名称、价格等 public string ProductName { get; set; } public decimal Price { get; set; } }
2. 编写抽象基控制器
让这个基类继承自Controller,封装通用的保存逻辑,用泛型约束确保传入的实体都实现了IEntityWithReference:
public abstract class BaseEntityController<TEntity> : Controller where TEntity : class, IEntityWithReference, new() { protected readonly YourDbContext _dbContext; // 通过构造函数注入EF上下文,避免手动创建上下文带来的问题 public BaseEntityController(YourDbContext dbContext) { _dbContext = dbContext; } // 通用保存方法,子类直接调用即可 protected virtual async Task<ActionResult> SaveEntity(TEntity entity) { if (!ModelState.IsValid) { return View(entity); } // 根据Id判断是新增还是编辑 if (entity.Id == 0) { _dbContext.Set<TEntity>().Add(entity); } else { // 结合ReferenceTable查找对应实体,确保修改的是正确记录 var existingEntity = await _dbContext.Set<TEntity>() .FirstOrDefaultAsync(e => e.Id == entity.Id && e.ReferenceTable == entity.ReferenceTable); if (existingEntity == null) { ModelState.AddModelError("", "未找到匹配的实体记录"); return View(entity); } // 更新实体属性,这里可以用AutoMapper简化赋值,也可以手动处理 _dbContext.Entry(existingEntity).CurrentValues.SetValues(entity); } await _dbContext.SaveChangesAsync(); return RedirectToAction("Index"); } }
3. 子类控制器继承基类
其他业务控制器直接继承这个基类,就能复用通用的保存逻辑了,不用再重复写相似的代码:
public class ProductController : BaseEntityController<Product> { public ProductController(YourDbContext dbContext) : base(dbContext) { } // 新增产品操作 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create(Product product) { return await SaveEntity(product); } // 编辑产品操作 [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit(Product product) { return await SaveEntity(product); } // 其他业务方法,比如列表、详情等... public async Task<ActionResult> Index() { var products = await _dbContext.Products.ToListAsync(); return View(products); } }
4. 适配GenericMapTable
如果你的GenericMapTable是用来做实体映射或者结构识别的,同样给它加上泛型约束,确保它处理的实体都包含Id和ReferenceTable:
public class GenericMapTable<TEntity> where TEntity : IEntityWithReference { public void Map(TEntity source, TEntity target) { // 统一映射公共属性 target.Id = source.Id; target.ReferenceTable = source.ReferenceTable; // 这里可以扩展其他属性的映射逻辑 } }
几个注意点
- 依赖注入:一定要通过构造函数注入DbContext,别在基类里手动new,这样能保证上下文实例的一致性,避免并发问题。
- 自定义扩展:如果某个控制器需要特殊的保存逻辑,比如额外的验证或者业务规则,可以重写基类的
SaveEntity方法,覆盖默认逻辑。 - 验证逻辑:基类已经处理了ModelState验证,但子类可以根据业务需求添加额外的验证规则,比如检查ReferenceTable的合法性。
内容的提问来源于stack exchange,提问作者Edwin Rooks
相关产品推荐
相关产品推荐

