PageRenderTime 59ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/Breeze.ContextProvider/ContextProvider.cs

https://github.com/WilliamBZA/breeze.server.net
C# | 516 lines | 384 code | 91 blank | 41 comment | 49 complexity | db48ae218b7fcc42ec0bcab8d26de945 MD5 | raw file
  1. using Newtonsoft.Json;
  2. using Newtonsoft.Json.Converters;
  3. using Newtonsoft.Json.Linq;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Data;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Net;
  11. using System.Reflection;
  12. using System.Transactions;
  13. using System.Xml.Linq;
  14. namespace Breeze.ContextProvider {
  15. // Base for EFContextProvider
  16. public abstract class ContextProvider {
  17. public IKeyGenerator KeyGenerator { get; set; }
  18. public static SaveOptions ExtractSaveOptions(dynamic dynSaveBundle) {
  19. var jsonSerializer = CreateJsonSerializer();
  20. var dynSaveOptions = dynSaveBundle.saveOptions;
  21. var saveOptions = (SaveOptions)jsonSerializer.Deserialize(new JTokenReader(dynSaveOptions), typeof(SaveOptions));
  22. return saveOptions;
  23. }
  24. public SaveOptions SaveOptions { get; set; }
  25. public string Metadata() {
  26. lock (_metadataLock) {
  27. if (_jsonMetadata == null) {
  28. _jsonMetadata = BuildJsonMetadata();
  29. }
  30. return _jsonMetadata;
  31. }
  32. }
  33. public static String XDocToJson(XDocument xDoc) {
  34. var sw = new StringWriter();
  35. using (var jsonWriter = new JsonPropertyFixupWriter(sw)) {
  36. // jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented;
  37. var jsonSerializer = new JsonSerializer();
  38. var converter = new XmlNodeConverter();
  39. jsonSerializer.Converters.Add(converter);
  40. jsonSerializer.Serialize(jsonWriter, xDoc);
  41. }
  42. var jsonText = sw.ToString();
  43. return jsonText;
  44. }
  45. public SaveResult SaveChanges(JObject saveBundle, TransactionSettings transactionSettings = null) {
  46. JsonSerializer = CreateJsonSerializer();
  47. var dynSaveBundle = (dynamic)saveBundle;
  48. var entitiesArray = (JArray)dynSaveBundle.entities;
  49. var dynSaveOptions = dynSaveBundle.saveOptions;
  50. SaveOptions = (SaveOptions) JsonSerializer.Deserialize(new JTokenReader(dynSaveOptions), typeof(SaveOptions));
  51. SaveWorkState = new SaveWorkState(this, entitiesArray);
  52. transactionSettings = transactionSettings ?? BreezeConfig.Instance.GetTransactionSettings();
  53. try {
  54. if (transactionSettings.TransactionType == TransactionType.TransactionScope) {
  55. var txOptions = transactionSettings.ToTransactionOptions();
  56. using (var txScope = new TransactionScope(TransactionScopeOption.Required, txOptions)) {
  57. OpenAndSave(SaveWorkState);
  58. txScope.Complete();
  59. }
  60. } else if (transactionSettings.TransactionType == TransactionType.DbTransaction) {
  61. this.OpenDbConnection();
  62. using (IDbTransaction tran = BeginTransaction(transactionSettings.IsolationLevelAs)) {
  63. try {
  64. OpenAndSave(SaveWorkState);
  65. tran.Commit();
  66. } catch {
  67. tran.Rollback();
  68. throw;
  69. }
  70. }
  71. } else {
  72. OpenAndSave(SaveWorkState);
  73. }
  74. } catch (EntityErrorsException e) {
  75. SaveWorkState.EntityErrors = e.EntityErrors;
  76. throw;
  77. } catch(Exception e2) {
  78. if (!HandleSaveException(e2, SaveWorkState)) {
  79. throw;
  80. }
  81. } finally {
  82. CloseDbConnection();
  83. }
  84. return SaveWorkState.ToSaveResult();
  85. }
  86. // allows subclasses to plug in own save exception handling
  87. // either throw an exception here, return false or return true and modify the saveWorkState.
  88. protected virtual bool HandleSaveException(Exception e, SaveWorkState saveWorkState) {
  89. return false;
  90. }
  91. private void OpenAndSave(SaveWorkState saveWorkState) {
  92. OpenDbConnection(); // ensure connection is available for BeforeSaveEntities
  93. saveWorkState.BeforeSave();
  94. SaveChangesCore(saveWorkState);
  95. saveWorkState.AfterSave();
  96. }
  97. private static JsonSerializer CreateJsonSerializer() {
  98. var serializerSettings = BreezeConfig.Instance.GetJsonSerializerSettings();
  99. var jsonSerializer = JsonSerializer.Create(serializerSettings);
  100. return jsonSerializer;
  101. }
  102. #region abstract and virtual methods
  103. /// <summary>
  104. /// Should only be called from BeforeSaveEntities and AfterSaveEntities.
  105. /// </summary>
  106. /// <returns>Open DbConnection used by the ContextProvider's implementation</returns>
  107. public abstract IDbConnection GetDbConnection();
  108. /// <summary>
  109. /// Internal use only. Should only be called by ContextProvider during SaveChanges.
  110. /// Opens the DbConnection used by the ContextProvider's implementation.
  111. /// Method must be idempotent; after it is called the first time, subsequent calls have no effect.
  112. /// </summary>
  113. protected abstract void OpenDbConnection();
  114. /// <summary>
  115. /// Internal use only. Should only be called by ContextProvider during SaveChanges.
  116. /// Closes the DbConnection used by the ContextProvider's implementation.
  117. /// </summary>
  118. protected abstract void CloseDbConnection();
  119. protected virtual IDbTransaction BeginTransaction(System.Data.IsolationLevel isolationLevel) {
  120. var conn = GetDbConnection();
  121. if (conn == null) return null;
  122. return conn.BeginTransaction(isolationLevel);
  123. }
  124. protected abstract String BuildJsonMetadata();
  125. protected abstract void SaveChangesCore(SaveWorkState saveWorkState);
  126. public virtual object[] GetKeyValues(EntityInfo entityInfo) {
  127. throw new NotImplementedException();
  128. }
  129. protected virtual EntityInfo CreateEntityInfo() {
  130. return new EntityInfo();
  131. }
  132. public EntityInfo CreateEntityInfo(Object entity, EntityState entityState = EntityState.Added) {
  133. var ei = CreateEntityInfo();
  134. ei.Entity = entity;
  135. ei.EntityState = entityState;
  136. ei.ContextProvider = this;
  137. return ei;
  138. }
  139. public Func<EntityInfo, bool> BeforeSaveEntityDelegate { get; set; }
  140. public Func<Dictionary<Type, List<EntityInfo>>, Dictionary<Type, List<EntityInfo>>> BeforeSaveEntitiesDelegate { get; set; }
  141. public Action<Dictionary<Type, List<EntityInfo>>, List<KeyMapping>> AfterSaveEntitiesDelegate { get; set; }
  142. /// <summary>
  143. /// The method is called for each entity to be saved before the save occurs. If this method returns 'false'
  144. /// then the entity will be excluded from the save. The base implementation returns the result of BeforeSaveEntityDelegate,
  145. /// or 'true' if BeforeSaveEntityDelegate is null.
  146. /// </summary>
  147. /// <param name="entityInfo"></param>
  148. /// <returns>true to include the entity in the save, false to exclude</returns>
  149. protected internal virtual bool BeforeSaveEntity(EntityInfo entityInfo) {
  150. if (BeforeSaveEntityDelegate != null) {
  151. return BeforeSaveEntityDelegate(entityInfo);
  152. } else {
  153. return true;
  154. }
  155. }
  156. /// <summary>
  157. /// Called after BeforeSaveEntity, and before saving the entities to the persistence layer.
  158. /// Allows adding, changing, and removing entities prior to save.
  159. /// The base implementation returns the result of BeforeSaveEntitiesDelegate, or the unchanged
  160. /// saveMap if BeforeSaveEntitiesDelegate is null.
  161. /// </summary>
  162. /// <param name="saveMap">A List of EntityInfo for each Type</param>
  163. /// <returns>The EntityInfo for each entity that should be saved</returns>
  164. protected internal virtual Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) {
  165. if (BeforeSaveEntitiesDelegate != null) {
  166. return BeforeSaveEntitiesDelegate(saveMap);
  167. } else {
  168. return saveMap;
  169. }
  170. }
  171. /// <summary>
  172. /// Called after the entities have been saved, and all the temporary keys have been replaced by real keys.
  173. /// The base implementation calls AfterSaveEntitiesDelegate, or does nothing if AfterSaveEntitiesDelegate is null.
  174. /// </summary>
  175. /// <param name="saveMap">The same saveMap that was returned from BeforeSaveEntities</param>
  176. /// <param name="keyMappings">The mapping of temporary keys to real keys</param>
  177. protected internal virtual void AfterSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap, List<KeyMapping> keyMappings) {
  178. if (AfterSaveEntitiesDelegate != null) {
  179. AfterSaveEntitiesDelegate(saveMap, keyMappings);
  180. }
  181. }
  182. #endregion
  183. protected internal EntityInfo CreateEntityInfoFromJson(dynamic jo, Type entityType) {
  184. var entityInfo = CreateEntityInfo();
  185. entityInfo.Entity = JsonSerializer.Deserialize(new JTokenReader(jo), entityType);
  186. entityInfo.EntityState = (EntityState)Enum.Parse(typeof(EntityState), (String)jo.entityAspect.entityState);
  187. entityInfo.ContextProvider = this;
  188. entityInfo.UnmappedValuesMap = JsonToDictionary(jo.__unmapped);
  189. entityInfo.OriginalValuesMap = JsonToDictionary(jo.entityAspect.originalValuesMap);
  190. var autoGeneratedKey = jo.entityAspect.autoGeneratedKey;
  191. if (entityInfo.EntityState == EntityState.Added && autoGeneratedKey != null) {
  192. entityInfo.AutoGeneratedKey = new AutoGeneratedKey(entityInfo.Entity, autoGeneratedKey);
  193. }
  194. return entityInfo;
  195. }
  196. private Dictionary<String, Object> JsonToDictionary(dynamic json) {
  197. if (json == null) return null;
  198. var jprops = ((System.Collections.IEnumerable)json).Cast<JProperty>();
  199. var dict = jprops.ToDictionary(jprop => jprop.Name, jprop => {
  200. var val = jprop.Value as JValue;
  201. if (val != null) {
  202. return val.Value;
  203. } else {
  204. return jprop.Value as JObject;
  205. }
  206. });
  207. return dict;
  208. }
  209. protected internal Type LookupEntityType(String entityTypeName) {
  210. var delims = new string[] { ":#" };
  211. var parts = entityTypeName.Split(delims, StringSplitOptions.None);
  212. var shortName = parts[0];
  213. var ns = parts[1];
  214. var typeName = ns + "." + shortName;
  215. var type = BreezeConfig.ProbeAssemblies
  216. .Select(a => a.GetType(typeName, false, true))
  217. .FirstOrDefault(t => t != null);
  218. if (type != null) {
  219. return type;
  220. } else {
  221. throw new ArgumentException("Assembly could not be found for " + entityTypeName);
  222. }
  223. }
  224. protected static Lazy<Type> KeyGeneratorType = new Lazy<Type>(() => {
  225. var typeCandidates = BreezeConfig.ProbeAssemblies.Concat(new Assembly[] { typeof(IKeyGenerator).Assembly })
  226. .SelectMany(a => a.GetTypes()).ToList();
  227. var generatorTypes = typeCandidates.Where(t => typeof(IKeyGenerator).IsAssignableFrom(t) && !t.IsAbstract)
  228. .ToList();
  229. if (generatorTypes.Count == 0) {
  230. throw new Exception("Unable to locate a KeyGenerator implementation.");
  231. }
  232. return generatorTypes.First();
  233. });
  234. protected SaveWorkState SaveWorkState { get; private set; }
  235. protected JsonSerializer JsonSerializer { get; private set; }
  236. private object _metadataLock = new object();
  237. private string _jsonMetadata;
  238. }
  239. public class SaveWorkState {
  240. public SaveWorkState(ContextProvider contextProvider, JArray entitiesArray) {
  241. ContextProvider = contextProvider;
  242. var jObjects = entitiesArray.Select(jt => (dynamic)jt).ToList();
  243. var groups = jObjects.GroupBy(jo => (String)jo.entityAspect.entityTypeName).ToList();
  244. EntityInfoGroups = groups.Select(g => {
  245. var entityType = ContextProvider.LookupEntityType(g.Key);
  246. var entityInfos = g.Select(jo => ContextProvider.CreateEntityInfoFromJson(jo, entityType)).Cast<EntityInfo>().ToList();
  247. return new EntityGroup() { EntityType = entityType, EntityInfos = entityInfos };
  248. }).ToList();
  249. }
  250. public void BeforeSave() {
  251. SaveMap = new Dictionary<Type, List<EntityInfo>>();
  252. EntitiesWithAutoGeneratedKeys = new List<EntityInfo>();
  253. EntityInfoGroups.ForEach(eg => {
  254. var entityInfos = eg.EntityInfos.Where(ei => ContextProvider.BeforeSaveEntity(ei)).ToList();
  255. EntitiesWithAutoGeneratedKeys.AddRange(entityInfos.Where(ei => ei.AutoGeneratedKey != null));
  256. SaveMap.Add(eg.EntityType, entityInfos);
  257. });
  258. SaveMap = ContextProvider.BeforeSaveEntities(SaveMap);
  259. }
  260. public void AfterSave() {
  261. ContextProvider.AfterSaveEntities(SaveMap, KeyMappings);
  262. }
  263. public ContextProvider ContextProvider;
  264. protected List<EntityGroup> EntityInfoGroups;
  265. public Dictionary<Type, List<EntityInfo>> SaveMap { get; set; }
  266. public List<EntityInfo> EntitiesWithAutoGeneratedKeys { get; set; }
  267. public List<KeyMapping> KeyMappings;
  268. public List<EntityError> EntityErrors;
  269. public class EntityGroup {
  270. public Type EntityType;
  271. public List<EntityInfo> EntityInfos;
  272. }
  273. public SaveResult ToSaveResult() {
  274. if (EntityErrors != null) {
  275. return new SaveResult() { Errors = EntityErrors.Cast<Object>().ToList() };
  276. } else {
  277. var entities = SaveMap.SelectMany(kvp => kvp.Value.Select(entityInfo => entityInfo.Entity)).ToList();
  278. return new SaveResult() { Entities = entities, KeyMappings = KeyMappings };
  279. }
  280. }
  281. }
  282. public class SaveOptions {
  283. public bool AllowConcurrentSaves { get; set; }
  284. public Object Tag { get; set; }
  285. }
  286. public interface IKeyGenerator {
  287. void UpdateKeys(List<TempKeyInfo> keys);
  288. }
  289. // instances of this sent to KeyGenerator
  290. public class TempKeyInfo {
  291. public TempKeyInfo(EntityInfo entityInfo) {
  292. _entityInfo = entityInfo;
  293. }
  294. public Object Entity {
  295. get { return _entityInfo.Entity; }
  296. }
  297. public Object TempValue {
  298. get { return _entityInfo.AutoGeneratedKey.TempValue; }
  299. }
  300. public Object RealValue {
  301. get { return _entityInfo.AutoGeneratedKey.RealValue; }
  302. set { _entityInfo.AutoGeneratedKey.RealValue = value; }
  303. }
  304. public PropertyInfo Property {
  305. get { return _entityInfo.AutoGeneratedKey.Property; }
  306. }
  307. private EntityInfo _entityInfo;
  308. }
  309. [Flags]
  310. public enum EntityState {
  311. Detached = 1,
  312. Unchanged = 2,
  313. Added = 4,
  314. Deleted = 8,
  315. Modified = 16,
  316. }
  317. public class EntityInfo {
  318. protected internal EntityInfo() {
  319. }
  320. public ContextProvider ContextProvider { get; internal set; }
  321. public Object Entity { get; internal set; }
  322. public EntityState EntityState { get; set; }
  323. public Dictionary<String, Object> OriginalValuesMap { get; set; }
  324. public bool ForceUpdate { get; set; }
  325. public AutoGeneratedKey AutoGeneratedKey { get; set; }
  326. public Dictionary<String, Object> UnmappedValuesMap { get; internal set; }
  327. }
  328. public enum AutoGeneratedKeyType {
  329. None,
  330. Identity,
  331. KeyGenerator
  332. }
  333. public class AutoGeneratedKey {
  334. public AutoGeneratedKey(Object entity, dynamic autoGeneratedKey) {
  335. Entity = entity;
  336. PropertyName = autoGeneratedKey.propertyName;
  337. AutoGeneratedKeyType = (AutoGeneratedKeyType)Enum.Parse(typeof(AutoGeneratedKeyType), (String)autoGeneratedKey.autoGeneratedKeyType);
  338. // TempValue and RealValue will be set later. - TempValue during Add, RealValue after save completes.
  339. }
  340. public Object Entity;
  341. public AutoGeneratedKeyType AutoGeneratedKeyType;
  342. public String PropertyName;
  343. public PropertyInfo Property {
  344. get {
  345. if (_property == null) {
  346. _property = Entity.GetType().GetProperty(PropertyName,
  347. BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  348. }
  349. return _property;
  350. }
  351. }
  352. public Object TempValue;
  353. public Object RealValue;
  354. private PropertyInfo _property;
  355. }
  356. // Types returned to javascript as Json.
  357. public class SaveResult {
  358. public List<Object> Entities;
  359. public List<KeyMapping> KeyMappings;
  360. public List<Object> Errors;
  361. }
  362. public class KeyMapping {
  363. public String EntityTypeName;
  364. public Object TempValue;
  365. public Object RealValue;
  366. }
  367. public class SaveError {
  368. public SaveError(IEnumerable<EntityError> entityErrors) {
  369. EntityErrors = entityErrors.ToList();
  370. }
  371. public List<EntityError> EntityErrors { get; protected set; }
  372. }
  373. public class EntityErrorsException : Exception {
  374. public EntityErrorsException(IEnumerable<EntityError> entityErrors) {
  375. EntityErrors = entityErrors.ToList();
  376. StatusCode = HttpStatusCode.Forbidden;
  377. }
  378. public EntityErrorsException(String message, IEnumerable<EntityError> entityErrors)
  379. : base(message) {
  380. EntityErrors = entityErrors.ToList();
  381. StatusCode = HttpStatusCode.Forbidden;
  382. }
  383. public HttpStatusCode StatusCode { get; set; }
  384. public List<EntityError> EntityErrors { get; protected set; }
  385. }
  386. public class EntityError {
  387. public String ErrorName;
  388. public String EntityTypeName;
  389. public Object[] KeyValues;
  390. public String PropertyName;
  391. public string ErrorMessage;
  392. }
  393. public class JsonPropertyFixupWriter : JsonTextWriter {
  394. public JsonPropertyFixupWriter(TextWriter textWriter)
  395. : base(textWriter) {
  396. _isDataType = false;
  397. }
  398. public override void WritePropertyName(string name) {
  399. if (name.StartsWith("@")) {
  400. name = name.Substring(1);
  401. }
  402. name = ToCamelCase(name);
  403. _isDataType = name == "type";
  404. base.WritePropertyName(name);
  405. }
  406. public override void WriteValue(string value) {
  407. if (_isDataType && !value.StartsWith("Edm.")) {
  408. base.WriteValue("Edm." + value);
  409. } else {
  410. base.WriteValue(value);
  411. }
  412. }
  413. private static string ToCamelCase(string s) {
  414. if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0])) {
  415. return s;
  416. }
  417. string str = char.ToLower(s[0], CultureInfo.InvariantCulture).ToString((IFormatProvider)CultureInfo.InvariantCulture);
  418. if (s.Length > 1) {
  419. str = str + s.Substring(1);
  420. }
  421. return str;
  422. }
  423. private bool _isDataType;
  424. }
  425. }