PageRenderTime 66ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/Breeze.ContextProvider.EF6/EFContextProvider.cs

https://github.com/WilliamBZA/breeze.server.net
C# | 817 lines | 602 code | 130 blank | 85 comment | 127 complexity | 3cecd9d87243289df9bf830a09fe8c4a MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.ComponentModel.DataAnnotations;
  5. using System.Configuration;
  6. using System.Data;
  7. using System.Data.Common;
  8. using System.Data.Entity;
  9. using System.Data.Entity.Core;
  10. using System.Data.Entity.Core.Common;
  11. using System.Data.Entity.Core.EntityClient;
  12. using System.Data.Entity.Core.Metadata.Edm;
  13. using System.Data.Entity.Core.Objects;
  14. using System.Data.Entity.Core.Objects.DataClasses;
  15. using System.Data.Entity.Infrastructure;
  16. using System.Data.Entity.Validation;
  17. using System.Globalization;
  18. using System.IO;
  19. using System.Linq;
  20. using System.Reflection;
  21. using System.Xml;
  22. using System.Xml.Linq;
  23. using Newtonsoft.Json;
  24. using Newtonsoft.Json.Converters;
  25. using Newtonsoft.Json.Linq;
  26. using Breeze.ContextProvider;
  27. using BreezeCp = Breeze.ContextProvider;
  28. namespace Breeze.ContextProvider.EF6 {
  29. public interface IEFContextProvider {
  30. ObjectContext ObjectContext { get; }
  31. String GetEntitySetName(Type entityType);
  32. }
  33. // T is either a subclass of DbContext or a subclass of ObjectContext
  34. public class EFContextProvider<T> : Breeze.ContextProvider.ContextProvider, IEFContextProvider where T : class, new() {
  35. public EFContextProvider() {
  36. }
  37. [Obsolete("The contextName is no longer needed. This overload will be removed after Dec 31st 2012.")]
  38. public EFContextProvider(string contextName) {
  39. }
  40. public T Context {
  41. get {
  42. if (_context == null) {
  43. _context = CreateContext();
  44. // Disable lazy loading and proxy creation as this messes up the data service.
  45. if (typeof(ObjectContext).IsAssignableFrom(typeof(T))) {
  46. var objCtx = (ObjectContext)(Object)_context;
  47. objCtx.ContextOptions.LazyLoadingEnabled = false;
  48. } else {
  49. var dbCtx = (DbContext)(Object)_context;
  50. dbCtx.Configuration.ProxyCreationEnabled = false;
  51. dbCtx.Configuration.LazyLoadingEnabled = false;
  52. }
  53. }
  54. return _context;
  55. }
  56. }
  57. protected virtual T CreateContext() {
  58. return new T();
  59. }
  60. public ObjectContext ObjectContext {
  61. get {
  62. if (Context is DbContext) {
  63. return ((IObjectContextAdapter)Context).ObjectContext;
  64. } else {
  65. return (ObjectContext)(Object)Context;
  66. }
  67. }
  68. }
  69. /// <summary>Gets the EntityConnection from the ObjectContext.</summary>
  70. public DbConnection EntityConnection {
  71. get {
  72. return (DbConnection)GetDbConnection();
  73. }
  74. }
  75. /// <summary>Gets the StoreConnection from the ObjectContext.</summary>
  76. public DbConnection StoreConnection {
  77. get {
  78. return ((EntityConnection)GetDbConnection()).StoreConnection;
  79. }
  80. }
  81. /// <summary>Gets the current transaction, if one is in progress.</summary>
  82. public EntityTransaction EntityTransaction {
  83. get; private set;
  84. }
  85. /// <summary>Gets the EntityConnection from the ObjectContext.</summary>
  86. public override IDbConnection GetDbConnection() {
  87. return ObjectContext.Connection;
  88. }
  89. /// <summary>
  90. /// Opens the DbConnection used by the Context.
  91. /// If the connection will be used outside of the DbContext, this method should be called prior to DbContext
  92. /// initialization, so that the connection will already be open when the DbContext uses it. This keeps
  93. /// the DbContext from closing the connection, so it must be closed manually.
  94. /// See http://blogs.msdn.com/b/diego/archive/2012/01/26/exception-from-dbcontext-api-entityconnection-can-only-be-constructed-with-a-closed-dbconnection.aspx
  95. /// </summary>
  96. /// <returns></returns>
  97. protected override void OpenDbConnection() {
  98. var ec = ObjectContext.Connection as EntityConnection;
  99. if (ec.State == ConnectionState.Closed) ec.Open();
  100. }
  101. protected override void CloseDbConnection() {
  102. if (_context != null) {
  103. var ec = ObjectContext.Connection as EntityConnection;
  104. ec.Close();
  105. ec.Dispose();
  106. }
  107. }
  108. // Override BeginTransaction so we can keep the current transaction in a property
  109. protected override IDbTransaction BeginTransaction(System.Data.IsolationLevel isolationLevel) {
  110. var conn = GetDbConnection();
  111. if (conn == null) return null;
  112. EntityTransaction = (EntityTransaction) conn.BeginTransaction(isolationLevel);
  113. return EntityTransaction;
  114. }
  115. #region Base implementation overrides
  116. protected override string BuildJsonMetadata() {
  117. var json = GetMetadataFromContext(Context);
  118. var altMetadata = BuildAltJsonMetadata();
  119. if (altMetadata != null) {
  120. json = "{ \"altMetadata\": " + altMetadata + "," + json.Substring(1);
  121. }
  122. return json;
  123. }
  124. protected virtual string BuildAltJsonMetadata() {
  125. // default implementation
  126. return null; // "{ \"foo\": 8, \"bar\": \"xxx\" }";
  127. }
  128. protected override EntityInfo CreateEntityInfo() {
  129. return new EFEntityInfo();
  130. }
  131. public override object[] GetKeyValues(EntityInfo entityInfo) {
  132. return GetKeyValues(entityInfo.Entity);
  133. }
  134. public object[] GetKeyValues(object entity) {
  135. string esName;
  136. try {
  137. esName = GetEntitySetName(entity.GetType());
  138. } catch (Exception ex) {
  139. throw new ArgumentException("EntitySet not found for type " + entity.GetType(), ex);
  140. }
  141. var key = ObjectContext.CreateEntityKey(esName, entity);
  142. var keyValues = key.EntityKeyValues.Select(km => km.Value).ToArray();
  143. return keyValues;
  144. }
  145. protected override void SaveChangesCore(SaveWorkState saveWorkState) {
  146. var saveMap = saveWorkState.SaveMap;
  147. var deletedEntities = ProcessSaves(saveMap);
  148. if (deletedEntities.Any()) {
  149. ProcessAllDeleted(deletedEntities);
  150. }
  151. ProcessAutogeneratedKeys(saveWorkState.EntitiesWithAutoGeneratedKeys);
  152. int count;
  153. try {
  154. if (Context is DbContext) {
  155. count = ((DbContext)(object)Context).SaveChanges();
  156. } else {
  157. count = ObjectContext.SaveChanges(System.Data.Entity.Core.Objects.SaveOptions.AcceptAllChangesAfterSave);
  158. }
  159. } catch (DbEntityValidationException e) {
  160. var entityErrors = new List<EntityError>();
  161. foreach (var eve in e.EntityValidationErrors) {
  162. var entity = eve.Entry.Entity;
  163. var entityTypeName = entity.GetType().FullName;
  164. Object[] keyValues;
  165. var key = ObjectContext.ObjectStateManager.GetObjectStateEntry(entity).EntityKey;
  166. if (key.EntityKeyValues != null) {
  167. keyValues = key.EntityKeyValues.Select(km => km.Value).ToArray();
  168. } else {
  169. var entityInfo = saveWorkState.EntitiesWithAutoGeneratedKeys.FirstOrDefault(ei => ei.Entity == entity);
  170. if (entityInfo != null) {
  171. keyValues = new Object[] { entityInfo.AutoGeneratedKey.TempValue };
  172. } else {
  173. // how can this happen?
  174. keyValues = null;
  175. }
  176. }
  177. foreach (var ve in eve.ValidationErrors) {
  178. var entityError = new EntityError() {
  179. EntityTypeName = entityTypeName,
  180. KeyValues = keyValues,
  181. ErrorMessage = ve.ErrorMessage,
  182. PropertyName = ve.PropertyName
  183. };
  184. entityErrors.Add(entityError);
  185. }
  186. }
  187. saveWorkState.EntityErrors = entityErrors;
  188. } catch (DataException e) {
  189. var nextException = (Exception) e;
  190. while (nextException.InnerException != null) {
  191. nextException = nextException.InnerException;
  192. }
  193. if (nextException == e) {
  194. throw;
  195. } else {
  196. //create a new exception that contains the toplevel exception
  197. //but has the innermost exception message propogated to the top.
  198. //For EF exceptions, this is often the most 'relevant' message.
  199. throw new Exception(nextException.Message, e);
  200. }
  201. } catch (Exception e) {
  202. throw;
  203. }
  204. saveWorkState.KeyMappings = UpdateAutoGeneratedKeys(saveWorkState.EntitiesWithAutoGeneratedKeys);
  205. }
  206. #endregion
  207. #region Save related methods
  208. private List<EFEntityInfo> ProcessSaves(Dictionary<Type, List<EntityInfo>> saveMap) {
  209. var deletedEntities = new List<EFEntityInfo>();
  210. foreach (var kvp in saveMap) {
  211. var entityType = kvp.Key;
  212. var entitySetName = GetEntitySetName(entityType);
  213. foreach (EFEntityInfo entityInfo in kvp.Value) {
  214. // entityInfo.EFContextProvider = this; may be needed eventually.
  215. entityInfo.EntitySetName = entitySetName;
  216. ProcessEntity(entityInfo);
  217. if (entityInfo.EntityState == BreezeCp.EntityState.Deleted) {
  218. deletedEntities.Add(entityInfo);
  219. }
  220. }
  221. }
  222. return deletedEntities;
  223. }
  224. private void ProcessAllDeleted(List<EFEntityInfo> deletedEntities) {
  225. deletedEntities.ForEach(entityInfo => {
  226. RestoreOriginal(entityInfo);
  227. var entry = GetOrAddObjectStateEntry(entityInfo);
  228. entry.ChangeState(System.Data.Entity.EntityState.Deleted);
  229. entityInfo.ObjectStateEntry = entry;
  230. });
  231. }
  232. private void ProcessAutogeneratedKeys(List<EntityInfo> entitiesWithAutoGeneratedKeys) {
  233. var tempKeys = entitiesWithAutoGeneratedKeys.Cast<EFEntityInfo>().Where(
  234. entityInfo => entityInfo.AutoGeneratedKey.AutoGeneratedKeyType == AutoGeneratedKeyType.KeyGenerator)
  235. .Select(ei => new TempKeyInfo(ei))
  236. .ToList();
  237. if (tempKeys.Count == 0) return;
  238. if (this.KeyGenerator == null) {
  239. this.KeyGenerator = GetKeyGenerator();
  240. }
  241. this.KeyGenerator.UpdateKeys(tempKeys);
  242. tempKeys.ForEach(tki => {
  243. // Clever hack - next 3 lines cause all entities related to tki.Entity to have
  244. // their relationships updated. So all related entities for each tki are updated.
  245. // Basically we set the entity to look like a preexisting entity by setting its
  246. // entityState to unchanged. This is what fixes up the relations, then we set it back to added
  247. // Now when we update the pk - all fks will get changed as well. Note that the fk change will only
  248. // occur during the save.
  249. var entry = GetObjectStateEntry(tki.Entity);
  250. entry.ChangeState(System.Data.Entity.EntityState.Unchanged);
  251. entry.ChangeState(System.Data.Entity.EntityState.Added);
  252. var val = ConvertValue(tki.RealValue, tki.Property.PropertyType);
  253. tki.Property.SetValue(tki.Entity, val, null);
  254. });
  255. }
  256. private IKeyGenerator GetKeyGenerator() {
  257. var generatorType = KeyGeneratorType.Value;
  258. return (IKeyGenerator)Activator.CreateInstance(generatorType, StoreConnection);
  259. }
  260. private EntityInfo ProcessEntity(EFEntityInfo entityInfo) {
  261. ObjectStateEntry ose;
  262. if (entityInfo.EntityState == BreezeCp.EntityState.Modified) {
  263. ose = HandleModified(entityInfo);
  264. } else if (entityInfo.EntityState == BreezeCp.EntityState.Added) {
  265. ose = HandleAdded(entityInfo);
  266. } else if (entityInfo.EntityState == BreezeCp.EntityState.Deleted) {
  267. // for 1st pass this does NOTHING
  268. ose = HandleDeletedPart1(entityInfo);
  269. } else {
  270. // needed for many to many to get both ends into the objectContext
  271. ose = HandleUnchanged(entityInfo);
  272. }
  273. entityInfo.ObjectStateEntry = ose;
  274. return entityInfo;
  275. }
  276. private ObjectStateEntry HandleAdded(EFEntityInfo entityInfo) {
  277. var entry = AddObjectStateEntry(entityInfo);
  278. if (entityInfo.AutoGeneratedKey != null) {
  279. entityInfo.AutoGeneratedKey.TempValue = GetOsePropertyValue(entry, entityInfo.AutoGeneratedKey.PropertyName);
  280. }
  281. entry.ChangeState(System.Data.Entity.EntityState.Added);
  282. return entry;
  283. }
  284. private ObjectStateEntry HandleModified(EFEntityInfo entityInfo) {
  285. var entry = AddObjectStateEntry(entityInfo);
  286. // EntityState will be changed to modified during the update from the OriginalValuesMap
  287. // Do NOT change this to EntityState.Modified because this will cause the entire record to update.
  288. entry.ChangeState(System.Data.Entity.EntityState.Unchanged);
  289. // updating the original values is necessary under certain conditions when we change a foreign key field
  290. // because the before value is used to determine ordering.
  291. UpdateOriginalValues(entry, entityInfo);
  292. //foreach (var dep in GetModifiedComplexTypeProperties(entity, metadata)) {
  293. // entry.SetModifiedProperty(dep.Name);
  294. //}
  295. if ((int)entry.State != (int) System.Data.Entity.EntityState.Modified || entityInfo.ForceUpdate) {
  296. // _originalValusMap can be null if we mark entity.SetModified but don't actually change anything.
  297. entry.ChangeState(System.Data.Entity.EntityState.Modified);
  298. }
  299. return entry;
  300. }
  301. private ObjectStateEntry HandleUnchanged(EFEntityInfo entityInfo) {
  302. var entry = AddObjectStateEntry(entityInfo);
  303. entry.ChangeState(System.Data.Entity.EntityState.Unchanged);
  304. return entry;
  305. }
  306. private ObjectStateEntry HandleDeletedPart1(EntityInfo entityInfo) {
  307. return null;
  308. }
  309. private EntityInfo RestoreOriginal(EntityInfo entityInfo) {
  310. // fk's can get cleared depending on the order in which deletions occur -
  311. // EF needs the original values of these fk's under certain circumstances - ( not sure entirely what these are).
  312. // so we restore the original fk values right before we attach the entity
  313. // shouldn't be any side effects because we delete it immediately after.
  314. // ??? Do concurrency values also need to be restored in some cases
  315. // This method restores more than it actually needs to because we don't
  316. // have metadata easily avail here, but usually a deleted entity will
  317. // not have much in the way of OriginalValues.
  318. if (entityInfo.OriginalValuesMap == null || entityInfo.OriginalValuesMap.Keys.Count == 0) {
  319. return entityInfo;
  320. }
  321. var entity = entityInfo.Entity;
  322. var entityType = entity.GetType();
  323. var efEntityType = GetEntityType(ObjectContext.MetadataWorkspace, entityType);
  324. var keyPropertyNames = efEntityType.KeyMembers.Select(km => km.Name).ToList();
  325. var ovl = entityInfo.OriginalValuesMap.ToList();
  326. for (var i = 0; i< ovl.Count; i++) {
  327. var kvp = ovl[i];
  328. var propName = kvp.Key;
  329. // keys should be ignored
  330. if (keyPropertyNames.Contains(propName)) continue;
  331. var pi = entityType.GetProperty(propName);
  332. // unmapped properties should be ignored.
  333. if (pi == null) continue;
  334. var nnPropType = TypeFns.GetNonNullableType(pi.PropertyType);
  335. // presumption here is that only a predefined type could be a fk or concurrency property
  336. if (TypeFns.IsPredefinedType(nnPropType)) {
  337. SetPropertyValue(entity, propName, kvp.Value);
  338. }
  339. }
  340. return entityInfo;
  341. }
  342. private static EntityType GetEntityType(MetadataWorkspace mws, Type entityType) {
  343. EntityType et =
  344. mws.GetItems<EntityType>(DataSpace.OSpace)
  345. .Single(x => x.Name == entityType.Name);
  346. return et;
  347. }
  348. private static void UpdateOriginalValues(ObjectStateEntry entry, EntityInfo entityInfo) {
  349. var originalValuesMap = entityInfo.OriginalValuesMap;
  350. if (originalValuesMap == null || originalValuesMap.Keys.Count == 0) return;
  351. var originalValuesRecord = entry.GetUpdatableOriginalValues();
  352. originalValuesMap.ToList().ForEach(kvp => {
  353. var propertyName = kvp.Key;
  354. var originalValue = kvp.Value;
  355. try {
  356. entry.SetModifiedProperty(propertyName);
  357. if (originalValue is JObject) {
  358. // only really need to perform updating original values on key properties
  359. // and a complex object cannot be a key.
  360. } else {
  361. var ordinal = originalValuesRecord.GetOrdinal(propertyName);
  362. var fieldType = originalValuesRecord.GetFieldType(ordinal);
  363. var originalValueConverted = ConvertValue(originalValue, fieldType);
  364. if (originalValueConverted == null) {
  365. // bug - hack because of bug in EF - see
  366. // http://social.msdn.microsoft.com/Forums/nl/adodotnetentityframework/thread/cba1c425-bf82-4182-8dfb-f8da0572e5da
  367. var temp = entry.CurrentValues[ordinal];
  368. entry.CurrentValues.SetDBNull(ordinal);
  369. entry.ApplyOriginalValues(entry.Entity);
  370. entry.CurrentValues.SetValue(ordinal, temp);
  371. } else {
  372. originalValuesRecord.SetValue(ordinal, originalValueConverted);
  373. }
  374. }
  375. } catch (Exception e) {
  376. if (e.Message.Contains(" part of the entity's key")) {
  377. throw;
  378. } else {
  379. // this can happen for "custom" data entity properties.
  380. }
  381. }
  382. });
  383. }
  384. private List<KeyMapping> UpdateAutoGeneratedKeys(List<EntityInfo> entitiesWithAutoGeneratedKeys) {
  385. // where clause is necessary in case the Entities were suppressed in the beforeSave event.
  386. var keyMappings = entitiesWithAutoGeneratedKeys.Cast<EFEntityInfo>()
  387. .Where(entityInfo => entityInfo.ObjectStateEntry != null)
  388. .Select(entityInfo => {
  389. var autoGeneratedKey = entityInfo.AutoGeneratedKey;
  390. if (autoGeneratedKey.AutoGeneratedKeyType == AutoGeneratedKeyType.Identity) {
  391. autoGeneratedKey.RealValue = GetOsePropertyValue(entityInfo.ObjectStateEntry, autoGeneratedKey.PropertyName);
  392. }
  393. return new KeyMapping() {
  394. EntityTypeName = entityInfo.Entity.GetType().FullName,
  395. TempValue = autoGeneratedKey.TempValue,
  396. RealValue = autoGeneratedKey.RealValue
  397. };
  398. });
  399. return keyMappings.ToList();
  400. }
  401. private Object GetOsePropertyValue(ObjectStateEntry ose, String propertyName) {
  402. var currentValues = ose.CurrentValues;
  403. var ix = currentValues.GetOrdinal(propertyName);
  404. return currentValues[ix];
  405. }
  406. private void SetOsePropertyValue(ObjectStateEntry ose, String propertyName, Object value) {
  407. var currentValues = ose.CurrentValues;
  408. var ix = currentValues.GetOrdinal(propertyName);
  409. currentValues.SetValue(ix, value);
  410. }
  411. private void SetPropertyValue(Object entity, String propertyName, Object value) {
  412. var propInfo = entity.GetType().GetProperty(propertyName,
  413. BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  414. // exit if unmapped property.
  415. if (propInfo == null) return;
  416. if (propInfo.CanWrite) {
  417. var val = ConvertValue(value, propInfo.PropertyType);
  418. propInfo.SetValue(entity, val, null);
  419. } else {
  420. throw new Exception(String.Format("Unable to write to property '{0}' on type: '{1}'", propertyName,
  421. entity.GetType()));
  422. }
  423. }
  424. private static Object ConvertValue(Object val, Type toType) {
  425. Object result;
  426. if (val == null) return val;
  427. if (toType == val.GetType()) return val;
  428. var nnToType = TypeFns.GetNonNullableType(toType);
  429. if (typeof(IConvertible).IsAssignableFrom(nnToType)) {
  430. result = Convert.ChangeType(val, nnToType, System.Threading.Thread.CurrentThread.CurrentCulture);
  431. } else if (val is JObject) {
  432. var serializer = new JsonSerializer();
  433. result = serializer.Deserialize(new JTokenReader((JObject)val), toType);
  434. } else {
  435. // Guids fail above - try this
  436. TypeConverter typeConverter = TypeDescriptor.GetConverter(toType);
  437. result = typeConverter.ConvertFrom(val);
  438. }
  439. return result;
  440. }
  441. private ObjectStateEntry GetOrAddObjectStateEntry(EFEntityInfo entityInfo) {
  442. ObjectStateEntry entry;
  443. if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entityInfo.Entity, out entry)) return entry;
  444. return AddObjectStateEntry(entityInfo);
  445. }
  446. private ObjectStateEntry AddObjectStateEntry(EFEntityInfo entityInfo) {
  447. var ose = GetObjectStateEntry(entityInfo.Entity, false);
  448. if (ose != null) return ose;
  449. ObjectContext.AddObject(entityInfo.EntitySetName, entityInfo.Entity);
  450. // Attach has lots of side effect - add has far fewer.
  451. return GetObjectStateEntry(entityInfo);
  452. }
  453. private ObjectStateEntry AttachObjectStateEntry(EFEntityInfo entityInfo) {
  454. ObjectContext.AttachTo(entityInfo.EntitySetName, entityInfo.Entity);
  455. // Attach has lots of side effect - add has far fewer.
  456. return GetObjectStateEntry(entityInfo);
  457. }
  458. private ObjectStateEntry GetObjectStateEntry(EFEntityInfo entityInfo) {
  459. return GetObjectStateEntry(entityInfo.Entity);
  460. }
  461. private ObjectStateEntry GetObjectStateEntry(Object entity, bool errorIfNotFound = true) {
  462. ObjectStateEntry entry;
  463. if (!ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) {
  464. if (errorIfNotFound) {
  465. throw new Exception("unable to add to context: " + entity);
  466. }
  467. }
  468. return entry;
  469. }
  470. #endregion
  471. #region Metadata methods
  472. public static String GetMetadataFromDbFirstAssembly(Assembly assembly, String resourcePrefix = "") {
  473. var xDoc = GetEmbeddedXDoc(assembly, resourcePrefix + ".csdl");
  474. // This is needed because the raw edmx has a different namespace than the CLR types that it references.
  475. xDoc = UpdateCSpaceOSpaceMapping(xDoc, assembly, resourcePrefix);
  476. return XDocToJson(xDoc);
  477. }
  478. public static String GetMetadataFromContext(Object context) {
  479. if (context is DbContext) {
  480. return GetMetadataFromDbContext(context);
  481. } else {
  482. return GetMetadataFromObjectContext(context);
  483. }
  484. }
  485. private static String GetMetadataFromDbContext(Object context) {
  486. var dbContext = (DbContext)context;
  487. XElement xele;
  488. try {
  489. using (var swriter = new StringWriter()) {
  490. using (var xwriter = new XmlTextWriter(swriter)) {
  491. EdmxWriter.WriteEdmx(dbContext, xwriter);
  492. xele = XElement.Parse(swriter.ToString());
  493. }
  494. }
  495. } catch (Exception e) {
  496. if (e is NotSupportedException) {
  497. // DbContext that fails on WriteEdmx is likely a DataBase first DbContext.
  498. return GetMetadataFromObjectContext(dbContext);
  499. } else {
  500. throw;
  501. }
  502. }
  503. var ns = xele.Name.Namespace;
  504. var conceptualEle = xele.Descendants(ns + "ConceptualModels").First();
  505. var schemaEle = conceptualEle.Elements().First(ele => ele.Name.LocalName == "Schema");
  506. var xDoc = XDocument.Load(schemaEle.CreateReader());
  507. var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
  508. // This is needed because the raw edmx has a different namespace than the CLR types that it references.
  509. xDoc = UpdateCSpaceOSpaceMapping(xDoc, objectContext);
  510. return XDocToJson(xDoc);
  511. }
  512. private static String GetMetadataFromObjectContext(Object context) {
  513. var ocAssembly = context.GetType().Assembly;
  514. var ocNamespace = context.GetType().Namespace;
  515. var objectContext = GetObjectContext(context);
  516. var normalizedResourceName = ExtractResourceName(objectContext);
  517. var xDoc = GetEmbeddedXDoc(ocAssembly, normalizedResourceName);
  518. // This is needed because the raw edmx has a different namespace than the CLR types that it references.
  519. xDoc = UpdateCSpaceOSpaceMapping(xDoc, objectContext);
  520. return XDocToJson(xDoc);
  521. }
  522. private static ObjectContext GetObjectContext(Object context) {
  523. ObjectContext objectContext;
  524. if (context is DbContext) {
  525. var dbContext = (DbContext)context;
  526. objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
  527. } else {
  528. objectContext = (ObjectContext)context;
  529. }
  530. return objectContext;
  531. }
  532. private static string ExtractResourceName(ObjectContext objectContext) {
  533. var ec = objectContext.Connection as EntityConnection;
  534. if (ec == null) {
  535. throw new Exception("Unable to create an EntityConnection for this ObjectContext");
  536. }
  537. var ecBuilder = new EntityConnectionStringBuilder(ec.ConnectionString);
  538. var metadataString = "";
  539. if (!String.IsNullOrEmpty(ecBuilder.Name)) {
  540. metadataString = GetConnectionStringFromConfig(ecBuilder.Name);
  541. } else if (!String.IsNullOrEmpty(ecBuilder.Metadata)) {
  542. metadataString = ecBuilder.Metadata;
  543. } else {
  544. throw new Exception("Unable to locate EDMX metadata for " + ec.ConnectionString);
  545. }
  546. var csdlResource = metadataString.Split('|', ';', '=')
  547. .FirstOrDefault(s => {
  548. s = s.Trim();
  549. return s.StartsWith(ResourcePrefix) && s.EndsWith(".csdl");
  550. });
  551. if (csdlResource == null) {
  552. throw new Exception("Unable to locate a 'csdl' resource within this connection:" + ec.ConnectionString);
  553. }
  554. var parts = csdlResource.Split('/', '.');
  555. var normalizedResourceName = String.Join(".", parts.Skip(parts.Length - 2));
  556. return normalizedResourceName;
  557. }
  558. private static XDocument GetEmbeddedXDoc(Assembly ocAssembly, String resourceSuffix) {
  559. var resourceNames = ocAssembly.GetManifestResourceNames();
  560. var manifestResourceName = resourceNames.FirstOrDefault(n => n.EndsWith(resourceSuffix));
  561. if (manifestResourceName == null) {
  562. manifestResourceName = resourceNames.FirstOrDefault(n =>
  563. n == "System.Data.Entity.Core.Resources.DbProviderServices.ConceptualSchemaDefinition.csdl"
  564. );
  565. if (manifestResourceName == null) {
  566. throw new Exception("Unable to locate an embedded resource with the name " +
  567. "'System.Data.Entity.Core.Resources.DbProviderServices.ConceptualSchemaDefinition.csdl'" +
  568. " or a resource that ends with: " + resourceSuffix);
  569. }
  570. }
  571. XDocument xDoc;
  572. using (var mmxStream = ocAssembly.GetManifestResourceStream(manifestResourceName)) {
  573. xDoc = XDocument.Load(mmxStream);
  574. }
  575. return xDoc;
  576. }
  577. private static XDocument UpdateCSpaceOSpaceMapping(XDocument xDoc, ObjectContext oc) {
  578. var metadataWs = oc.MetadataWorkspace;
  579. // ForceOSpaceLoad
  580. var asm = oc.GetType().Assembly;
  581. metadataWs.LoadFromAssembly(asm);
  582. return UpdateCSpaceOSpaceMappingCore(xDoc, metadataWs);
  583. }
  584. private static XDocument UpdateCSpaceOSpaceMapping(XDocument xDoc, Assembly assembly, String resourcePrefix) {
  585. String[] res;
  586. if (resourcePrefix == "") {
  587. res = new string[] { "res://*/" };
  588. } else {
  589. var pre = "res://*/" + resourcePrefix;
  590. res = new String[] { pre + ".csdl", pre + ".msl", pre + ".ssdl" };
  591. }
  592. var metadataWs = new MetadataWorkspace(
  593. res,
  594. new Assembly[] { assembly });
  595. // force an OSpace load - UGH - this was hard to find.... need to create the object item collection before loading assembly
  596. metadataWs.RegisterItemCollection(new ObjectItemCollection());
  597. metadataWs.LoadFromAssembly(assembly);
  598. return UpdateCSpaceOSpaceMappingCore(xDoc, metadataWs);
  599. }
  600. private static XDocument UpdateCSpaceOSpaceMappingCore(XDocument xDoc, MetadataWorkspace metadataWs) {
  601. var cspaceTypes = metadataWs.GetItems<System.Data.Entity.Core.Metadata.Edm.StructuralType>(DataSpace.CSpace);
  602. var tpls = cspaceTypes
  603. .Where(st => !(st is AssociationType))
  604. .Select(st => {
  605. var ost = metadataWs.GetObjectSpaceType(st);
  606. return new[] { st.FullName, ost.FullName };
  607. })
  608. .ToList();
  609. var ocMapping = JsonConvert.SerializeObject(tpls);
  610. xDoc.Root.SetAttributeValue("CSpaceOSpaceMapping", ocMapping);
  611. return xDoc;
  612. }
  613. protected static String GetConnectionStringFromConfig(String connectionName) {
  614. var item = ConfigurationManager.ConnectionStrings[connectionName];
  615. return item.ConnectionString;
  616. }
  617. #endregion
  618. // TODO: may want to improve perf on this later ( cache the mappings maybe).
  619. public String GetEntitySetName(Type entityType) {
  620. var metaWs = ObjectContext.MetadataWorkspace;
  621. EntityType cspaceEntityType;
  622. var ospaceEntityTypes = metaWs.GetItems<EntityType>(DataSpace.OSpace);
  623. if (ospaceEntityTypes.Any()) {
  624. var ospaceEntityType = ospaceEntityTypes.First(oet => oet.FullName == entityType.FullName);
  625. cspaceEntityType = (EntityType)metaWs.GetEdmSpaceType(ospaceEntityType);
  626. } else {
  627. // Old EDMX ObjectContext has empty OSpace, so we get cspaceEntityType directly
  628. var cspaceEntityTypes = metaWs.GetItems<EntityType>(DataSpace.CSpace);
  629. cspaceEntityType = cspaceEntityTypes.First(et => et.FullName == entityType.FullName);
  630. }
  631. // note CSpace below - not OSpace - evidently the entityContainer is only in the CSpace.
  632. var entitySets = metaWs.GetItems<EntityContainer>(DataSpace.CSpace)
  633. .SelectMany(c => c.BaseEntitySets.Where(es => es.ElementType.BuiltInTypeKind == BuiltInTypeKind.EntityType)).ToList();
  634. return GetDefaultEntitySetName(cspaceEntityType, entitySets);
  635. }
  636. private static string GetDefaultEntitySetName(EntityType cspaceEntityType, IList<EntitySetBase> entitySets) {
  637. // 1st entity set with matching entity type, otherwise with matching assignable type.
  638. EdmType baseType = cspaceEntityType;
  639. EntitySetBase entitySet = null;
  640. while (baseType != null) {
  641. entitySet = entitySets.FirstOrDefault(es => es.ElementType == baseType);
  642. if (entitySet != null) return entitySet.Name;
  643. baseType = baseType.BaseType;
  644. }
  645. return string.Empty;
  646. }
  647. //var entityTypes = key.MetadataWorkspace.GetItems<EntityType>(DataSpace.OSpace);
  648. //// note CSpace below - not OSpace - evidently the entityContainer is only in the CSpace.
  649. //var entitySets = key.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.CSpace)
  650. // .SelectMany(c => c.BaseEntitySets.Where(es => es.ElementType.BuiltInTypeKind == BuiltInTypeKind.EntityType)).ToList();
  651. //private EntitySet GetDefaultEntitySet(EntityType cspaceEntityType) {
  652. // var entitySet = _cspaceContainers.First().BaseEntitySets.OfType<EntitySet>().Where(es => es.ElementType == cspaceEntityType).FirstOrDefault();
  653. // if (entitySet == null) {
  654. // var baseEntityType = cspaceEntityType.BaseType as EntityType;
  655. // if (baseEntityType != null) {
  656. // return GetDefaultEntitySet(baseEntityType);
  657. // } else {
  658. // return null;
  659. // }
  660. // }
  661. // return entitySet;
  662. //}
  663. //// from DF
  664. private const string ResourcePrefix = @"res://";
  665. private T _context;
  666. }
  667. public class EFEntityInfo : EntityInfo {
  668. internal EFEntityInfo() {
  669. }
  670. internal String EntitySetName;
  671. internal ObjectStateEntry ObjectStateEntry;
  672. }
  673. public class EFEntityError : EntityError {
  674. public EFEntityError(EntityInfo entityInfo, String errorName, String errorMessage, String propertyName) {
  675. if (entityInfo != null) {
  676. this.EntityTypeName = entityInfo.Entity.GetType().FullName;
  677. this.KeyValues = GetKeyValues(entityInfo);
  678. }
  679. ErrorName = ErrorName;
  680. ErrorMessage = errorMessage;
  681. PropertyName = propertyName;
  682. }
  683. private Object[] GetKeyValues(EntityInfo entityInfo) {
  684. return entityInfo.ContextProvider.GetKeyValues(entityInfo);
  685. }
  686. }
  687. }