本文模拟实现的不是精细到某个属性的延迟加载,仅 item级别的延迟加载。
在如今的数据编程,或多或少都用过 orm 来进行数据库数据的操作,特别是 微软的 ef 也是深受欢迎。
而 ef 中有个特性,即延迟加载。就是当你查询一个列表出来,在tolist之前或者是遍历之前,它的item数据还是不全面的,只有当你遍历到某个对象时,它才会去查出这个对象各个属性的值,这样有一个好处就是,单次查询的数据量不会很大,很多东西我们不需要就不用查询出来,避免浪费空间占用。
不过本次模拟实现的效果并不是很细化到某个属性的动态加载,仅仅是模拟对象延迟创建。
要进行可控的延迟创建,自然是需要我们自己实现 IEnumerator
而这个 IEnumerator 实现需要有一下几点需要注意:
1.原列表数据只有 id列表
2.需要提供对象创建委托,在需要的时候动态创建对象
3.创建的对象缓存起来,第二次用到时将不再进行创建
一下直接放代码:
首先是自定义实现的 列表(包含迭代器)
/// <summary>
/// 仅模拟实现,实际情况复杂的多
/// </summary>
/// <typeparam name="T"></typeparam>
public class MyList<T> : IEnumerable<T>
{
/// <summary>
/// 数据的唯一标识
/// </summary>
private string[] ids;
/// <summary>
/// 获取详细信息的构造器,该构造器根据 id 获取到item的信息
/// </summary>
private Func<string, object> itemCreator;
public MyList(Func<string, object> itemCreator,params string[] ids)
{
this.ids = ids;
this.itemCreator = itemCreator;
}
public IEnumerator<T> GetEnumerator()
{
return new MyListEnumeratorT<T>(GetMyEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetMyEnumerator();
}
private IEnumerator GetMyEnumerator()
{
return new MyListEnumerator(ids, itemCreator); ;
}
}
/// <summary>
/// 这里相当使用装饰器扩展而已,无其它实现
/// </summary>
/// <typeparam name="T"></typeparam>
public class MyListEnumeratorT<T> : IEnumerator<T>
{
private IEnumerator myListEnumerator;
public MyListEnumeratorT(IEnumerator myListEnumerator)
{
this.myListEnumerator = myListEnumerator;
}
public T Current => (T)myListEnumerator.Current;
object IEnumerator.Current => myListEnumerator;
public void Dispose()
{
;
}
public bool MoveNext()
{
return myListEnumerator.MoveNext();
}
public void Reset()
{
myListEnumerator.Reset();
}
}
public class MyListEnumerator : IEnumerator
{
/// <summary>
/// id列表
/// </summary>
private string[] ids;
/// <summary>
/// 对象构造器
/// </summary>
private Func<string, object> itemCreator;
/// <summary>
/// 对象缓存
/// </summary>
private Dictionary<string, object> keyValuePairs = new Dictionary<string, object>();
/// <summary>
/// 索引
/// </summary>
private int index = 0;
public MyListEnumerator(string[] ids,Func<string,object>itemCreator)
{
this.ids = ids;
this.itemCreator = itemCreator;
}
public object Current { get; set; }
public bool MoveNext()
{
if(ids.Length > index)
{
if(!keyValuePairs.ContainsKey(ids[index]))
{
Debug.WriteLine("未加载的对象这个时候进行加载");
keyValuePairs.Add(ids[index], itemCreator.Invoke(ids[index]));
}
Current = keyValuePairs[ids[index]];
index++;
return true;
}
return false;
}
public void Reset()
{
index = 0;
}
}
测试代码:
public class SetUp
{
[STAThread]
public static void Main()
{
/*
* 模拟测试 延迟加载(item层面)
* 假设 sql查询只根据条件查询出 id (列表),之查出一个字段而且是唯一索引字段,这个效率会快很多,数据量也不大
* 在枚举各个对象时,再根据具体的id获取详细信息(延迟获取详细信息,单词传输的数据量会变少,但也导致请求次数也会相应的增多,看到通过动态代理在获取item某个属性的值才去请求数据,那个就更细但请求次数也更多)
*/
var ids = "one,two,three,four,five".Split(',');
{
var myList = new MyList<MyModel>(ItemCreator, ids);
Console.WriteLine("会在用到的时候才创建(每个创建时间间隔 0.5s+)");
foreach (MyModel item in myList)
{
Thread.Sleep(500);
Console.WriteLine(string.Format("{0}:{1}", item.CreateTime, item.Id));
}
}
{
//直接toList 会创建所有?
Console.WriteLine("直接toList 会马上创建所有(输出的创建时间只有几毫秒)");
var myList2 = new MyList<MyModel>(ItemCreator, ids).ToList();
foreach (MyModel item in myList2)
{
Thread.Sleep(500);
Console.WriteLine(string.Format("{0}:{1}", item.CreateTime, item.Id));
}
}
{
//获取某个对象时会生成该对象
Console.WriteLine("获取某个对象时会生成该对象");
var item = new MyList<MyModel>(ItemCreator, ids).FirstOrDefault();
Console.WriteLine(string.Format("{0}:{1}", item.CreateTime, item.Id));
}
Console.ReadLine();
}
/// <summary>
/// 对象创建器
/// 在实际使用过程中,可以是一个根据 id查询详细信息 这样的一个方法
/// 而且实际查询也不一定是查询所有字段的值,所以orm做延迟加载处理的东西不少()
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private static object ItemCreator(string id)
{
Console.WriteLine(string.Format("创建id:{0}",id));
return new MyModel
{
Id = id,
CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
}
class MyModel
{
public string Id { get; set; }
public string CreateTime { get; set; }
}
}
执行效果: