如果每个 CSV 文件代表数据库的一个表,那么考虑做一些类似于实体框架的事情。
让你的DbSets代替IQueryable<...>实现IEnumerable<...>
如果您只需要获取数据,这将相当容易。如果您还想更新,则需要实现(或重复使用)DbChangeTracker
public DbSet<T> : IEnumerable<T> where T: class
{
public FileInfo CsvFile {get; set;}
public IEnumerator<T> GetEnumerator()
{
return this.ReadCsvFile().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
protected IEnumerable<T> ReadCsvFile()
{
// open the CsvFile, read the lines and convert to objects of type T
// consider using Nuget package CsvHelper
...
foreach (var csvLine in csvLines)
{
T item = Create<T>(csvLine); // TODO: write how to convert a line into T
yield return T;
}
}
}
您还需要一个包含所有 DbSet 的 DbContext:
class DbContext
{
public DbSet<School> Schools {get; } = new DbSet<School>{CsvFile = ...};
public DbSet<Teacher> Teachers {get; } = new DbSet<Teacher> {CsvFile = ...};
public DbSet<Student> Students {get; } = new DbSet<Student> {CsvFile = ...};
}
您可以通过记住已获取的项目来提高性能。将它们放在字典中,使用主键作为字典键。还将Find 函数添加到 DbSet:
class DbSet<T> : IEnumerable<T>
{
private readonly Dictionary<int, T> fetchedItems = new Dictionary<int, T>();
public T Find(int id)
{
if (!fetchedItems.TryGetValue(id, out T fetchedItem))
{
// fetch elements using ReadCsvFile and put them in the Dictionary
// until you found the item with the requested primary key
// or until the end of your sequence
}
return fetchedItem;
}
}
如果每个表项都具有相同类型的主键,则最简单:
interface IPrimaryKey
{
int Id {get;}
}
class DbSet<T> : IEnumerable<T> where T : IPrimaryKey {...}
如果没有,您需要告诉 DbSet 主键的类型:
class DbSet<T, TKey> : IEnumerable<T> where T : class
{
private readonly Dictinary<TKey, T> fetchedItems = ...
}
如果您决定将您的项目保存在字典中,那么让您的 GetEnumerator 首先返回已获取的项目,然后再从您的 CSV 文件中获取新行。
添加/更新/删除项目
为此,您需要能够从您的 CsVFile 中添加/更新/删除项目。我假设已经有这样的功能。
要有效地进行更新,您需要类似于 DbContext.SaveChanges 的东西。使用 ChangeTracker 让每个 DbSet 记住要添加/删除/更新的项目:
class Entity<T> where T : IPrimaryKey
{
public T Value {get; set;}
public T OriginalValue {get; set;}
}
class ChangeTracker<T, TKey> where T: ICloneable
{
readonly Dictionary<int, Entity<T, TKey>> fetchedEntities = new Dictionary<int, Entity<T, TKey>>
readonly List<T> itemsToAdd = new List<T>();
public T Add(T item)
{
// TODO: check for not NULL, and Id == 0
this.ItemsToAdd.Add(itemToAdd);
return item;
}
public void Remove(T item)
{
// TODO: check not null, and primary key != 0
Entity<T> entityToRemove = Find(item.Id);
// TODO: decide what to do if there is no such item
entityToRemove.Value = null;
// null indicates it is about to be removed
}
您需要一个记住原始值的 Find:
public Entity<T> Find(TKey primaryKey)
{
// is it already in the Dictionary (found before)?
// if not: get it from the CsvDatabase and put it in the dictionary
if (!fetchedItems.TryGetValue(primaryKey, out Entity<T> fetchedEntity))
{
// not fetched yet, fetch if from your Csv File
T fetchedItem = ...
// what to do if does not exist?
// add to the dictionary:
fetchedEntities.Add(new Entity<T>
{
value = fetchedItem,
originalValue = (T)fetchedItem.Clone(),
// so if value changes, original does not change
});
}
return fetchedItem;
}
最后是你的 SaveChanges()
void SaveChanges()
{
// your CsvFile database has functions to add / update / remove items
foreach (var itemToAdd in itemsToAdd)
{
csvDatabase.Add(itemToAdd);
}
// update or remove fetched items with OriginalValue unequal to Value
var itemsToUpdate = this.fetchedItems
.Where(fetchedItem => !ValueComparer.Equals(fetchedItem.OriginalValue, fetchedItem.Value)
.ToList();
foreach (Entity<T> itemToUpdate in itemsToUpdate)
{
if (itemToUpdate.Value == null)
{ // remove
csvFile.Remove(itemToUpdate.OriginalValue);
}
else
{ // update
csvFile.Update(...);
}
}
}
显然,如果您希望能够更新数据库中的项目,您需要能够检查项目是否已更改。你需要一个 IEqualityComparer<T> 来检查值
class DbChangeTracker<T, TKey> : IEnumerable<T> where T : class
{
public IEqualityComparer<T> ValueComparer {get; set;}
...
}
DbSet 保存更改:
void SaveChanges()
{
this.ChangeTracker.SaveChanges();
}
DbContext SaveChanges:
Students.SaveChanges()
Teachers.SaveChanges();
Schools.SaveChanges();