用于 CRUD (Create/Retrieve/Update/Delete) 视图的 EF Core 通用 Context.Find
EF Core Generic Context.Find for CRUD (Create/Retrieve/Update/Delete) Views
考虑以下控制器是 generic where T : DbContext where U : class, new()
注意:这适用于您有很多 table 的情况,比方说在您正在使用的某些现有数据库中有 25+ 甚至高达 100+脚手架,并且您希望 listing/adding/updating/deleting 的后端有一个管理 CRUD 视图,每个 table 的记录都相同,这不需要更新,因为 DbContext 模型中的数据库架构在 运行 像 Scaffold-DbContext 这样的工具。
[Route("Get/{id}")]
public async Task<IActionResult> Get(string id)
{
var entityType = Context.Model.GetEntityTypes().Where(x => x.ClrType.Name == typeof(U).Name).FirstOrDefault();
var keyProperty = entityType.FindPrimaryKey().Properties.ToArray()[0];
var key = Convert.ChangeType(id, keyProperty.PropertyInfo.PropertyType);
var result = Context.Find(typeof(U), new object[] { key });
return new JsonResult(result);
}
如果需要,您如何使它普遍支持主键中的多个字段?有没有更简洁的方法来执行上述操作?
Update,将 ChangeType 更改为 TypeDescriptor,向 Find 添加了多个键:
[Route("Get/{id}")]
public async Task<IActionResult> Get(string id)
{
string[] keysStr = id.Split(',');
var entityType = Context.Model.GetEntityTypes().Where(x => x.ClrType.Name == typeof(U).Name).FirstOrDefault();
var keyProperties = entityType.FindPrimaryKey().Properties.ToArray();
List<dynamic> keys = new List<object>();
if (keysStr.Length != keyProperties.Length)
{
throw new ArgumentException("Keys must be comma-separated and must be of the same number as the number of keys in the table.");
}
for(int i=0;i<keyProperties.Length;i++)
{
//var key = Convert.ChangeType(id, keyProperty.PropertyInfo.PropertyType);
TypeConverter converter = TypeDescriptor.GetConverter(keyProperties[i].PropertyInfo.PropertyType);
var key = converter.ConvertFromString(id);
keys.Add(key);
}
var result = Context.Find(typeof(U), keys.ToArray());
return new JsonResult(result);
}
使用string.Split(',') 分隔路由中的多个关键字段。更好的方法?
虽然这看起来可行,但我建议不要采用这种方法。
尤其有问题的是 CRUD 的“C”和“U”:你打算如何检查 class 无效?
例如。用户可以在以后提交 Birthday
或者在之后提交 StartDate
EndDate
,你将无法阻止它,或者至少它会很棘手,
特别是对于复杂的先决条件,例如“状态 S 中的 parent 实体不得
超过 N children".
如果无论如何都要这样做,请考虑让客户端指定实体类型。
像这样:
[ApiController]
[Route("any")]
public class JackOfAllTradesCOntroller : ControllerBase
{
private readonly MyContext _ctx;
public JackOfAllTradesCOntroller(MyContext ctx)
{
_ctx = ctx;
// Add a random entity.
// Customer configured with:
// modelBuilder.Entity<Customer>().HasKey(new[] { "Id", "FirstName" });
_ctx.Customers.Add(
new Customer() {
Id = 1,
FirstName = Guid.NewGuid().ToString().Substring(1,6),
LastName = Guid.NewGuid().ToString(),
Address = "Addr"
});
_ctx.SaveChanges();
}
private IEntityType GetType(string typeName)
{
return _ctx.Model.GetEntityTypes()
.Where(x => x.ClrType.Name == typeName).First();
}
[Route("GetAll/{entityType}")]
public IActionResult GetAll(string entityType)
{
IEntityType eType = GetType(entityType);
MethodInfo set = _ctx.GetType().GetMethod("Set")
.MakeGenericMethod(eType.ClrType);
object result = set.Invoke(_ctx, new object[0]);
return new JsonResult(result);
}
[Route("Get/{entityType}")]
public IActionResult Get(
string entityType,
[FromQuery(Name = "id")] string[] id)
{
IEntityType eType = GetType(entityType);
IProperty[] keyProperties = eType.FindPrimaryKey().Properties.ToArray();
// ... validate id here ...
object[] keys = id.Zip(
keyProperties,
(i, p) => Convert.ChangeType(i, p.PropertyInfo.PropertyType)
).ToArray();
object result = _ctx.Find(eType.ClrType, keys);
return new JsonResult(result);
}
}
演示:
Although it seems doable, I would advise against this approach.
Especially problematic are the "C" and "U" of CRUD: how are you going
to check class invaliants? E.g. the user may submit a Birthday in the
future or a StartDate after EndDate, and you won't be able to prevent
that, or at least it will be tricky, especially with complex
preconditions like "a parent entity in state S must not have more than
N children".
If you want to do it anyway, consider letting the client specify the entity type.
这是一个关于基于 C# 的验证检查 RDBMS 通常不会检查的无效条目的好问题,例如未来的生日,以及其他。
由于 Scaffold-DbContext 为模型和 DbContext 创建部分 classes,一种方法是在单独的位置为上下文创建部分,以免被工具更改覆盖,或者您可以 subclass 并实现一个可用于 pre-validation.
的接口
这是一个简单的:
public interface IPrevalidateModel
{
ValidationResult Validate(Type t, object value);
}
public class ValidationResult
{
public bool Success { get; set; }
public string Message { get; set; }
public object Data { get; set; }
}
public class ToDoContext : Models.ToDoApp.ToDoAppDbContext, IPrevalidateModel
{
public ToDoContext() : base()
{
}
public ToDoContext(DbContextOptions<ToDoAppDbContext> options)
: base(options)
{
}
public ValidationResult Validate(Type t, object value)
{
if (t == typeof(ToDo))
{
ToDo validate = (ToDo)value;
if (validate.Done > validate.Started)
{
return new ValidationResult() { Success = false, Message = "Error: Done time is greater than Start time.", Data = value };
}
}
return new ValidationResult() { Success = true, Message = "OK" };
}
}
那个子class是一个简单的 ToDo 应用程序的上下文,并为 pre-validation 提供了一个界面。我一直在想另一种用途可能是 per-table 角色授权检查 context/connection.
中不同的 CRUD 操作
现在让我们回到控制器逻辑,我为逻辑做了一个代理模型class作为控制器和模型的go-between,所以它不受scaffolding-tooling的影响以及减轻控制器中的逻辑阻塞。
这需要一些 clean-up,但为您提供了您想要的大部分主要好东西:
public class ContextCRUD<T> where T : DbContext, IPrevalidateModel
{
public class DbUpdateResult
{
public bool Success { get; set; }
public string Message { get; set; }
public int SaveChangesResult { get; set; }
public object Entity { get; set; }
}
public class TableInfo
{
public string Name { get; set; }
public Type DbSetType { get; set; }
public Type TableType { get; set; }
public IEntityType EntityType { get; set; }
}
T Context { get; set; }
IServiceProvider Provider { get; set; }
public List<TableInfo> TableNames { get; set; }
public ContextCRUD(T context, IServiceProvider provider)
{
Context = context;
Provider = provider;
TableNames = ListAllTables();
}
List<TableInfo> ListAllTables()
{
var entityType = Context.Model.GetEntityTypes().ToArray();
return entityType.Select(x => new TableInfo() { Name = x.ClrType.Name, TableType = x.ClrType, EntityType = x }).ToList();
}
TableInfo GetTable(string table)
{
var tableInfo = TableNames.Where(x => x.Name == table).FirstOrDefault();
if (tableInfo == null)
throw new Exception($"Table not found '{table}'.");
return tableInfo;
}
public async Task<object> GetAll(string table)
{
var tableInfo = GetTable(table);
var contextSetMethod = Context.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "Set" && x.ContainsGenericParameters).FirstOrDefault();
var cMethod = contextSetMethod.MakeGenericMethod(new Type[] { tableInfo.TableType });
var results = cMethod.Invoke(Context, null);
return await Task.FromResult(results);
}
public async Task<object> Get(string table, string[] id)
{
var tableInfo = GetTable(table);
var entityType = Context.Model.GetEntityTypes().Where(x => x.ClrType.Name == table).FirstOrDefault();
var keyProperties = entityType.FindPrimaryKey().Properties.ToArray();
if (id.Length != keyProperties.Length)
{
throw new ArgumentException("Keys must be comma-separated and must be of the same number and the number of keys in the table.");
}
object[] keys = id.Zip(keyProperties, (i, p) => { return TypeDescriptor.GetConverter(p.PropertyInfo.PropertyType).ConvertFromString(i); }).ToArray();
var result = Context.Find(entityType.ClrType, keys.ToArray());
return await Task.FromResult(result);
}
public async Task<List<dynamic>> GetFields(string table)
{
var tableInfo = TableNames.Where(x => x.Name == table).FirstOrDefault();
if (tableInfo == null)
{
throw new Exception("Invalid table.");
}
var tableType = tableInfo.TableType;
var props = tableInfo.EntityType.GetProperties();
List<dynamic> results = new List<dynamic>();
foreach(var p in props)
{
var isGeneric = p.PropertyInfo.PropertyType.GenericTypeArguments.Length > 0;
string name = p.PropertyInfo.PropertyType.Name;
if (isGeneric)
{
name += "[" + p.PropertyInfo.PropertyType.GenericTypeArguments[0] + "]";
}
results.Add(new { Name = p.Name, Type = name });
}
return await Task.FromResult(results);
}
void UpdateRecordFields(object record, FieldValue[] fields, TableInfo tableInfo)
{
var recordProperties = tableInfo.EntityType.GetProperties();
foreach (var f in fields)
{
var recordProperty = recordProperties.Where(x => x.Name == f.Name).FirstOrDefault();
object value = TypeDescriptor.GetConverter(recordProperty.PropertyInfo.PropertyType).ConvertFromString(f.Value);
recordProperty.PropertyInfo.SetValue(record, value);
}
}
public async Task<DbUpdateResult> Update(string table, string[] id, FieldValue[] fields)
{
var tableInfo = GetTable(table);
var record = await Get(table, id);
UpdateRecordFields(record, fields, tableInfo);
var validateResult = Context.Validate(tableInfo.TableType, record);
if (!validateResult.Success) {
return new DbUpdateResult() { Success = false, Message = validateResult.Message, Entity = record, SaveChangesResult = -1 };
};
Context.Update(record);
int result = await Context.SaveChangesAsync();
return await Task.FromResult(new DbUpdateResult() { Success = (result > 0) ? true : false, Message = "OK", Entity = record, SaveChangesResult = result });
}
public async Task<DbUpdateResult> Update(string table, FieldValue[] fields)
{
var tableInfo = GetTable(table);
var record = Activator.CreateInstance(tableInfo.TableType);
UpdateRecordFields(record, fields, tableInfo);
var validateResult = Context.Validate(tableInfo.TableType, record);
if (!validateResult.Success)
{
return new DbUpdateResult() { Success = false, Message = validateResult.Message, Entity = record, SaveChangesResult = -1 };
};
Context.Update(record);
int result = await Context.SaveChangesAsync();
return await Task.FromResult(new DbUpdateResult() { Success = (result > 0) ? true : false, Message = "OK", Entity = record, SaveChangesResult = result });
}
public async Task<DbUpdateResult> Delete(string table, string[] id)
{
var tableInfo = GetTable(table);
var record = await Get(table, id);
Context.Remove(record);
int result = await Context.SaveChangesAsync();
return await Task.FromResult(new DbUpdateResult() { Success = (result > 0) ? true : false, Message = "OK", Entity = record, SaveChangesResult = result } );
}
}
然后是真正的控制器:
public class ToDoController : GenericController<ToDoContext>
{
public ToDoController(ToDoContext context, IServiceProvider provider, ILogger<CustomLogger> logger) : base(context, provider, logger)
{
}
}
[Route("api/[controller]")]
public class GenericController<T> : Controller where T: DbContext, IPrevalidateModel
{
T Context { get; set; }
ILogger<CustomLogger> Logger { get; set; }
ContextCRUD<T> CRUD { get; set; }
public GenericController(T context, IServiceProvider provider, ILogger<CustomLogger> logger)
{
Context = context;
Logger = logger;
CRUD = new ContextCRUD<T>(context, provider);
}
void LogAction(string action = "")
{
// ... Logger code here ...
}
void LogValues(string json)
{
ConsoleEx console = new ConsoleEx(); // Note this is a 24-bit ANSI Color Console class I created and am planning to release.
// ... Logger code here ...
}
async Task<IActionResult> RunAction(string actionName, Func<Task<object>> func)
{
LogAction(actionName);
var values = await func();
LogValues(JsonConvert.SerializeObject(values, Formatting.Indented));
return new JsonResult(values);
}
[Route("{table}/")]
public Task<IActionResult> GetAll(string table)
{
return RunAction("GetAll", async () => { return await CRUD.GetAll(table); });
}
[Route("{table}/Get/{id}/")]
public Task<IActionResult> Get(string table, [ModelBinder(BinderType = typeof(CRUDModelCommaBinder))] string [] id)
{
return RunAction("Get", async () => { return await CRUD.Get(table, id); });
}
[Route("{table}/GetFields/")]
public Task<IActionResult> GetFields(string table)
{
return RunAction("GetFields", async () => { return await CRUD.GetFields(table); });
}
[Route("{table}/Add/{fields}/")]
public Task<IActionResult> Add(string table, [ModelBinder(BinderType = typeof(CRUDModelFieldValueBinder))] FieldValue[] fields)
{
return RunAction("GetFields", async () => { return await CRUD.Update(table, fields); });
}
[Route("{table}/Update/{id}/{fields}/")]
public Task<IActionResult> Update(string table, [ModelBinder(BinderType = typeof(CRUDModelCommaBinder))] string [] id, [ModelBinder(BinderType = typeof(CRUDModelFieldValueBinder))] FieldValue[] fields)
{
return RunAction("Update", async () => { return await CRUD.Update(table, id, fields); });
}
[Route("{table}/Delete/{id}/")]
public Task<IActionResult> Delete(string table, [ModelBinder(BinderType = typeof(CRUDModelCommaBinder))] string [] id)
{
return RunAction("Delete", async () => { return await CRUD.Delete(table, id); });
}
public Task<IActionResult> Index()
{
return RunAction("Index (Show Table Names)", async () => { return await Task.FromResult(CRUD.TableNames.Select(x => new { Name = x.Name })); });
}
}
If you want to do it anyway, consider letting the client specify the
entity type.
另请注意,这个是 DbContext 的通用 T,而不是 ,我之前写过一个通用 T 变体,但在 ContextCRUD 中有很多反射逻辑,现在它被简化了,更干净了,尽管您可以摆脱 ContextCRUD 中的 IServiceProvider,但如果您想要将 CreateScope 用于某些其他范围内的服务,例如访问其他 DbContext 或范围内的存储库,则可以使用它。
我会查看您的外部 http 程序并为您提供带有 unicode Pacman 表情符号的自定义 ILogger。不过,更严肃地说,我目前想不出任何其他陷阱,除非你能想到任何东西。然后是制作视图以使用 JSON api.
考虑以下控制器是 generic
注意:这适用于您有很多 table 的情况,比方说在您正在使用的某些现有数据库中有 25+ 甚至高达 100+脚手架,并且您希望 listing/adding/updating/deleting 的后端有一个管理 CRUD 视图,每个 table 的记录都相同,这不需要更新,因为 DbContext 模型中的数据库架构在 运行 像 Scaffold-DbContext 这样的工具。
[Route("Get/{id}")]
public async Task<IActionResult> Get(string id)
{
var entityType = Context.Model.GetEntityTypes().Where(x => x.ClrType.Name == typeof(U).Name).FirstOrDefault();
var keyProperty = entityType.FindPrimaryKey().Properties.ToArray()[0];
var key = Convert.ChangeType(id, keyProperty.PropertyInfo.PropertyType);
var result = Context.Find(typeof(U), new object[] { key });
return new JsonResult(result);
}
如果需要,您如何使它普遍支持主键中的多个字段?有没有更简洁的方法来执行上述操作?
Update,将 ChangeType 更改为 TypeDescriptor,向 Find 添加了多个键:
[Route("Get/{id}")]
public async Task<IActionResult> Get(string id)
{
string[] keysStr = id.Split(',');
var entityType = Context.Model.GetEntityTypes().Where(x => x.ClrType.Name == typeof(U).Name).FirstOrDefault();
var keyProperties = entityType.FindPrimaryKey().Properties.ToArray();
List<dynamic> keys = new List<object>();
if (keysStr.Length != keyProperties.Length)
{
throw new ArgumentException("Keys must be comma-separated and must be of the same number as the number of keys in the table.");
}
for(int i=0;i<keyProperties.Length;i++)
{
//var key = Convert.ChangeType(id, keyProperty.PropertyInfo.PropertyType);
TypeConverter converter = TypeDescriptor.GetConverter(keyProperties[i].PropertyInfo.PropertyType);
var key = converter.ConvertFromString(id);
keys.Add(key);
}
var result = Context.Find(typeof(U), keys.ToArray());
return new JsonResult(result);
}
使用string.Split(',') 分隔路由中的多个关键字段。更好的方法?
虽然这看起来可行,但我建议不要采用这种方法。
尤其有问题的是 CRUD 的“C”和“U”:你打算如何检查 class 无效?
例如。用户可以在以后提交 Birthday
或者在之后提交 StartDate
EndDate
,你将无法阻止它,或者至少它会很棘手,
特别是对于复杂的先决条件,例如“状态 S 中的 parent 实体不得
超过 N children".
如果无论如何都要这样做,请考虑让客户端指定实体类型。
像这样:
[ApiController]
[Route("any")]
public class JackOfAllTradesCOntroller : ControllerBase
{
private readonly MyContext _ctx;
public JackOfAllTradesCOntroller(MyContext ctx)
{
_ctx = ctx;
// Add a random entity.
// Customer configured with:
// modelBuilder.Entity<Customer>().HasKey(new[] { "Id", "FirstName" });
_ctx.Customers.Add(
new Customer() {
Id = 1,
FirstName = Guid.NewGuid().ToString().Substring(1,6),
LastName = Guid.NewGuid().ToString(),
Address = "Addr"
});
_ctx.SaveChanges();
}
private IEntityType GetType(string typeName)
{
return _ctx.Model.GetEntityTypes()
.Where(x => x.ClrType.Name == typeName).First();
}
[Route("GetAll/{entityType}")]
public IActionResult GetAll(string entityType)
{
IEntityType eType = GetType(entityType);
MethodInfo set = _ctx.GetType().GetMethod("Set")
.MakeGenericMethod(eType.ClrType);
object result = set.Invoke(_ctx, new object[0]);
return new JsonResult(result);
}
[Route("Get/{entityType}")]
public IActionResult Get(
string entityType,
[FromQuery(Name = "id")] string[] id)
{
IEntityType eType = GetType(entityType);
IProperty[] keyProperties = eType.FindPrimaryKey().Properties.ToArray();
// ... validate id here ...
object[] keys = id.Zip(
keyProperties,
(i, p) => Convert.ChangeType(i, p.PropertyInfo.PropertyType)
).ToArray();
object result = _ctx.Find(eType.ClrType, keys);
return new JsonResult(result);
}
}
演示:
Although it seems doable, I would advise against this approach. Especially problematic are the "C" and "U" of CRUD: how are you going to check class invaliants? E.g. the user may submit a Birthday in the future or a StartDate after EndDate, and you won't be able to prevent that, or at least it will be tricky, especially with complex preconditions like "a parent entity in state S must not have more than N children". If you want to do it anyway, consider letting the client specify the entity type.
这是一个关于基于 C# 的验证检查 RDBMS 通常不会检查的无效条目的好问题,例如未来的生日,以及其他。
由于 Scaffold-DbContext 为模型和 DbContext 创建部分 classes,一种方法是在单独的位置为上下文创建部分,以免被工具更改覆盖,或者您可以 subclass 并实现一个可用于 pre-validation.
的接口这是一个简单的:
public interface IPrevalidateModel
{
ValidationResult Validate(Type t, object value);
}
public class ValidationResult
{
public bool Success { get; set; }
public string Message { get; set; }
public object Data { get; set; }
}
public class ToDoContext : Models.ToDoApp.ToDoAppDbContext, IPrevalidateModel
{
public ToDoContext() : base()
{
}
public ToDoContext(DbContextOptions<ToDoAppDbContext> options)
: base(options)
{
}
public ValidationResult Validate(Type t, object value)
{
if (t == typeof(ToDo))
{
ToDo validate = (ToDo)value;
if (validate.Done > validate.Started)
{
return new ValidationResult() { Success = false, Message = "Error: Done time is greater than Start time.", Data = value };
}
}
return new ValidationResult() { Success = true, Message = "OK" };
}
}
那个子class是一个简单的 ToDo 应用程序的上下文,并为 pre-validation 提供了一个界面。我一直在想另一种用途可能是 per-table 角色授权检查 context/connection.
中不同的 CRUD 操作现在让我们回到控制器逻辑,我为逻辑做了一个代理模型class作为控制器和模型的go-between,所以它不受scaffolding-tooling的影响以及减轻控制器中的逻辑阻塞。
这需要一些 clean-up,但为您提供了您想要的大部分主要好东西:
public class ContextCRUD<T> where T : DbContext, IPrevalidateModel
{
public class DbUpdateResult
{
public bool Success { get; set; }
public string Message { get; set; }
public int SaveChangesResult { get; set; }
public object Entity { get; set; }
}
public class TableInfo
{
public string Name { get; set; }
public Type DbSetType { get; set; }
public Type TableType { get; set; }
public IEntityType EntityType { get; set; }
}
T Context { get; set; }
IServiceProvider Provider { get; set; }
public List<TableInfo> TableNames { get; set; }
public ContextCRUD(T context, IServiceProvider provider)
{
Context = context;
Provider = provider;
TableNames = ListAllTables();
}
List<TableInfo> ListAllTables()
{
var entityType = Context.Model.GetEntityTypes().ToArray();
return entityType.Select(x => new TableInfo() { Name = x.ClrType.Name, TableType = x.ClrType, EntityType = x }).ToList();
}
TableInfo GetTable(string table)
{
var tableInfo = TableNames.Where(x => x.Name == table).FirstOrDefault();
if (tableInfo == null)
throw new Exception($"Table not found '{table}'.");
return tableInfo;
}
public async Task<object> GetAll(string table)
{
var tableInfo = GetTable(table);
var contextSetMethod = Context.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == "Set" && x.ContainsGenericParameters).FirstOrDefault();
var cMethod = contextSetMethod.MakeGenericMethod(new Type[] { tableInfo.TableType });
var results = cMethod.Invoke(Context, null);
return await Task.FromResult(results);
}
public async Task<object> Get(string table, string[] id)
{
var tableInfo = GetTable(table);
var entityType = Context.Model.GetEntityTypes().Where(x => x.ClrType.Name == table).FirstOrDefault();
var keyProperties = entityType.FindPrimaryKey().Properties.ToArray();
if (id.Length != keyProperties.Length)
{
throw new ArgumentException("Keys must be comma-separated and must be of the same number and the number of keys in the table.");
}
object[] keys = id.Zip(keyProperties, (i, p) => { return TypeDescriptor.GetConverter(p.PropertyInfo.PropertyType).ConvertFromString(i); }).ToArray();
var result = Context.Find(entityType.ClrType, keys.ToArray());
return await Task.FromResult(result);
}
public async Task<List<dynamic>> GetFields(string table)
{
var tableInfo = TableNames.Where(x => x.Name == table).FirstOrDefault();
if (tableInfo == null)
{
throw new Exception("Invalid table.");
}
var tableType = tableInfo.TableType;
var props = tableInfo.EntityType.GetProperties();
List<dynamic> results = new List<dynamic>();
foreach(var p in props)
{
var isGeneric = p.PropertyInfo.PropertyType.GenericTypeArguments.Length > 0;
string name = p.PropertyInfo.PropertyType.Name;
if (isGeneric)
{
name += "[" + p.PropertyInfo.PropertyType.GenericTypeArguments[0] + "]";
}
results.Add(new { Name = p.Name, Type = name });
}
return await Task.FromResult(results);
}
void UpdateRecordFields(object record, FieldValue[] fields, TableInfo tableInfo)
{
var recordProperties = tableInfo.EntityType.GetProperties();
foreach (var f in fields)
{
var recordProperty = recordProperties.Where(x => x.Name == f.Name).FirstOrDefault();
object value = TypeDescriptor.GetConverter(recordProperty.PropertyInfo.PropertyType).ConvertFromString(f.Value);
recordProperty.PropertyInfo.SetValue(record, value);
}
}
public async Task<DbUpdateResult> Update(string table, string[] id, FieldValue[] fields)
{
var tableInfo = GetTable(table);
var record = await Get(table, id);
UpdateRecordFields(record, fields, tableInfo);
var validateResult = Context.Validate(tableInfo.TableType, record);
if (!validateResult.Success) {
return new DbUpdateResult() { Success = false, Message = validateResult.Message, Entity = record, SaveChangesResult = -1 };
};
Context.Update(record);
int result = await Context.SaveChangesAsync();
return await Task.FromResult(new DbUpdateResult() { Success = (result > 0) ? true : false, Message = "OK", Entity = record, SaveChangesResult = result });
}
public async Task<DbUpdateResult> Update(string table, FieldValue[] fields)
{
var tableInfo = GetTable(table);
var record = Activator.CreateInstance(tableInfo.TableType);
UpdateRecordFields(record, fields, tableInfo);
var validateResult = Context.Validate(tableInfo.TableType, record);
if (!validateResult.Success)
{
return new DbUpdateResult() { Success = false, Message = validateResult.Message, Entity = record, SaveChangesResult = -1 };
};
Context.Update(record);
int result = await Context.SaveChangesAsync();
return await Task.FromResult(new DbUpdateResult() { Success = (result > 0) ? true : false, Message = "OK", Entity = record, SaveChangesResult = result });
}
public async Task<DbUpdateResult> Delete(string table, string[] id)
{
var tableInfo = GetTable(table);
var record = await Get(table, id);
Context.Remove(record);
int result = await Context.SaveChangesAsync();
return await Task.FromResult(new DbUpdateResult() { Success = (result > 0) ? true : false, Message = "OK", Entity = record, SaveChangesResult = result } );
}
}
然后是真正的控制器:
public class ToDoController : GenericController<ToDoContext>
{
public ToDoController(ToDoContext context, IServiceProvider provider, ILogger<CustomLogger> logger) : base(context, provider, logger)
{
}
}
[Route("api/[controller]")]
public class GenericController<T> : Controller where T: DbContext, IPrevalidateModel
{
T Context { get; set; }
ILogger<CustomLogger> Logger { get; set; }
ContextCRUD<T> CRUD { get; set; }
public GenericController(T context, IServiceProvider provider, ILogger<CustomLogger> logger)
{
Context = context;
Logger = logger;
CRUD = new ContextCRUD<T>(context, provider);
}
void LogAction(string action = "")
{
// ... Logger code here ...
}
void LogValues(string json)
{
ConsoleEx console = new ConsoleEx(); // Note this is a 24-bit ANSI Color Console class I created and am planning to release.
// ... Logger code here ...
}
async Task<IActionResult> RunAction(string actionName, Func<Task<object>> func)
{
LogAction(actionName);
var values = await func();
LogValues(JsonConvert.SerializeObject(values, Formatting.Indented));
return new JsonResult(values);
}
[Route("{table}/")]
public Task<IActionResult> GetAll(string table)
{
return RunAction("GetAll", async () => { return await CRUD.GetAll(table); });
}
[Route("{table}/Get/{id}/")]
public Task<IActionResult> Get(string table, [ModelBinder(BinderType = typeof(CRUDModelCommaBinder))] string [] id)
{
return RunAction("Get", async () => { return await CRUD.Get(table, id); });
}
[Route("{table}/GetFields/")]
public Task<IActionResult> GetFields(string table)
{
return RunAction("GetFields", async () => { return await CRUD.GetFields(table); });
}
[Route("{table}/Add/{fields}/")]
public Task<IActionResult> Add(string table, [ModelBinder(BinderType = typeof(CRUDModelFieldValueBinder))] FieldValue[] fields)
{
return RunAction("GetFields", async () => { return await CRUD.Update(table, fields); });
}
[Route("{table}/Update/{id}/{fields}/")]
public Task<IActionResult> Update(string table, [ModelBinder(BinderType = typeof(CRUDModelCommaBinder))] string [] id, [ModelBinder(BinderType = typeof(CRUDModelFieldValueBinder))] FieldValue[] fields)
{
return RunAction("Update", async () => { return await CRUD.Update(table, id, fields); });
}
[Route("{table}/Delete/{id}/")]
public Task<IActionResult> Delete(string table, [ModelBinder(BinderType = typeof(CRUDModelCommaBinder))] string [] id)
{
return RunAction("Delete", async () => { return await CRUD.Delete(table, id); });
}
public Task<IActionResult> Index()
{
return RunAction("Index (Show Table Names)", async () => { return await Task.FromResult(CRUD.TableNames.Select(x => new { Name = x.Name })); });
}
}
If you want to do it anyway, consider letting the client specify the entity type.
另请注意,这个是 DbContext 的通用 T,而不是
我会查看您的外部 http 程序并为您提供带有 unicode Pacman 表情符号的自定义 ILogger。不过,更严肃地说,我目前想不出任何其他陷阱,除非你能想到任何东西。然后是制作视图以使用 JSON api.