EntityFramework更新Collection不能够简单的进行赋值运算,因为EntityFramework需要跟踪更新操作。
为了让代码更简洁和代码复用,需要对EntityFramework Collection进行拓展。
下面是拓展代码:
1 // Copyright (c) 雾里看花 Q397386036 2 public static class CollectionExtensions 3 { 4 public static void ToDb<Tsource, Ttarget>( 5 this ICollection<Tsource> tsource, 6 ICollection<Ttarget> ttarget, Func<Tsource, object> outKeySelector, Func<Ttarget, object> innerKeySelector) 7 where Tsource:class, new() 8 where Ttarget:class, new() 9 { 10 if (tsource == null) 11 tsource = new HashSet<Tsource>(); 12 if (ttarget == null) 13 throw new ArgumentNullException(); 14 15 var intersectSources = (from source in tsource.AsQueryable() 16 join target in ttarget.AsQueryable() on outKeySelector(source) equals innerKeySelector(target) 17 select source).ToList(); 18 var intersectTargets = (from source in tsource.AsQueryable() 19 join target in ttarget.AsQueryable() on outKeySelector(source) equals innerKeySelector(target) 20 select target).ToList(); 21 22 //更新 23 foreach (Tsource sourceItem in intersectSources) 24 { 25 var targetItem = (from source in tsource.AsQueryable() 26 join target in ttarget.AsQueryable() on outKeySelector(source) equals innerKeySelector(target) 27 where source.Equals(sourceItem) 28 select target).FirstOrDefault(); 29 if (targetItem == null) continue; 30 Mapper.Map<Tsource, Ttarget>(sourceItem, targetItem); 31 } 32 33 //删除 34 var removeTargets = (from target in ttarget.AsQueryable() 35 where !intersectTargets.Any(t=>t.Equals(target)) 36 select target).ToList(); 37 foreach (var targetItem in removeTargets) 38 { 39 ttarget.Remove(targetItem); 40 } 41 42 //添加 43 var addSources = (from source in tsource.AsQueryable() 44 where !intersectSources.Any(t => t.Equals(source)) 45 select source).ToList(); 46 foreach (var sourceItem in addSources) 47 { 48 ttarget.Add(Mapper.Map<Tsource, Ttarget>(sourceItem)); 49 } 50 51 } 52 }
从一个Collection更新另一个Collection(ttarget EF Collection),我们发现其实俩个基类从没有任何关系,他通过Mapper.Map进行映射。他们更新又靠 内联关联起来,红色部分所标记。
Mapper.Map使用的ValueInjecter对象转换,Mapper的代码如下(一时找不到从哪下,就直接贴出来了):
1 /// <summary> 2 /// DtoMapper 3 /// </summary> 4 public static class Mapper 5 { 6 //map source to an existing target 7 public static TTarget Map<TSource, TTarget>(TSource source, TTarget target) 8 { 9 target = MapperFactory.GetMapper<TSource, TTarget>().Map(source, target); 10 return target; 11 } 12 13 //create a new target and map source on it 14 public static TTarget Map<TSource, TTarget>(TSource source) 15 { 16 var target = (TTarget)Creator.Create(typeof(TTarget)); 17 return MapperFactory.GetMapper<TSource, TTarget>().Map(source, target); 18 } 19 20 public static object Map(object source, object target, Type sourceType, Type targetType) 21 { 22 target = target ?? Creator.Create(targetType); 23 var getMapper = typeof(MapperFactory).GetMethod("GetMapper").MakeGenericMethod(sourceType, targetType); 24 var mapper = getMapper.Invoke(null, null); 25 var map = mapper.GetType().GetMethod("Map"); 26 return map.Invoke(mapper, new[] { source, target }); 27 } 28 } 29 30 public static class MapperFactory 31 { 32 private static readonly IDictionary<Type, object> Mappers = new Dictionary<Type, object>(); 33 34 public static ITypeMapper<TSource, TTarget> GetMapper<TSource, TTarget>() 35 { 36 //if we have a specified TypeMapper for <TSource,Target> return it 37 if (Mappers.ContainsKey(typeof(ITypeMapper<TSource, TTarget>))) 38 return Mappers[typeof(ITypeMapper<TSource, TTarget>)] as ITypeMapper<TSource, TTarget>; 39 40 //if both Source and Target types are Enumerables return new EnumerableTypeMapper<TSource,TTarget>() 41 if (typeof(TSource).IsEnumerable() && typeof(TTarget).IsEnumerable()) 42 { 43 return (ITypeMapper<TSource, TTarget>)Activator.CreateInstance(typeof(EnumerableTypeMapper<,>).MakeGenericType(typeof(TSource), typeof(TTarget))); 44 } 45 46 //return the default TypeMapper 47 return new TypeMapper<TSource, TTarget>(); 48 } 49 50 public static void AddMapper<TS, TT>(ITypeMapper<TS, TT> o) 51 { 52 Mappers.Add(typeof(ITypeMapper<TS, TT>), o); 53 } 54 55 public static void ClearMappers() 56 { 57 Mappers.Clear(); 58 } 59 } 60 61 public interface ITypeMapper<TSource, TTarget> 62 { 63 TTarget Map(TSource source, TTarget target); 64 } 65 66 public class TypeMapper<TSource, TTarget> : ITypeMapper<TSource, TTarget> 67 { 68 public virtual TTarget Map(TSource source, TTarget target) 69 { 70 target.InjectFrom(source) 71 .InjectFrom<NullablesToNormal>(source) 72 .InjectFrom<NormalToNullables>(source) 73 .InjectFrom<IntToEnum>(source) 74 .InjectFrom<EnumToInt>(source) 75 .InjectFrom<MapperInjection>(source);// apply mapper.map for Foo, Bar, IEnumerable<Foo> etc. 76 77 return target; 78 } 79 } 80 81 public class EnumerableTypeMapper<TSource, TTarget> : ITypeMapper<TSource, TTarget> 82 where TSource : class 83 where TTarget : class 84 { 85 public TTarget Map(TSource source, TTarget target) 86 { 87 if (source == null) return null; 88 var targetArgumentType = typeof(TTarget).GetGenericArguments()[0]; 89 90 var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(targetArgumentType)); 91 var add = list.GetType().GetMethod("Add"); 92 93 foreach (var o in source as IEnumerable) 94 { 95 var t = Creator.Create(targetArgumentType); 96 add.Invoke(list, new[] { Mapper.Map(o, t, o.GetType(), targetArgumentType) }); 97 } 98 return (TTarget)list; 99 } 100 } 101 102 public static class TypeExtensions 103 { 104 //returns true if type is IEnumerable<> or ICollection<>, IList<> ... 105 public static bool IsEnumerable(this Type type) 106 { 107 if (type.IsGenericType) 108 { 109 if (type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable))) 110 return true; 111 } 112 return false; 113 } 114 } 115 116 public static class Creator 117 { 118 public static object Create(Type type) 119 { 120 if (type.IsEnumerable()) 121 { 122 return Activator.CreateInstance(typeof(List<>).MakeGenericType(type.GetGenericArguments()[0])); 123 } 124 125 if (type.IsInterface) 126 throw new Exception("don't know any implementation of this type: " + type.Name); 127 128 return Activator.CreateInstance(type); 129 } 130 } 131 132 public class MapperInjection : ConventionInjection 133 { 134 protected override bool Match(ConventionInfo c) 135 { 136 return c.SourceProp.Name == c.TargetProp.Name && 137 !c.SourceProp.Type.IsValueType && c.SourceProp.Type != typeof(string) && 138 !c.SourceProp.Type.IsGenericType && !c.TargetProp.Type.IsGenericType 139 || 140 c.SourceProp.Type.IsEnumerable() && 141 c.TargetProp.Type.IsEnumerable(); 142 } 143 144 protected override object SetValue(ConventionInfo c) 145 { 146 if (c.SourceProp.Value == null) return null; 147 return Mapper.Map(c.SourceProp.Value, c.TargetProp.Value, c.SourceProp.Type, c.TargetProp.Type); 148 } 149 } 150 151 public class EnumToInt : ConventionInjection 152 { 153 protected override bool Match(ConventionInfo c) 154 { 155 return c.SourceProp.Name == c.TargetProp.Name && 156 c.SourceProp.Type.IsSubclassOf(typeof(Enum)) && c.TargetProp.Type == typeof(int); 157 } 158 } 159 160 public class IntToEnum : ConventionInjection 161 { 162 protected override bool Match(ConventionInfo c) 163 { 164 return c.SourceProp.Name == c.TargetProp.Name && 165 c.SourceProp.Type == typeof(int) && c.TargetProp.Type.IsSubclassOf(typeof(Enum)); 166 } 167 } 168 169 //e.g. int? -> int 170 public class NullablesToNormal : ConventionInjection 171 { 172 protected override bool Match(ConventionInfo c) 173 { 174 return c.SourceProp.Name == c.TargetProp.Name && 175 Nullable.GetUnderlyingType(c.SourceProp.Type) == c.TargetProp.Type; 176 } 177 } 178 179 //e.g. int -> int? 180 public class NormalToNullables : ConventionInjection 181 { 182 protected override bool Match(ConventionInfo c) 183 { 184 return c.SourceProp.Name == c.TargetProp.Name && 185 c.SourceProp.Type == Nullable.GetUnderlyingType(c.TargetProp.Type); 186 } 187 }
实际应用实例:
1 public class Demo1 2 { 3 public string Key1 { get; set; } 4 public string Key2 { get; set; } 5 public string Property { get; set; } 6 } 7 8 public class Demo2 9 { 10 public string Key1 { get; set; } 11 public string Key2 { get; set; } 12 public string Property { get; set; } 13 } 14 15 class Program 16 { 17 public void Main() 18 { 19 (new HashSet<Demo1>()).ToDb((new HashSet<Demo2>()), 20 d1 => new { d1.Key1, d1.Key2 }, d2 => new { d2.Key1, d2.Key2 }); 21 } 22 } 23 }
为了说明 代码使用情况,只是一个简单的事例,并未弄得过于复杂。
(newHashSet<Demo1>())是我们的Collection数据源,
(new HashSet<Demo2>())是ef的目标Collection