我一直想追求类型安全。因此,我将创建一个类来显示您的值,并使用一个通用适配器类来处理数据库值的获取和更新。
你的显示类需要这样的东西:
abstract class DisplayedValue
{
public int Id {get; protected set;}
public string FieldDescription {get; protected set;}
public abstract string Value {get; set;}
}
如果您尝试将整数值分配给 DateTime 或其他无效转换,我们希望编译器发出投诉。所以我们需要一个通用类来保存获取的值,并将显示的值转换为获取的值
class Display<Tproperty> : Display
{
public override string Value
{
get {return this.FetchValue.ToString();}
set {this.SetValue(Parse(value));}
}
public Func<string, TProperty> Parse {get; set;}
public Func<int, TProperty> FetchValue {get; set;}
public Action <int, TProperty> SetValue {get; set;}
}
这个类代表你想要显示的属性的原始值。因为我不知道您要在行中显示的项目类型(简单数字?Guids?客户名称?),所以我需要一个 Parse 函数,将要更新的字符串解析为要更新的值。
TODO:如果 ToString() 不适合将您的属性转换为显示值,请考虑使用将您的 TProperty 转换为 DisplayValue 的 Func 属性:
public Func<TProperty, string> ToDisplayValue {get; set;}
TODO:为提高性能,请考虑跟踪数据是否已被提取和翻译,如果需要,不要再次提取/翻译。
FetchValue 是一个函数,它接受一个 int Id,并返回必须显示的项目的 Tproperty 值。
UpdateValue 是一个 void 函数,它将一个 Id 和一个要更新的 Tproperty 值作为输入。它会更新正确的值
所以要创建一个你需要的 Display 对象:
- 要显示的 ID
- 字段说明
- 将显示的值解析为 TProperty 值的解析函数
- 获取数据的函数
- 用于更新数据的 void 函数
你注意到了吗,在这个课程中我从未提到我使用数据库来获取或更新数据。这隐藏在获取和更新数据的委托函数中。这允许重用以将数据存储在其他媒体中,例如变量、流、文件等
例如:一个带有学生的 SchoolDbContext:
class Student
{
public int Id {get; set;} // primary Key
public DateTime Birthday {get; set;
public string FirstName {get; set;}
... // other properties
}
class SchoolDbContext : DbContext
{
public DbSet<Student> Students {get; set;} // the table you want to update
... // other tables
}
假设您想要显示一行,该行可以更新 ID 为 myStudentId 的学生的生日。
int myStudentId = ...
MyDbContext myDbContext = ...
DisplayedValue birthday = new Display<DateTime>()
{
Id = myStudentId,
FieldDescription = "Birthday",
// Parse function to parse the update string to a DateTime
Parse = (txt) => DateTime.Parse(txt),
// function to parse the DateTime to a displayable string
ToDisplayValue = (birthday) => birthDay.ToString("yyyy/MMM/DD"),
// the function that fetches the Birthday of Student with Id from myDbContext:
FetchValue = (id) => myDbContext.Students
.Where(student => student.Id == id)
.Select(student => student.Birthday)
.SingleOrDefault();
// the function that updates the Birthday of the Student with Id from myDbContext:
UpdateValue = (id, valueToUpdate) =>
{
Student studentToUpdate = dbContext.Students
.Where(student => student.Id == id)
.SingleOrDefault();
studentToUpdate.BirthDay = valueToUpdate);
myDbContext.SaveChanges();
},
}
虽然这是一个非常简洁且可重复使用的解决方案,但对于您要显示的每个项目来说,工作量都很大。如果你想在工厂中实现自动化,你会遇到几个问题
- 您需要确保每个项目都需要有一个 Id
- 如何获取显示项目的描述性名称?属性名够吗?
.
interface IId
{
int Id {get;}
}
您需要确保 DbContext 中将成为 DbSet 的每个类都派生自此接口。
public DisplayFactory
{
public MyDbContext MyDbContext {get; set;}
public Display<TProperty> Create<TEntity, TProperty>(int id,
Expression<Func<TEntity, TProperty>> propertySelector,
Action<TEntity, TProperty> propertyUpdater,
Func<string, TProperty> parse,
Func<TProperty, string> toDisplayValue)
{
return new Display<TProperty>()
{
Id = id,
Parse = parse,
ToDisplayValue = toDisplayValue,
FetchValue = (id) => this.MyDbContext.DbSet<TEntity>()
.Where(entity => entity.Id == id) // this is where I need the interface
.Select(propertySelector)
.SingleOrDefault(),
SetValue = (id, valueToUpdate) =>
{
TEntity entityToUpdate = this.MyDbContext.DbSet<TEntity>()
.Where(entity => entity.Id == id)
.SingleOrDefault();
propertyUpdate(entityToUpdate, valueToUpdate);
SaveChanges();
}
}
}
用法:
DisplayFactory factory = new DisplayFactory()
{
MyDbContext = ...
}
DisplayedValue createdValue = factory.Create(id,
student => student.Birthday, // property selector
(student, value) => student.Birthday = value; // property updater
(txt) => DateTime.Parse(txt), // string to Datetime
(birthday) => birthDay.ToString(...)); // to displayed birthday
注意,这是完全类型安全的,如果你想更新不存在的列或不存在的类型,或者想要分配不兼容的类型,例如将 int 分配给 DateTime,编译器不会接受它。您不能不小心更新刚刚显示的其他属性。
如果你还是觉得这工作量太大,可以考虑使用反射和PropertyInfo来选择DbSet和你要更新的Column。
但是请记住,您仍然必须提供解析器来显示并将显示的字符串值解析为要更新的值。如果您使用不存在的表或列的名称,您将失去所有类型安全性,编译器将接受它。
我不确定额外的测试时间是否相当于节省的打字时间。