PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Breeze.WebApi.EF/EFContextProvider.cs

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