From bd31d7856f62f46339930c56efcb1eacfd6d5a3a Mon Sep 17 00:00:00 2001 From: GioviQ Date: Wed, 9 Sep 2020 10:03:48 +0200 Subject: [PATCH 01/17] Fixes and improvements tested on https://github.com/enkodellc/blazorboilerplate/tree/development --- Breeze.Sharp/Breeze.Sharp.csproj | 3 + Breeze.Sharp/DataService.cs | 20 +-- Breeze.Sharp/EntityAspect.cs | 7 +- Breeze.Sharp/EntityManager.cs | 144 +++++++++--------- Breeze.Sharp/EntityQuery.cs | 1 + .../Json/JsonQueryExpressionVisitor.cs | 20 +-- Breeze.Sharp/MetadataStore.cs | 130 ++++++++-------- Breeze.Sharp/NamingConvention.cs | 9 +- Breeze.Sharp/StructuralType.cs | 3 +- Breeze.Sharp/Validators.cs | 2 +- Breeze.Sharp/app.config | 2 +- Tests/Internal/Tests/QueryConcurrencyTests.cs | 69 +++++++++ 12 files changed, 246 insertions(+), 164 deletions(-) create mode 100644 Tests/Internal/Tests/QueryConcurrencyTests.cs diff --git a/Breeze.Sharp/Breeze.Sharp.csproj b/Breeze.Sharp/Breeze.Sharp.csproj index a18ec53..bcc2adc 100644 --- a/Breeze.Sharp/Breeze.Sharp.csproj +++ b/Breeze.Sharp/Breeze.Sharp.csproj @@ -136,6 +136,9 @@ 5.8.4 + + 12.0.3 + diff --git a/Breeze.Sharp/DataService.cs b/Breeze.Sharp/DataService.cs index d1d0b34..96ef378 100644 --- a/Breeze.Sharp/DataService.cs +++ b/Breeze.Sharp/DataService.cs @@ -24,7 +24,7 @@ namespace Breeze.Sharp { /// public class DataService : IJsonSerializable { - + /// /// Constructs a new DataService with the option to use an already configured HttpClient. If one is not provided /// then the DataService will create one internally. In either case it will be available via the HttpClient property. @@ -94,12 +94,10 @@ private void InitializeHttpClient(HttpClient httpClient) { httpClient = DefaultHttpMessageHandler == null ? new HttpClient() : new HttpClient(DefaultHttpMessageHandler); } _httpClient = httpClient; - _httpClient.BaseAddress = new Uri(ServiceName); - + // Add an Accept header for JSON format. _httpClient.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); - } private IDataServiceAdapter GetAdapter(string adapterName) { @@ -134,7 +132,7 @@ public async Task GetAsync(String resourcePath) { public async Task GetAsync(String resourcePath, CancellationToken cancellationToken) { try { - var response = await _httpClient.GetAsync(resourcePath, cancellationToken); + var response = await _httpClient.GetAsync($"{ServiceName}{resourcePath}", cancellationToken); cancellationToken.ThrowIfCancellationRequested(); @@ -158,10 +156,9 @@ public async Task PostAsync(String resourcePath, String json) { // new KeyValuePair("", "login") // }); - var response = await _httpClient.PostAsync(resourcePath, content); + var response = await _httpClient.PostAsync($"{ServiceName}{resourcePath}", content); return await ReadResult(response); - } - catch (Exception e) { + } catch (Exception e) { Debug.WriteLine(e); throw; } @@ -171,6 +168,11 @@ private static async Task ReadResult(HttpResponseMessage response) { var result = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { + try { + var json = JNode.DeserializeFrom(result); + response.ReasonPhrase = json.Get("Message"); + } catch (Exception) { } + throw new DataServiceRequestException(response, result); } return result; @@ -210,7 +212,7 @@ public DataServiceRequestException(HttpResponseMessage httpResponse, String resp public String ResponseContent { get; private set; } public HttpResponseMessage HttpResponse { get; private set; } - + } } diff --git a/Breeze.Sharp/EntityAspect.cs b/Breeze.Sharp/EntityAspect.cs index 0417914..4b2839c 100644 --- a/Breeze.Sharp/EntityAspect.cs +++ b/Breeze.Sharp/EntityAspect.cs @@ -1,4 +1,4 @@ -using Breeze.Sharp.Core; +using Breeze.Sharp.Core; using System; using System.Collections; using System.Collections.Generic; @@ -931,13 +931,14 @@ private bool FixupFksOnUnattached(NavigationProperty np) { var npEntity = GetValue(np); // property is already linked up if (npEntity != null) { - if (npEntity.EntityAspect.IsDetached) { + if (npEntity.EntityAspect.IsDetached && np.ForeignKeyProperties.Any()) { // need to insure that fk props match var fkProps = np.ForeignKeyProperties; npEntity.EntityAspect.EntityType = np.EntityType; // Set this Entity's fk to match np EntityKey // Order.CustomerID = aCustomer.CustomerID - npEntity.EntityAspect.EntityKey.Values.ForEach((v, i) => SetDpValue(fkProps[i], v)); + //commented because set keys to default values + //npEntity.EntityAspect.EntityKey.Values.ForEach((v, i) => SetDpValue(fkProps[i], v)); } return false; } diff --git a/Breeze.Sharp/EntityManager.cs b/Breeze.Sharp/EntityManager.cs index bcc28a2..b4ceffc 100644 --- a/Breeze.Sharp/EntityManager.cs +++ b/Breeze.Sharp/EntityManager.cs @@ -18,7 +18,7 @@ namespace Breeze.Sharp { /// Instances of the EntityManager contain and manage collections of entities, /// either retrieved from a backend datastore or created on the client. /// - public class EntityManager { + public class EntityManager { #region Ctor @@ -30,9 +30,9 @@ public class EntityManager { /// // Example: /// var em = new EntityManager("http://localhost:7150/breeze/NorthwindIBModel/") /// - public EntityManager(String serviceName, MetadataStore metadataStore = null) + public EntityManager(String serviceName, MetadataStore metadataStore = null) : this(new DataService(serviceName), metadataStore) { - + } /// @@ -72,7 +72,7 @@ private void Initialize() { TempIds = new HashSet(); } - + #endregion #region Thread checking @@ -117,6 +117,7 @@ public void CheckAuthorizedThreadId() { private static ThreadLocal __threadLocalId; private static Int32 __nextThreadId = 34; private Int32 _authorizedThreadId; + private readonly object _reificationLock = new Object(); #endregion @@ -181,7 +182,7 @@ public bool ChangeNotificationEnabled { this.EntityGroups.ForEach(eg => eg.ChangeNotificationEnabled = value); } } - + #endregion #region async methods @@ -192,9 +193,8 @@ public bool ChangeNotificationEnabled { /// /// /// - public async Task FetchMetadata(DataService dataService = null) - { - return await FetchMetadata(CancellationToken.None, dataService); + public async Task FetchMetadata(DataService dataService = null) { + return await FetchMetadata(CancellationToken.None, dataService); } /// /// Fetches the metadata associated with the EntityManager's current 'serviceName'. @@ -217,10 +217,9 @@ public async Task FetchMetadata(CancellationToken cancellationToken /// /// /// - public async Task> ExecuteQuery(EntityQuery query) - { - var result = await ExecuteQuery((EntityQuery)query, CancellationToken.None); - return (IEnumerable)result; + public async Task> ExecuteQuery(EntityQuery query) { + var result = await ExecuteQuery((EntityQuery)query, CancellationToken.None); + return (IEnumerable)result; } /// @@ -231,7 +230,7 @@ public async Task> ExecuteQuery(EntityQuery query) /// /// public async Task> ExecuteQuery(EntityQuery query, CancellationToken cancellationToken) { - var result = await ExecuteQuery((EntityQuery) query, cancellationToken); + var result = await ExecuteQuery((EntityQuery)query, cancellationToken); return (IEnumerable)result; } @@ -240,9 +239,8 @@ public async Task> ExecuteQuery(EntityQuery query, Cancella /// /// /// - public async Task ExecuteQuery(EntityQuery query) - { - return await ExecuteQuery(query, CancellationToken.None); + public async Task ExecuteQuery(EntityQuery query) { + return await ExecuteQuery(query, CancellationToken.None); } /// @@ -251,8 +249,7 @@ public async Task ExecuteQuery(EntityQuery query) /// /// /// - public async Task ExecuteQuery(EntityQuery query, CancellationToken cancellationToken) - { + public async Task ExecuteQuery(EntityQuery query, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (query.ElementType == null) { @@ -273,12 +270,13 @@ public async Task ExecuteQuery(EntityQuery query, CancellationToken var resourcePath = query.GetResourcePath(this.MetadataStore); // HACK resourcePath = resourcePath.Replace("/*", ""); - + var result = await dataService.GetAsync(resourcePath, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); - + CheckAuthorizedThreadId(); + var mergeStrategy = query.QueryOptions.MergeStrategy ?? this.DefaultQueryOptions.MergeStrategy ?? QueryOptions.Default.MergeStrategy; var mappingContext = new MappingContext() { @@ -294,27 +292,28 @@ public async Task ExecuteQuery(EntityQuery query, CancellationToken // serializer.Converters.Add(new StringEnumConverter()); Type rType; - using (NewIsLoadingBlock()) { - var jt = JToken.Parse(result); - jt = mappingContext.JsonResultsAdapter.ExtractResults(jt); - if (result.IndexOf("\"InlineCount\":", StringComparison.OrdinalIgnoreCase) > 0) { - rType = typeof (QueryResult<>).MakeGenericType(query.ElementType); - return (IEnumerable)serializer.Deserialize(new JTokenReader(jt), rType); - } else if (jt is JArray) { - rType = typeof(IEnumerable<>).MakeGenericType(query.ElementType); - return (IEnumerable)serializer.Deserialize(new JTokenReader(jt), rType); - } else { - rType = query.ElementType; - var list= (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(query.ElementType)); - var item = serializer.Deserialize(new JTokenReader(jt), rType); - list.Add(item); - return list; + lock (_reificationLock) { + using (NewIsLoadingBlock()) { + var jt = JToken.Parse(result); + jt = mappingContext.JsonResultsAdapter.ExtractResults(jt); + if (result.IndexOf("\"Results\":", StringComparison.OrdinalIgnoreCase) > 0) { + rType = typeof(QueryResult<>).MakeGenericType(query.ElementType); + return (IEnumerable)serializer.Deserialize(new JTokenReader(jt), rType); + } else if (jt is JArray) { + rType = typeof(IEnumerable<>).MakeGenericType(query.ElementType); + return (IEnumerable)serializer.Deserialize(new JTokenReader(jt), rType); + } else { + rType = query.ElementType; + var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(query.ElementType)); + var item = serializer.Deserialize(new JTokenReader(jt), rType); + list.Add(item); + return list; + } } - } } - + /// /// Performs an asynchronous saves of all pending changes within this EntityManager. @@ -359,9 +358,9 @@ public async Task SaveChanges(IEnumerable entities = null, return errors; } }); - if (errs.Any()) { - throw new SaveException(errs); - }; + if (errs.Any()) { + throw new SaveException(errs); + }; } saveOptions = new SaveOptions(saveOptions ?? this.DefaultSaveOptions ?? SaveOptions.Default); if (saveOptions.ResourceName == null) saveOptions.ResourceName = "SaveChanges"; @@ -382,9 +381,8 @@ public async Task SaveChanges(IEnumerable entities = null, /// /// /// - public async Task FetchEntityByKey(EntityKey entityKey, bool checkLocalCacheFirst = false) - { - return await FetchEntityByKey(entityKey, CancellationToken.None, checkLocalCacheFirst); + public async Task FetchEntityByKey(EntityKey entityKey, bool checkLocalCacheFirst = false) { + return await FetchEntityByKey(entityKey, CancellationToken.None, checkLocalCacheFirst); } /// @@ -396,7 +394,7 @@ public async Task FetchEntityByKey(EntityKey entityKey, bo /// public async Task FetchEntityByKey(EntityKey entityKey, CancellationToken cancellationToken, bool checkLocalCacheFirst = false) { IEntity entity; - + cancellationToken.ThrowIfCancellationRequested(); if (checkLocalCacheFirst) { @@ -476,7 +474,7 @@ internal void OnHasChangesChanged() { internal void FireQueuedEvents() { // IsLoadingEntity will still be true when this occurs. - if (! _queuedEvents.Any()) return; + if (!_queuedEvents.Any()) return; var events = _queuedEvents.ToList(); _queuedEvents.Clear(); events.ForEach(a => a()); @@ -555,10 +553,10 @@ private ImportResult ImportEntities(JNode jn, ImportOptions importOptions) { var entityGroupNodesMap = jn.GetJNodeArrayMap("entityGroupMap"); // tempKeyMap will have a new values where collisions will occur var tempKeyMap = jn.GetJNodeArray("tempKeys").Select(jnEk => new EntityKey(jnEk, this.MetadataStore)).ToDictionary( - ek => ek, - ek => this.GetEntityByKey(ek) == null ? ek : EntityKey.Create(ek.EntityType, KeyGenerator.GetNextTempId(ek.EntityType.KeyProperties.First())) + ek => ek, + ek => this.GetEntityByKey(ek) == null ? ek : EntityKey.Create(ek.EntityType, KeyGenerator.GetNextTempId(ek.EntityType.KeyProperties.First())) ); - + var mergeStrategy = (importOptions.MergeStrategy ?? this.DefaultQueryOptions.MergeStrategy ?? QueryOptions.Default.MergeStrategy).Value; var importedEntities = new List(); using (NewIsLoadingBlock()) { @@ -609,7 +607,7 @@ private List ImportEntityGroup(IEnumerable entityNodes, EntityTy } UpdateTempFks(targetEntity, entityAspectNode, tempKeyMap); AttachImportedEntity(targetEntity, entityType, entityState); - } + } importedEntities.Add(targetEntity); }; @@ -906,7 +904,7 @@ public IEnumerable GetEntities(IEnumerable types, EntityState ent /// public IEnumerable GetEntities(Type type, EntityState entityState) { if (type.GetTypeInfo().IsAbstract) { - var groups = type == typeof(IEntity) + var groups = type == typeof(IEntity) ? this.EntityGroups : this.EntityGroups.Where(eg => type.IsAssignableFrom(eg.ClrType)); return groups.SelectMany(f => f.LocalEntityAspects) @@ -926,7 +924,7 @@ public IEnumerable GetEntities(Type type, EntityState entityState) { /// /// public IEnumerable GetChanges(params Type[] entityTypes) { - return GetChanges((IEnumerable) entityTypes); + return GetChanges((IEnumerable)entityTypes); } /// @@ -982,7 +980,7 @@ public IEntity GetEntityByKey(EntityKey entityKey) { return (eg == null) ? null : eg.FindEntityAspect(entityKey, true); }).FirstOrDefault(a => a != null); } - + return ea == null ? null : ea.Entity; } @@ -1010,7 +1008,7 @@ public T CreateEntity(EntityState entityState) { /// /// Default is 'Added' /// - public T CreateEntity(Object initialValues=null, EntityState entityState = EntityState.Added) { + public T CreateEntity(Object initialValues = null, EntityState entityState = EntityState.Added) { return (T)CreateEntity(typeof(T), initialValues, entityState); } @@ -1022,7 +1020,7 @@ public T CreateEntity(Object initialValues=null, EntityState entityState = En /// /// /// - public IEntity CreateEntity(EntityType entityType, Object initialValues=null, EntityState entityState = EntityState.Added) { + public IEntity CreateEntity(EntityType entityType, Object initialValues = null, EntityState entityState = EntityState.Added) { return CreateEntity(entityType.ClrType, initialValues, entityState); } @@ -1077,11 +1075,11 @@ private static void InitializeEntity(IEntity entity, object initialValues, Entit if (prop.IsScalar) { entity.EntityAspect.SetValue(prop, val); } else { - var navSet = (INavigationSet) entity.EntityAspect.GetValue(prop); + var navSet = (INavigationSet)entity.EntityAspect.GetValue(prop); (val as IEnumerable).Cast().ForEach(e => navSet.Add(e)); } } - } catch(Exception e) { + } catch (Exception e) { var msg = "Unable to create entity of type '{0}'. Initialization failed on property: '{1}'"; throw new Exception(String.Format(msg, et.Name, pi.Name), e); } @@ -1115,9 +1113,9 @@ public IEntity AttachEntity(IEntity entity, EntityState entityState = EntityStat } // TODO: handle mergeStrategy here - + AttachEntityAspect(aspect, entityState); - + aspect.EntityType.NavigationProperties.ForEach(np => { aspect.ProcessNpValue(np, e => AttachEntity(e, entityState, mergeStrategy)); }); @@ -1149,7 +1147,7 @@ public bool DetachEntity(IEntity entity) { internal EntityAspect AttachQueriedEntity(IEntity entity, EntityType entityType) { var aspect = entity.EntityAspect; aspect.EntityType = entityType; - AttachEntityAspect(aspect, EntityState.Unchanged); + AttachEntityAspect(aspect, EntityState.Unchanged); if ((this.ValidationOptions.ValidationApplicability & ValidationApplicability.OnQuery) > 0) { aspect.ValidateInternal(); @@ -1163,7 +1161,7 @@ internal EntityAspect AttachImportedEntity(IEntity entity, EntityType entityType var aspect = entity.EntityAspect; aspect.EntityType = entityType; AttachEntityAspect(aspect, entityState); - + aspect.OnEntityChanged(EntityAction.AttachOnImport); return aspect; } @@ -1205,7 +1203,7 @@ private void InitializeEntityKey(EntityAspect aspect) { // return properties that are = to defaultValues var keyProps = aspect.EntityType.KeyProperties; var keyPropsWithDefaultValues = keyProps - .Zip(ek.Values, (kp, kv) => Object.Equals(kp.DefaultValue,kv) ? kp : null) + .Zip(ek.Values, (kp, kv) => Object.Equals(kp.DefaultValue, kv) ? kp : null) .Where(kp => kp != null); if (keyPropsWithDefaultValues.Any()) { @@ -1237,7 +1235,7 @@ internal EntityGroup GetEntityGroup(Type clrEntityType) { if (eg != null) { return eg; } - + lock (this.EntityGroups) { // check again just in case another thread got in. eg = this.EntityGroups[clrEntityType]; @@ -1302,7 +1300,7 @@ public UniqueId GenerateId(IEntity entity, DataProperty entityProperty) { if (aspect.EntityGroup == null) { aspect.EntityGroup = GetEntityGroup(entityType); } - + if (KeyGenerator == null) { throw new Exception("Unable to locate a KeyGenerator"); } @@ -1333,12 +1331,12 @@ public UniqueId GenerateId(IEntity entity, DataProperty entityProperty) { internal void UpdatePkIfNeeded(EntityAspect aspect) { if (KeyGenerator == null) return; var keyProperties = aspect.EntityType.KeyProperties; - + foreach (var aProperty in keyProperties) { var val = aspect.GetValue(aProperty.Name); var aUniqueId = new UniqueId(aProperty, val); - + // determine if a temp pk is needed. if (aProperty.IsAutoIncrementing) { if (!KeyGenerator.IsTempId(aUniqueId)) { @@ -1418,8 +1416,8 @@ public bool HasChanges(IEnumerable entityTypes = null) { // backdoor the "really" check for changes. private bool HasChangesCore(IEnumerable entityTypes) { - var entityGroups = (entityTypes == null) - ? this.EntityGroups + var entityGroups = (entityTypes == null) + ? this.EntityGroups : entityTypes.Select(et => GetEntityGroup(et)); return entityGroups.Any(eg => eg != null && eg.HasChanges()); } @@ -1472,17 +1470,17 @@ internal void SetHasChanges(bool? value) { internal void UpdateFkVal(DataProperty fkProp, Object oldValue, Object newValue) { var eg = this.EntityGroups[fkProp.ParentType.ClrType]; if (eg == null) return; - + eg.UpdateFkVal(fkProp, oldValue, newValue); } internal static void CheckEntityType(Type clrEntityType) { var etInfo = clrEntityType.GetTypeInfo(); - if ( typeof(IEntity).GetTypeInfo().IsAssignableFrom(etInfo) && !etInfo.IsAbstract) return; + if (typeof(IEntity).GetTypeInfo().IsAssignableFrom(etInfo) && !etInfo.IsAbstract) return; throw new ArgumentException("This operation requires a nonabstract type that implements the IEntity interface"); } - internal LoadingBlock NewIsLoadingBlock(bool allowHasChangesAction=true) { + internal LoadingBlock NewIsLoadingBlock(bool allowHasChangesAction = true) { return new LoadingBlock(this, allowHasChangesAction); } @@ -1512,8 +1510,8 @@ public void Dispose() { private bool _wasLoadingEntity; } - internal bool IsLoadingEntity { get; set; } - internal bool IsRejectingChanges { get; set; } + internal bool IsLoadingEntity { get; set; } + internal bool IsRejectingChanges { get; set; } internal UnattachedChildrenMap UnattachedChildrenMap { get; private set; } #endregion @@ -1540,7 +1538,7 @@ private T InsureNotNull(T val, String argumentName) { #endregion } - + } diff --git a/Breeze.Sharp/EntityQuery.cs b/Breeze.Sharp/EntityQuery.cs index af4b39d..2380d68 100644 --- a/Breeze.Sharp/EntityQuery.cs +++ b/Breeze.Sharp/EntityQuery.cs @@ -216,6 +216,7 @@ private string GetJsonResourcePath(string resourceName) { var json = JsonQueryExpressionVisitor.Translate(this.Expression); if (json.Length > 2) { // TODO may be able to get away with not escaping the URI + System.Diagnostics.Debug.WriteLine($"json query: {json}"); var uri = Uri.EscapeUriString(json); return resourceName + '?' + uri; } else { diff --git a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs index 8f58ac3..1f21a81 100644 --- a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs +++ b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs @@ -14,7 +14,7 @@ public class JsonQueryExpressionVisitor : ExpressionVisitor { public int? Skip { get; private set; } = null; public int? Take { get; private set; } = null; public bool? InlineCount { get; private set; } = null; - public string OrderBy { get; private set; } = null; + public List OrderBy { get; private set; } = null; [JsonConverter(typeof(PlainJsonStringConverter))] public string Where { get; private set; } = null; public List Select { get; private set; } = null; @@ -43,7 +43,7 @@ public static string Translate(Expression expression) { return json; } - private JsonQueryExpressionVisitor() {} + private JsonQueryExpressionVisitor() { } /// Populate this visitor's properties from the expression protected void VisitRoot(Expression expression) { @@ -120,7 +120,7 @@ protected override Expression VisitMethodCall(MethodCallExpression m) { return this.Visit(m.Arguments[0]); } } else if (methodName == "OrderByDescending") { - if (this.ParseOrderByExpression(m, "DESC")) { + if (this.ParseOrderByExpression(m, "desc")) { return this.Visit(m.Arguments[0]); } } @@ -274,15 +274,15 @@ private bool ParseOrderByExpression(MethodCallExpression expression, string orde order = " " + order.Trim(); } - //lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression); + MemberExpression body = lambdaExpression.Body is MemberExpression ? + (MemberExpression)lambdaExpression.Body : ((UnaryExpression)lambdaExpression.Body).Operand as MemberExpression; - MemberExpression body = lambdaExpression.Body as MemberExpression; if (body != null) { - if (string.IsNullOrEmpty(OrderBy)) { - OrderBy = string.Format("{0}{1}", body.Member.Name, order); - } else { - OrderBy = string.Format("{0}, {1}{2}", OrderBy, body.Member.Name, order); - } + if (OrderBy == null) + OrderBy = new List(); + + OrderBy.Add(string.Format("{0}{1}", body.Member.Name, order)); + return true; } diff --git a/Breeze.Sharp/MetadataStore.cs b/Breeze.Sharp/MetadataStore.cs index 059b0f7..494be95 100644 --- a/Breeze.Sharp/MetadataStore.cs +++ b/Breeze.Sharp/MetadataStore.cs @@ -1,4 +1,4 @@ -using Breeze.Sharp.Core; +using Breeze.Sharp.Core; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -21,14 +21,14 @@ namespace Breeze.Sharp { public class MetadataStore : IJsonSerializable { - // Explicit static constructor to tell C# compiler + // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static MetadataStore() { - + } public MetadataStore() { - + lock (__lock) { StoreID = __nextStoreID++; } @@ -40,6 +40,8 @@ public MetadataStore() { public static String MetadataVersion = "1.0.3"; + private Regex regExGeneric = new Regex(@"`\d+"); + public static MetadataStore Detached { get { return __detached; } set { __detached = value ?? new MetadataStore(); } @@ -72,7 +74,7 @@ public ICollection ComplexTypes { /// public NamingConvention NamingConvention { get { return _namingConvention; } - set { _namingConvention = value; } + set { _namingConvention = value; } } /// @@ -101,8 +103,7 @@ internal Message OnMetadataMismatch(String entityTypeName, String propertyName, try { handler(this, args); allow = args.Allow; - } - catch { + } catch { // Eat any handler exceptions but throw later. allow = false; } @@ -113,14 +114,14 @@ internal Message OnMetadataMismatch(String entityTypeName, String propertyName, } internal Message AddMessage(String message, MessageType messageType, bool throwOnError = false) { - + var msg = new Message() { Text = message, MessageType = messageType }; _messages.Add(msg); if (messageType == MessageType.Error && throwOnError) { throw new Exception(message); } return msg; - + } public IEnumerable GetMessages(MessageType messageType = MessageType.All) { @@ -131,16 +132,15 @@ public IEnumerable GetMessages(MessageType messageType = MessageType.All #region Public methods - + /// /// Fetches the metadata for a specified 'service'. This method is automatically called /// internally by an EntityManager before its first query against a new service. /// /// /// - public async Task FetchMetadata(DataService dataService) - { - return await FetchMetadata(dataService, CancellationToken.None); + public async Task FetchMetadata(DataService dataService) { + return await FetchMetadata(dataService, CancellationToken.None); } /// @@ -169,41 +169,41 @@ public async Task FetchMetadata(DataService dataService, Cancellati metadata = await dataService.GetAsync("Metadata", cancellationToken); cancellationToken.ThrowIfCancellationRequested(); - } - catch (Exception e) - { - if (!(e is TaskCanceledException)) - throw new Exception("Unable to locate metadata resource for: " + dataService.ServiceName, e); - throw; - } finally { - _asyncSemaphore.Release(); - } - metadata = metadata.Trim(); - // this section is needed if metadata is returned as an escaped string - ( not common but does happen ... ). - if (metadata.Substring(0, 1) == "\"" && metadata.Substring(metadata.Length - 1, 1) == "\"") { - metadata = Regex.Unescape(metadata.Substring(1, metadata.Length - 2)); - } - var json = (JObject)JsonConvert.DeserializeObject(metadata); - var schema = json["schema"]; - if (schema != null) { - // metadata returned in CSDL format - var metadataProcessor = new CsdlMetadataProcessor(); - metadataProcessor.ProcessMetadata(this, json); - } else { - // metadata returned in breeze native format - this.ImportMetadata(metadata, true); - - } - var errorMessages = GetMessages(MessageType.Error).ToList(); - if (errorMessages.Any()) { - throw new Exception("Metadata errors encountered: \n" + errorMessages.ToAggregateString("\n")); - } + metadata = metadata.Trim(); + // this section is needed if metadata is returned as an escaped string - ( not common but does happen ... ). + if (metadata.Substring(0, 1) == "\"" && metadata.Substring(metadata.Length - 1, 1) == "\"") { + metadata = Regex.Unescape(metadata.Substring(1, metadata.Length - 2)); + } + + metadata = regExGeneric.Replace(metadata, string.Empty); - dataService.ServerMetadata = metadata; - AddDataService(dataService); - return dataService; + var json = (JObject)JsonConvert.DeserializeObject(metadata); + var schema = json["schema"]; + if (schema != null) { + // metadata returned in CSDL format + var metadataProcessor = new CsdlMetadataProcessor(); + metadataProcessor.ProcessMetadata(this, json); + } else { + // metadata returned in breeze native format + this.ImportMetadata(metadata, true); + } + var errorMessages = GetMessages(MessageType.Error).ToList(); + if (errorMessages.Any()) { + throw new Exception("Metadata errors encountered: \n" + errorMessages.ToAggregateString("\n")); + } + + dataService.ServerMetadata = metadata; + AddDataService(dataService); + return dataService; + } catch (Exception e) { + if (!(e is TaskCanceledException)) + throw new Exception("Unable to locate metadata resource for: " + dataService.ServiceName, e); + throw; + } finally { + _asyncSemaphore.Release(); + } } /// @@ -261,7 +261,7 @@ public EntityType GetEntityType(String etName, bool okIfNotFound = false) { /// /// /// - public ComplexType GetComplexType(Type clrComplexType ) { + public ComplexType GetComplexType(Type clrComplexType) { return GetStructuralType(clrComplexType); } @@ -281,8 +281,8 @@ internal T GetStructuralType(Type clrType) where T : class { var result = st as T; if (result == null) { throw new Exception("A type by this name exists but is not a " + typeof(T).FullName); - } - return result; + } + return result; } // T is either or @@ -348,7 +348,7 @@ private StructuralType CreateStructuralType(Type clrType) { var st = stb.CreateStructuralType(clrType); return st; } - + /// /// Sets a resourceName for a specified clrType. /// @@ -389,7 +389,7 @@ public EntityType GetEntityTypeForResourceName(String resourceName, bool okIfNot return null; } else { throw new Exception("Unable to locate a resource named: " + resourceName); - } + } } } @@ -408,7 +408,7 @@ public String GetDefaultResourceName(Type clrType) { /// /// /// - public string GetDefaultResourceName(EntityType entityType) { + public string GetDefaultResourceName(EntityType entityType) { lock (_defaultResourceNameMap) { String resourceName = null; // give the type it's base's resource name if it doesn't have its own. @@ -472,7 +472,7 @@ public void ImportMetadata(TextReader textReader, bool isFromServer = false) { ImportMetadata(jNode, isFromServer); } - internal void ImportMetadata(JNode jNode, bool isFromServer ) { + internal void ImportMetadata(JNode jNode, bool isFromServer) { DeserializeFrom(jNode, isFromServer); EntityTypes.ForEach(et => { // cross entity/complex type fixup. @@ -481,7 +481,7 @@ internal void ImportMetadata(JNode jNode, bool isFromServer ) { .Where(cp => cp.ComplexType == null) .ForEach(cp => cp.ComplexType = GetComplexType(cp.ComplexType.Name)); }); - + } @@ -503,7 +503,7 @@ JNode IJsonSerializable.ToJNode(Object config) { private void DeserializeFrom(JNode jNode, bool isFromServer) { MetadataVersion = jNode.Get("metadataVersion"); // may be more than just a name - + var ncNode = jNode.GetJNode("namingConvention"); if (ncNode != null) { var nc = Configuration.Instance.FindOrCreateNamingConvention(ncNode); @@ -527,7 +527,7 @@ private void DeserializeFrom(JNode jNode, bool isFromServer) { jNode.GetMap("resourceEntityTypeMap").ForEach(kvp => { var stName = kvp.Value; if (isFromServer) { - stName = TypeNameInfo.FromStructuralTypeName(stName).ToClient(this).StructuralTypeName; + stName = TypeNameInfo.FromStructuralTypeName(stName).ToClient(this).StructuralTypeName; } // okIfNotFound because metadata may contain refs to types that were already excluded earlier in // UpdateStructuralTypeFromJNode @@ -541,13 +541,15 @@ private void DeserializeFrom(JNode jNode, bool isFromServer) { private void UpdateStructuralTypeFromJNode(JNode jNode, bool isFromServer) { var name = GetStructuralTypeNameFromJNode(jNode, isFromServer); var stype = GetStructuralTypeCore(name); + if (isFromServer) + stype.NameOnServer = $"{jNode.Get("shortName")}:#{jNode.Get("namespace")}"; if (stype == null) { var isComplexType = jNode.Get("isComplexType", false); - OnMetadataMismatch(name, null, isComplexType ? + OnMetadataMismatch(name, null, isComplexType ? MetadataMismatchTypes.MissingCLRComplexType : MetadataMismatchTypes.MissingCLREntityType); return; } - + stype.UpdateFromJNode(jNode, isFromServer); } @@ -609,13 +611,13 @@ private void AddStructuralType(StructuralType stType, bool allowMerge = true) { } } - + #endregion #region Inner classes // inner class - + private class InternCache where T : Internable { public readonly Dictionary TypeMap = new Dictionary(); @@ -698,7 +700,7 @@ private T InternableFromJNode(JNode jNode) { private static int __nextStoreID = 1; // Note: the two lines above need to appear before this next one private static MetadataStore __detached = new MetadataStore(); - + private NamingConvention _namingConvention = new NamingConvention(); @@ -707,14 +709,14 @@ private T InternableFromJNode(JNode jNode) { // lock using _dataServiceMap private readonly Dictionary _dataServiceMap = new Dictionary(); - - + + private readonly HashSet _probedAssemblies = new HashSet(); private readonly List, Func>> _typeDiscoveryActions = new List, Func>>(); private readonly StructuralTypeCollection _structuralTypes = new StructuralTypeCollection(); private readonly Dictionary _shortNameMap = new Dictionary(); - + // locked using _resourceNameEntityTypeMap private readonly Dictionary _defaultResourceNameMap = new Dictionary(); @@ -723,7 +725,7 @@ private T InternableFromJNode(JNode jNode) { private InternCache _validatorCache = new InternCache(); private InternCache _namingConventionCache = new InternCache(); - + private readonly Dictionary _namingConventionMap = new Dictionary(); private readonly Dictionary _namingConventionJNodeCache = new Dictionary(); diff --git a/Breeze.Sharp/NamingConvention.cs b/Breeze.Sharp/NamingConvention.cs index e8a97cc..a7f54f7 100644 --- a/Breeze.Sharp/NamingConvention.cs +++ b/Breeze.Sharp/NamingConvention.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using Breeze.Sharp.Core; using System; @@ -85,6 +85,13 @@ public NamingConvention WithClientServerNamespaceMapping(IDictionary serverClientNamespaceMap) { + var clone = Clone(); + clone._serverClientNamespaceMap = new Dictionary(serverClientNamespaceMap); + _clientServerNamespaceMap = null; + return clone; + } + public NamingConvention WithClientServerNamespaceMapping(String clientNamespace, String serverNamespace) { var clone = Clone(); clone.AddClientServerNamespaceMapping(clientNamespace, serverNamespace); diff --git a/Breeze.Sharp/StructuralType.cs b/Breeze.Sharp/StructuralType.cs index 021937d..8a46df0 100644 --- a/Breeze.Sharp/StructuralType.cs +++ b/Breeze.Sharp/StructuralType.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -49,7 +49,6 @@ public String NameOnServer { } internal set { _nameOnServer = value; - Name = TypeNameInfo.FromStructuralTypeName(value).ToClient(MetadataStore).StructuralTypeName; } } diff --git a/Breeze.Sharp/Validators.cs b/Breeze.Sharp/Validators.cs index b9a9075..c53cb1b 100644 --- a/Breeze.Sharp/Validators.cs +++ b/Breeze.Sharp/Validators.cs @@ -47,7 +47,7 @@ public override String GetErrorMessage(ValidationContext context) { public bool TreatEmptyStringAsNull { get; private set; } - private static bool __defaultTreatEmptyStringAsNull = true; + private static bool __defaultTreatEmptyStringAsNull = false; } /// diff --git a/Breeze.Sharp/app.config b/Breeze.Sharp/app.config index 4429813..e936cc1 100644 --- a/Breeze.Sharp/app.config +++ b/Breeze.Sharp/app.config @@ -4,7 +4,7 @@ - + diff --git a/Tests/Internal/Tests/QueryConcurrencyTests.cs b/Tests/Internal/Tests/QueryConcurrencyTests.cs new file mode 100644 index 0000000..a37c628 --- /dev/null +++ b/Tests/Internal/Tests/QueryConcurrencyTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Foo; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Breeze.Sharp.Tests +{ + [TestClass] + public class QueryConcurrencyTests + { + private String _serviceName; + + [TestInitialize] + public void TestInitializeMethod() + { + Configuration.Instance.ProbeAssemblies(typeof(Customer).Assembly); + _serviceName = "http://localhost:7150/breeze/NorthwindIBModel/"; + } + + + + [TestCleanup] + public void TearDown() + { + + } + + [TestMethod] + public async Task ConcurrentQueryResultsAreAddedToTheEntityManagerCache() + { + var em = await TestFns.NewEm(_serviceName); + + var q = EntityQuery.From().Select(x => new { x.CustomerID}); + + var ids = (await q.Execute(em)).Select(x => x.CustomerID).ToList(); + + Assert.IsNotNull(ids); + Assert.IsTrue(ids.Count > 0); + + em.Clear(); + + var customerTasks = + ids.Select(x => + { + var customerQuery = EntityQuery.From().Where(c => c.CustomerID == x); + + return em.ExecuteQuery(customerQuery); + + }).ToArray(); + + Task.WaitAll(customerTasks); + + var customers = + customerTasks.Select(x => x.Status == TaskStatus.RanToCompletion + ? x.Result.FirstOrDefault() + : null) + .ToList(); + + CollectionAssert.AllItemsAreNotNull(customers); + + var localCustomersQuery = EntityQuery.From(); + + var localCustomers = em.ExecuteQueryLocally(localCustomersQuery).ToDictionary(x => x.CustomerID); + + Assert.IsTrue(customers.All(x => localCustomers.ContainsKey(x.CustomerID))); + } + } +} From e28110f5a6f329bb23348f0ae021c17e525981ae Mon Sep 17 00:00:00 2001 From: GioviQ Date: Mon, 14 Sep 2020 08:27:57 +0200 Subject: [PATCH 02/17] add missing implemetation of Parameters (with hack) --- Breeze.Sharp/EntityQuery.cs | 69 ++++++++++--------- .../Json/JsonQueryExpressionVisitor.cs | 21 ++++++ 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/Breeze.Sharp/EntityQuery.cs b/Breeze.Sharp/EntityQuery.cs index 2380d68..2513240 100644 --- a/Breeze.Sharp/EntityQuery.cs +++ b/Breeze.Sharp/EntityQuery.cs @@ -16,7 +16,7 @@ namespace Breeze.Sharp { - + // TODO: EntityQuery is currently just additive - i.e. no way to remove clauses /// @@ -25,14 +25,14 @@ namespace Breeze.Sharp { /// Therefore EntityQueries can be 'modified' without affecting any current instances. /// /// - public class EntityQuery : EntityQuery, IQueryable, IOrderedQueryable, IQueryProvider { + public class EntityQuery : EntityQuery, IQueryable, IOrderedQueryable, IQueryProvider { /// /// Constructor /// - public EntityQuery( ) : base() { + public EntityQuery() : base() { var context = new DataServiceContext(new Uri(__placeholderServiceName), DataServiceProtocolVersion.V3); - DataServiceQuery = (DataServiceQuery) context.CreateQuery(__placeholderResourceName).Where(x => true); + DataServiceQuery = (DataServiceQuery)context.CreateQuery(__placeholderResourceName).Where(x => true); QueryableType = typeof(T); } @@ -59,7 +59,7 @@ protected EntityQuery(EntityQuery query) : base(query) { /// For internal use only. /// /// - public override object Clone() { + public override object Clone() { return new EntityQuery(this); } @@ -82,9 +82,8 @@ public EntityQuery From(String resourceName) { /// /// /// - public new async Task> Execute(EntityManager entityManager = null) - { - return await Execute(CancellationToken.None, entityManager); + public new async Task> Execute(EntityManager entityManager = null) { + return await Execute(CancellationToken.None, entityManager); } /// @@ -143,7 +142,7 @@ public EntityQuery Expand(Expression> navigationPro /// public EntityQuery Expand(String path) { var q = new EntityQuery(this); - q.DataServiceQuery = this.DataServiceQuery.Expand(path.Replace('.','/')); + q.DataServiceQuery = this.DataServiceQuery.Expand(path.Replace('.', '/')); return q; } @@ -159,8 +158,13 @@ protected internal override EntityQuery ExpandNonGeneric(String path) { /// /// public EntityQuery WithParameter(string name, Object value) { - var q = new EntityQuery(this); - q.DataServiceQuery = this.DataServiceQuery.AddQueryOption(name, value); + var q = this; + + if (value != null) { + q = new EntityQuery(this); + q.DataServiceQuery = this.DataServiceQuery.AddQueryOption(name, value); + } + return q; } @@ -172,11 +176,11 @@ public EntityQuery WithParameter(string name, Object value) { public EntityQuery WithParameters(IDictionary dictionary) { var q = new EntityQuery(this); var dsq = this.DataServiceQuery; - dictionary.ForEach(kvp => dsq = dsq.AddQueryOption(kvp.Key, kvp.Value)); + dictionary.Where(kvp => kvp.Value != null).ForEach(kvp => dsq = dsq.AddQueryOption(kvp.Key, kvp.Value)); q.DataServiceQuery = dsq; return q; } - + /// /// Returns a query with the 'inlineCount' capability either enabled or disabled. With /// 'InlineCount' enabled, an additional 'InlineCount' property will be returned with the @@ -257,7 +261,7 @@ private string RewriteEdmCastOperations(String resourcePath) { var pattern = @"(?.*)cast\((?.*),'Edm\..*'\)(?.*)"; var m = System.Text.RegularExpressions.Regex.Match(resourcePath, pattern); if (m.Success) { - + var result = m.Groups["prefix"].Value + m.Groups["propName"].Value + m.Groups["suffix"].Value; @@ -274,14 +278,14 @@ private string RewriteEnumFilters(string resourcePath) { var m = System.Text.RegularExpressions.Regex.Match(resourcePath, pattern); if (m.Success) { var enumName = m.Groups["enumName"].Value; - var enumValue = m.Groups["enumValue"].Value; + var enumValue = m.Groups["enumValue"].Value; var pinfo = this.QueryableType.GetTypeInfo().GetDeclaredProperty(enumName); if (pinfo == null) return resourcePath; var enumType = TypeFns.GetNonNullableType(pinfo.PropertyType); if (!enumType.GetTypeInfo().IsEnum) return resourcePath; var enumString = "'" + Enum.GetName(enumType, Int32.Parse(enumValue)) + "'"; - var result = m.Groups["prefix"].Value - + enumName + "%20eq%20" + enumString + var result = m.Groups["prefix"].Value + + enumName + "%20eq%20" + enumString + m.Groups["suffix"].Value; // in case there is another cast in the string. return RewriteResourcePath(result); @@ -318,7 +322,7 @@ public IQueryProvider Provider { public EntityQuery(Expression expression, IQueryable queryable) { var oldDataServiceQuery = ((IHasDataServiceQuery)queryable).DataServiceQuery; - DataServiceQuery = (DataServiceQuery) oldDataServiceQuery.Provider.CreateQuery(expression); + DataServiceQuery = (DataServiceQuery)oldDataServiceQuery.Provider.CreateQuery(expression); UpdateFrom((EntityQuery)queryable); } @@ -360,7 +364,7 @@ TResult IQueryProvider.Execute(Expression expression) { throw new Exception("EntityQueries can only be executed asynchronously"); } - + /// /// Internal use only - part of implementation. @@ -377,23 +381,23 @@ Object IQueryProvider.Execute(Expression expression) { /// The element type of the IEnumerable{T} returned by this query. /// public override Type ElementType { - get { return typeof(T);} + get { return typeof(T); } } /// /// For internal use. /// protected new DataServiceQuery DataServiceQuery { - get { return (DataServiceQuery) base.DataServiceQuery; } - set { base.DataServiceQuery = value; } + get { return (DataServiceQuery)base.DataServiceQuery; } + set { base.DataServiceQuery = value; } } - + private static String __placeholderServiceName = "http://localhost:7890/breeze/Undefined/"; private static String __placeholderResourceName = "__Undefined__"; } - + /// /// Base class for all EntityQueries. This class is untyped and may be used /// when you need to create entity queries dynamically. @@ -430,7 +434,7 @@ public static EntityQuery From() { /// /// public static EntityQuery From(string resourceName) { - return new EntityQuery(resourceName); + return new EntityQuery(resourceName); } /// @@ -459,9 +463,8 @@ protected EntityQuery(EntityQuery query) { /// /// /// - public async Task Execute(EntityManager entityManager = null) - { - return await Execute(CancellationToken.None, entityManager); + public async Task Execute(EntityManager entityManager = null) { + return await Execute(CancellationToken.None, entityManager); } /// @@ -565,11 +568,11 @@ DataServiceQuery IHasDataServiceQuery.DataServiceQuery { /// Interface for all Entity queries. /// public interface IEntityQuery { - DataService DataService { get; } - EntityManager EntityManager { get; } - QueryOptions QueryOptions { get; } - String ResourceName { get; } - IJsonResultsAdapter JsonResultsAdapter { get; } + DataService DataService { get; } + EntityManager EntityManager { get; } + QueryOptions QueryOptions { get; } + String ResourceName { get; } + IJsonResultsAdapter JsonResultsAdapter { get; } Object Clone(); } diff --git a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs index 1f21a81..d0b6531 100644 --- a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs +++ b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs @@ -40,6 +40,12 @@ public static string Translate(Expression expression) { }; var json = JsonConvert.SerializeObject(visitor, Formatting.None, jsonSettings); + + //without a server-side custom model binder for 'Customer?{"parameters":{"companyName":"C"}}' I cannot have parameters with right values, + //so I have to use this hack + if (visitor.Parameters?.Count > 0) + json = json + "&" + string.Join("&", visitor.Parameters.Select(kvp => string.Format("{0}={1}", kvp.Key, kvp.Value))); + return json; } @@ -123,6 +129,12 @@ protected override Expression VisitMethodCall(MethodCallExpression m) { if (this.ParseOrderByExpression(m, "desc")) { return this.Visit(m.Arguments[0]); } + } else if (methodName == "AddQueryOption") { + if (this.ParseAddQueryOptionExpression(m)) { + var operand = ((UnaryExpression)m.Object).Operand; + this.Visit(operand); + return m; + } } throw new NotSupportedException(string.Format("The method '{0}' is not supported", methodName)); @@ -289,6 +301,15 @@ private bool ParseOrderByExpression(MethodCallExpression expression, string orde return false; } + private bool ParseAddQueryOptionExpression(MethodCallExpression expression) { + if (Parameters == null) + Parameters = new Dictionary(); + + Parameters.Add(((ConstantExpression)expression.Arguments[0]).Value.ToString(), ((ConstantExpression)expression.Arguments[1]).Value.ToString()); + + return true; + } + private bool ParseTakeExpression(MethodCallExpression expression) { return ParseIntExpression(expression, i => Take = i); } From cbfb64950f5ef62fdf8ea7287e629532123532d5 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Tue, 13 Oct 2020 10:01:40 +0200 Subject: [PATCH 03/17] fix instructions order --- Breeze.Sharp/MetadataStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Breeze.Sharp/MetadataStore.cs b/Breeze.Sharp/MetadataStore.cs index 494be95..eba36db 100644 --- a/Breeze.Sharp/MetadataStore.cs +++ b/Breeze.Sharp/MetadataStore.cs @@ -541,14 +541,14 @@ private void DeserializeFrom(JNode jNode, bool isFromServer) { private void UpdateStructuralTypeFromJNode(JNode jNode, bool isFromServer) { var name = GetStructuralTypeNameFromJNode(jNode, isFromServer); var stype = GetStructuralTypeCore(name); - if (isFromServer) - stype.NameOnServer = $"{jNode.Get("shortName")}:#{jNode.Get("namespace")}"; if (stype == null) { var isComplexType = jNode.Get("isComplexType", false); OnMetadataMismatch(name, null, isComplexType ? MetadataMismatchTypes.MissingCLRComplexType : MetadataMismatchTypes.MissingCLREntityType); return; } + if (isFromServer) + stype.NameOnServer = $"{jNode.Get("shortName")}:#{jNode.Get("namespace")}"; stype.UpdateFromJNode(jNode, isFromServer); } From 8823cdd7c2a19f1806d5501f021643ef3547d6ee Mon Sep 17 00:00:00 2001 From: GioviQ Date: Tue, 20 Oct 2020 15:32:44 +0200 Subject: [PATCH 04/17] fix missing TimeSpanConverter --- Breeze.Sharp/JNode.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Breeze.Sharp/JNode.cs b/Breeze.Sharp/JNode.cs index 82d7383..20a4716 100644 --- a/Breeze.Sharp/JNode.cs +++ b/Breeze.Sharp/JNode.cs @@ -1,4 +1,4 @@ - + using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -318,6 +318,7 @@ public Stream SerializeTo(Stream stream) { public TextWriter SerializeTo(TextWriter textWriter) { var serializer = new JsonSerializer(); serializer.Converters.Add(new StringEnumConverter()); + serializer.Converters.Add(new TimeSpanConverter()); #if DEBUG serializer.Formatting = Formatting.Indented; From 71982ca80fbb0c83d20cf14e83e9d42dae1f7090 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Tue, 17 Nov 2020 15:07:08 +0100 Subject: [PATCH 05/17] parameters escape --- Breeze.Sharp/EntityQuery.cs | 4 ++-- Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs | 6 ++++-- Tests/Internal/Tests/JsonQuerySerializationTests.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Breeze.Sharp/EntityQuery.cs b/Breeze.Sharp/EntityQuery.cs index 2513240..068f4db 100644 --- a/Breeze.Sharp/EntityQuery.cs +++ b/Breeze.Sharp/EntityQuery.cs @@ -217,12 +217,12 @@ public override String GetResourcePath(MetadataStore metadataStore) { /// Return the query as JSON url, e.g. "Customer?{where:{FirstName:'Maria'}}" private string GetJsonResourcePath(string resourceName) { - var json = JsonQueryExpressionVisitor.Translate(this.Expression); + var json = JsonQueryExpressionVisitor.Translate(this.Expression, out string parameters); if (json.Length > 2) { // TODO may be able to get away with not escaping the URI System.Diagnostics.Debug.WriteLine($"json query: {json}"); var uri = Uri.EscapeUriString(json); - return resourceName + '?' + uri; + return resourceName + '?' + uri + (parameters != null ? "&" + parameters : string.Empty); } else { return resourceName; } diff --git a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs index d0b6531..6c7787f 100644 --- a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs +++ b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs @@ -27,7 +27,7 @@ public class JsonQueryExpressionVisitor : ExpressionVisitor { private ListExpressionVisitor expandVisitor; /// Translate the EntityQuery expression into a JSON string - public static string Translate(Expression expression) { + public static string Translate(Expression expression, out string parameters) { var visitor = new JsonQueryExpressionVisitor(); visitor.VisitRoot(expression); @@ -44,7 +44,9 @@ public static string Translate(Expression expression) { //without a server-side custom model binder for 'Customer?{"parameters":{"companyName":"C"}}' I cannot have parameters with right values, //so I have to use this hack if (visitor.Parameters?.Count > 0) - json = json + "&" + string.Join("&", visitor.Parameters.Select(kvp => string.Format("{0}={1}", kvp.Key, kvp.Value))); + parameters = string.Join("&", visitor.Parameters.Select(kvp => string.Format("{0}={1}", kvp.Key, Uri.EscapeDataString(kvp.Value)))); + else + parameters = null; return json; } diff --git a/Tests/Internal/Tests/JsonQuerySerializationTests.cs b/Tests/Internal/Tests/JsonQuerySerializationTests.cs index f38172c..66570fe 100644 --- a/Tests/Internal/Tests/JsonQuerySerializationTests.cs +++ b/Tests/Internal/Tests/JsonQuerySerializationTests.cs @@ -25,7 +25,7 @@ public void TearDown() { // TODO somehow compare JSON by structure instead of string, so whitespace changes won't matter private void Check(EntityQuery query, string expectedJson) { - var json = JsonQueryExpressionVisitor.Translate(query.Expression); + var json = JsonQueryExpressionVisitor.Translate(query.Expression, out string parameters); Assert.AreEqual(expectedJson, json); } From 592d02d47366de601be24dfa7c7d97701a9ea414 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Sun, 21 Feb 2021 18:02:16 +0100 Subject: [PATCH 06/17] escape json fix --- Breeze.Sharp/EntityQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Breeze.Sharp/EntityQuery.cs b/Breeze.Sharp/EntityQuery.cs index 068f4db..c5b32ae 100644 --- a/Breeze.Sharp/EntityQuery.cs +++ b/Breeze.Sharp/EntityQuery.cs @@ -221,7 +221,7 @@ private string GetJsonResourcePath(string resourceName) { if (json.Length > 2) { // TODO may be able to get away with not escaping the URI System.Diagnostics.Debug.WriteLine($"json query: {json}"); - var uri = Uri.EscapeUriString(json); + var uri = Uri.EscapeDataString(json); return resourceName + '?' + uri + (parameters != null ? "&" + parameters : string.Empty); } else { return resourceName; From d9e5dabd2bf7876904e182fbd781aeaa096e3abb Mon Sep 17 00:00:00 2001 From: GioviQ Date: Sun, 8 Aug 2021 06:10:40 +0200 Subject: [PATCH 07/17] Fix TimeSpan not found --- Breeze.Sharp/DataType.cs | 6 +++--- Breeze.Sharp/JNode.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Breeze.Sharp/DataType.cs b/Breeze.Sharp/DataType.cs index 61687c7..7f63ca7 100644 --- a/Breeze.Sharp/DataType.cs +++ b/Breeze.Sharp/DataType.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -91,8 +91,8 @@ public virtual Object Parse(Object val) { DataTypeInfo = DataTypeInfo.IsDate }; - public static DataType Time = new DataType(typeof(TimeSpan)) { - Name = "Time", + public static DataType TimeSpan = new DataType(typeof(TimeSpan)) { + Name = "TimeSpan", DefaultValue = new TimeSpan(0), FmtOData = FmtTime, }; diff --git a/Breeze.Sharp/JNode.cs b/Breeze.Sharp/JNode.cs index 20a4716..612b12c 100644 --- a/Breeze.Sharp/JNode.cs +++ b/Breeze.Sharp/JNode.cs @@ -140,7 +140,9 @@ public Object Get(String propName, Type objectType) { if (nonnullableType != null && nonnullableType.GetTypeInfo().IsEnum) { val = Enum.Parse(nonnullableType, prop.Value.ToString()); } else { - val = prop.Value.ToObject(objectType); + var serializer = new JsonSerializer(); + serializer.Converters.Add(new TimeSpanConverter()); + val = prop.Value.ToObject(objectType, serializer); } return val; } From bd6d05f6ddca7d41e75580573ae16cf5abb3e987 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Sun, 8 Aug 2021 06:12:10 +0200 Subject: [PATCH 08/17] fix issue with composite keys --- Breeze.Sharp/EntityType.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Breeze.Sharp/EntityType.cs b/Breeze.Sharp/EntityType.cs index 6b7dd06..d38b8dc 100644 --- a/Breeze.Sharp/EntityType.cs +++ b/Breeze.Sharp/EntityType.cs @@ -38,7 +38,7 @@ internal override void UpdateFromJNode(JNode jNode, bool isFromServer) { if (dp == null) { throw new Exception($"Data Property {dpName} not found on type {name}"); } - dp.UpdateFromJNode(jn, isFromServer); + dp.UpdateFromJNode(jn, isFromServer); }); jNode.GetJNodeArray("navigationProperties").ForEach(jn => { var npName = GetPropertyNameFromJNode(jn); @@ -56,7 +56,7 @@ internal override void UpdateFromJNode(JNode jNode, bool isFromServer) { } } - + JNode IJsonSerializable.ToJNode(Object config) { var jo = new JNode(); @@ -109,7 +109,7 @@ public AutoGeneratedKeyType AutoGeneratedKeyType { set { if (_autoGeneratedKeyType == value) return; _autoGeneratedKeyType = value; - bool isAutoIncrementing = ( value != AutoGeneratedKeyType.None); + bool isAutoIncrementing = (value != AutoGeneratedKeyType.None); if (_keyProperties.Count() == 1) { _keyProperties.First().IsAutoIncrementing = isAutoIncrementing; } @@ -180,7 +180,7 @@ public ReadOnlyCollection ConcurrencyProperties { get { return _concurrencyProperties.ReadOnlyValues; } } - + #endregion @@ -275,16 +275,16 @@ internal void UpdateKeyProperties(DataProperty dp) { } internal void UpdateConcurrencyProperties(DataProperty dp) { - UpdateCollection( _concurrencyProperties, dp, dp.ConcurrencyMode == ConcurrencyMode.Fixed); + UpdateCollection(_concurrencyProperties, dp, dp.ConcurrencyMode == ConcurrencyMode.Fixed); } internal void UpdateForeignKeyProperties(DataProperty dp) { - UpdateCollection( _foreignKeyProperties, dp, dp.IsForeignKey); + UpdateCollection(_foreignKeyProperties, dp, dp.IsForeignKey); } // not the same as the others above. internal void UpdateInverseForeignKeyProperties(DataProperty dp) { - UpdateCollection( _inverseForeignKeyProperties, dp, true); + UpdateCollection(_inverseForeignKeyProperties, dp, true); } private bool UpdateNavigationProperty(NavigationProperty np) { @@ -315,8 +315,12 @@ private bool UpdateNavigationProperty(NavigationProperty np) { } // sets navigation property: relatedDataProperties and dataProperty: relatedNavigationProperty - np.ForeignKeyProperties.ForEach(dp => dp.RelatedNavigationProperty = np); - + np.ForeignKeyProperties.ForEach(dp => { + //fix issue with composite keys + if (dp.RelatedNavigationProperty == null) + dp.RelatedNavigationProperty = np; + }); + return true; } From 487e9f4b6a6794adb911acfd7f4efc6623ef4035 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Mon, 9 Aug 2021 18:17:20 +0200 Subject: [PATCH 09/17] ParseOrderByExpression fix --- Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs index 6c7787f..e181f70 100644 --- a/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs +++ b/Breeze.Sharp/Json/JsonQueryExpressionVisitor.cs @@ -292,10 +292,18 @@ private bool ParseOrderByExpression(MethodCallExpression expression, string orde (MemberExpression)lambdaExpression.Body : ((UnaryExpression)lambdaExpression.Body).Operand as MemberExpression; if (body != null) { + var exp = new List(); + exp.Add(body.Member.Name); + + while (body.Expression is MemberExpression) { + body = (MemberExpression)body.Expression; + exp.Insert(0, body.Member.Name); + } + if (OrderBy == null) OrderBy = new List(); - OrderBy.Add(string.Format("{0}{1}", body.Member.Name, order)); + OrderBy.Add(string.Format("{0}{1}", string.Join(".", exp), order)); return true; } From 4b337a3c6daf797b0624773e6d785624e03e148b Mon Sep 17 00:00:00 2001 From: GioviQ Date: Wed, 18 Aug 2021 18:00:45 +0200 Subject: [PATCH 10/17] MetadataStore.OnMetadataMismatch in EntityType --- Breeze.Sharp/EntityType.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Breeze.Sharp/EntityType.cs b/Breeze.Sharp/EntityType.cs index d38b8dc..a9a15b9 100644 --- a/Breeze.Sharp/EntityType.cs +++ b/Breeze.Sharp/EntityType.cs @@ -36,7 +36,8 @@ internal override void UpdateFromJNode(JNode jNode, bool isFromServer) { var dpName = GetPropertyNameFromJNode(jn); var dp = et.GetDataProperty(dpName); if (dp == null) { - throw new Exception($"Data Property {dpName} not found on type {name}"); + MetadataStore.OnMetadataMismatch(name, dpName, MetadataMismatchTypes.MissingCLRDataProperty); + return; } dp.UpdateFromJNode(jn, isFromServer); }); @@ -44,7 +45,8 @@ internal override void UpdateFromJNode(JNode jNode, bool isFromServer) { var npName = GetPropertyNameFromJNode(jn); var np = et.GetNavigationProperty(npName); if (np == null) { - throw new Exception($"Navigation Property {npName} not found on type {name}"); + MetadataStore.OnMetadataMismatch(name, npName, MetadataMismatchTypes.MissingCLRNavigationProperty); + return; } np.UpdateFromJNode(jn, isFromServer); }); @@ -78,7 +80,7 @@ internal void Check(Object v1, Object v2, String name) { if (Object.Equals(v1, v2)) return; var msg = String.Format("EntityType metadata mismatch. EntityType: '{0}'. Metadata property: '{1}'. Client value: '{2}', Server value: '{3}'", this.Name, name, (v1 ?? "").ToString(), (v2 ?? "").ToString()); - MetadataStore.AddMessage(msg, MessageType.Error); + MetadataStore.OnMetadataMismatch(Name, null, MetadataMismatchTypes.InconsistentCLRTypeDefinition, msg); } #region Public properties @@ -359,8 +361,4 @@ public enum AutoGeneratedKeyType { Identity = 1, KeyGenerator = 2 } - - - - } From ddee84db39a3f78b2b093880572301504a5b7f32 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Mon, 23 Aug 2021 17:14:48 +0200 Subject: [PATCH 11/17] fix issue with composite keys --- Breeze.Sharp/EntityAspect.cs | 11 ++++++++++- Breeze.Sharp/EntityType.cs | 8 ++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Breeze.Sharp/EntityAspect.cs b/Breeze.Sharp/EntityAspect.cs index 4b2839c..bb2789c 100644 --- a/Breeze.Sharp/EntityAspect.cs +++ b/Breeze.Sharp/EntityAspect.cs @@ -1002,7 +1002,16 @@ internal void UpdateRelated(DataProperty property, object newValue, object oldVa // ==> (see set navProp above) if (newValue != null) { - var key = new EntityKey(relatedNavProp.EntityType, newValue); + var newValues = new List(); + + foreach (var fKey in relatedNavProp.ForeignKeyProperties) { + if (fKey == property) + newValues.Add(newValue); + else + newValues.Add(GetValue(fKey)); + } + + var key = new EntityKey(relatedNavProp.EntityType, newValues.ToArray()); var relatedEntity = EntityManager.GetEntityByKey(key); if (relatedEntity != null) { diff --git a/Breeze.Sharp/EntityType.cs b/Breeze.Sharp/EntityType.cs index a9a15b9..eb7a32b 100644 --- a/Breeze.Sharp/EntityType.cs +++ b/Breeze.Sharp/EntityType.cs @@ -318,9 +318,13 @@ private bool UpdateNavigationProperty(NavigationProperty np) { // sets navigation property: relatedDataProperties and dataProperty: relatedNavigationProperty np.ForeignKeyProperties.ForEach(dp => { - //fix issue with composite keys - if (dp.RelatedNavigationProperty == null) + if (np.EntityType.KeyProperties.Count == 1) dp.RelatedNavigationProperty = np; + else { + dp.IsForeignKey = true; + np.EntityType.UpdateInverseForeignKeyProperties(dp); + np.UpdateWithRelatedDataProperty(dp); + } }); return true; From a19a188b23c345822186a3ce55e139926701852f Mon Sep 17 00:00:00 2001 From: GioviQ Date: Tue, 31 Aug 2021 14:48:52 +0200 Subject: [PATCH 12/17] RelatedNavigationProperty null check --- Breeze.Sharp/EntityAspect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Breeze.Sharp/EntityAspect.cs b/Breeze.Sharp/EntityAspect.cs index bb2789c..3e24e51 100644 --- a/Breeze.Sharp/EntityAspect.cs +++ b/Breeze.Sharp/EntityAspect.cs @@ -624,7 +624,7 @@ private void SetDpValueKey(DataProperty property, object newValue, object oldVal if (this.IsAttached) { this.EntityType.InverseForeignKeyProperties.ForEach(invFkProp => { - if (invFkProp.RelatedNavigationProperty.Inverse == null) { + if (invFkProp.RelatedNavigationProperty != null && invFkProp.RelatedNavigationProperty.Inverse == null) { // this next step may be slow - it iterates over all of the entities in a group; // hopefully it doesn't happen often. EntityManager.UpdateFkVal(invFkProp, oldValue, newValue); From cd6e12cb7cdf953d354838de8194f8932be84eb1 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Wed, 22 Sep 2021 17:41:46 +0200 Subject: [PATCH 13/17] DomainException for clear error messages --- Breeze.Sharp/DomainException.cs | 21 +++++++++++++++++++++ Breeze.Sharp/MetadataStore.cs | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 Breeze.Sharp/DomainException.cs diff --git a/Breeze.Sharp/DomainException.cs b/Breeze.Sharp/DomainException.cs new file mode 100644 index 0000000..7a42c0c --- /dev/null +++ b/Breeze.Sharp/DomainException.cs @@ -0,0 +1,21 @@ +using System; + +namespace Breeze.Sharp { + public class DomainException : Exception { + public string Description { get; } + + public DomainException(string description) : base() { + Description = description; + } + + public DomainException(string description, string message) : base(message) { + Description = description; + + } + + public DomainException(string description, string message, Exception innerException) : base(message, innerException) { + Description = description; + + } + } +} diff --git a/Breeze.Sharp/MetadataStore.cs b/Breeze.Sharp/MetadataStore.cs index eba36db..e8e03d8 100644 --- a/Breeze.Sharp/MetadataStore.cs +++ b/Breeze.Sharp/MetadataStore.cs @@ -191,14 +191,14 @@ public async Task FetchMetadata(DataService dataService, Cancellati } var errorMessages = GetMessages(MessageType.Error).ToList(); if (errorMessages.Any()) { - throw new Exception("Metadata errors encountered: \n" + errorMessages.ToAggregateString("\n")); + throw new DomainException("Metadata errors encountered: \n" + errorMessages.ToAggregateString("\n")); } dataService.ServerMetadata = metadata; AddDataService(dataService); return dataService; } catch (Exception e) { - if (!(e is TaskCanceledException)) + if (!(e is DomainException)) throw new Exception("Unable to locate metadata resource for: " + dataService.ServiceName, e); throw; } finally { From 7c0ef68e6fbbb7626882c360149fbec369f9600f Mon Sep 17 00:00:00 2001 From: GioviQ Date: Tue, 16 Nov 2021 16:31:08 +0100 Subject: [PATCH 14/17] from Edm.Time to Edm.TimeSpan --- Breeze.Sharp/CsdlMetadataProcessor.cs | 48 +++++++++++++++------------ Breeze.Sharp/DataType.cs | 4 ++- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Breeze.Sharp/CsdlMetadataProcessor.cs b/Breeze.Sharp/CsdlMetadataProcessor.cs index 696dca3..22b3c10 100644 --- a/Breeze.Sharp/CsdlMetadataProcessor.cs +++ b/Breeze.Sharp/CsdlMetadataProcessor.cs @@ -1,4 +1,4 @@ -using Breeze.Sharp.Core; +using Breeze.Sharp.Core; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -11,11 +11,11 @@ namespace Breeze.Sharp { internal class CsdlMetadataProcessor { public CsdlMetadataProcessor() { - + } public MetadataStore MetadataStore { get; private set; } - + public void ProcessMetadata(MetadataStore metadataStore, JObject json) { MetadataStore = metadataStore; _schema = json["schema"]; @@ -38,7 +38,7 @@ public void ProcessMetadata(MetadataStore metadataStore, JObject json) { } et.UpdateNavigationProperties(); }); - + var complexTypes = ToEnumerable(_schema["complexType"]).Cast() .Select(ParseCsdlComplexType).Where(ct => ct != null).ToList(); @@ -47,10 +47,10 @@ public void ProcessMetadata(MetadataStore metadataStore, JObject json) { if (entityContainer != null) { var entitySets = ToEnumerable(entityContainer["entitySet"]).Cast().ToList(); entitySets.ForEach(es => { - var clientEtName = GetClientTypeNameFromClrTypeName((String) es["entityType"]); + var clientEtName = GetClientTypeNameFromClrTypeName((String)es["entityType"]); var entityType = MetadataStore.GetEntityType(clientEtName, true); if (entityType != null) { - var resourceName = (String) es["name"]; + var resourceName = (String)es["name"]; MetadataStore.SetResourceName(resourceName, entityType, true); } }); @@ -58,12 +58,12 @@ public void ProcessMetadata(MetadataStore metadataStore, JObject json) { } - + private NamingConvention NamingConvention { get { return MetadataStore.NamingConvention; } } - + private EntityType ParseCsdlEntityType(JObject csdlEntityType) { var abstractVal = (String)csdlEntityType["abstract"]; var baseTypeVal = (String)csdlEntityType["baseType"]; @@ -75,9 +75,9 @@ private EntityType ParseCsdlEntityType(JObject csdlEntityType) { MetadataStore.OnMetadataMismatch(etName, null, MetadataMismatchTypes.MissingCLREntityType); return null; } - + entityType.IsAbstract = isAbstract; - + var baseKeyNamesOnServer = new List(); if (baseTypeVal != null) { var baseEtName = GetClientTypeNameFromClrTypeName(baseTypeVal); @@ -96,7 +96,7 @@ private EntityType ParseCsdlEntityType(JObject csdlEntityType) { var keyNamesOnServer = keyVal == null ? new List() : ToEnumerable(keyVal["propertyRef"]).Select(x => (String)x["name"]).ToList(); - + keyNamesOnServer.AddRange(baseKeyNamesOnServer); ToEnumerable(csdlEntityType["property"]).ForEach(csdlDataProp => { @@ -135,6 +135,10 @@ private DataProperty ParseCsdlDataProperty(StructuralType parentType, JObject cs private DataProperty ParseCsdlSimpleProperty(StructuralType parentType, JObject csdlProperty, List keyNamesOnServer) { var typeVal = (String)csdlProperty["type"]; + + if (typeVal == "Edm.Time") + typeVal = "Edm.TimeSpan"; + var nameVal = (String)csdlProperty["name"]; var nullableVal = (String)csdlProperty["nullable"]; var maxLengthVal = (String)csdlProperty["maxLength"]; @@ -146,7 +150,7 @@ private DataProperty ParseCsdlSimpleProperty(StructuralType parentType, JObject } var dpName = NamingConvention.ServerPropertyNameToClient(nameVal, parentType); - var dp = parentType.GetDataProperty( dpName); + var dp = parentType.GetDataProperty(dpName); if (dp == null) { MetadataStore.OnMetadataMismatch(parentType.Name, dpName, MetadataMismatchTypes.MissingCLRDataProperty); return null; @@ -178,7 +182,7 @@ private DataProperty ParseCsdlSimpleProperty(StructuralType parentType, JObject var maxLength = (maxLengthVal == null || maxLengthVal == "Max") ? (Int64?)null : Int64.Parse(maxLengthVal); var concurrencyMode = concurrencyModeVal == "fixed" ? ConcurrencyMode.Fixed : ConcurrencyMode.None; - + CheckProperty(dp, dp.DataType, dataType, "DataType"); CheckProperty(dp, dp.IsScalar, true, "IsScalar"); @@ -187,7 +191,7 @@ private DataProperty ParseCsdlSimpleProperty(StructuralType parentType, JObject dp.IsNullable = isNullable; dp.MaxLength = maxLength; dp.DefaultValue = defaultValue; - // fixedLength: fixedLength, + // fixedLength: fixedLength, dp.ConcurrencyMode = concurrencyMode; dp.IsAutoIncrementing = isAutoIncrementing; @@ -210,7 +214,7 @@ private DataProperty ParseCsdlComplexProperty(StructuralType parentType, JObject MetadataStore.OnMetadataMismatch(parentType.Name, name, MetadataMismatchTypes.MissingCLRDataProperty); return null; } - + if (!dp.IsComplexProperty) { var detail = "Defined as a ComplexProperty on the server but not on the client"; MetadataStore.OnMetadataMismatch(parentType.Name, name, MetadataMismatchTypes.InconsistentCLRPropertyDefinition, detail); @@ -241,7 +245,7 @@ private NavigationProperty ParseCsdlNavigationProperty(EntityType parentType, JO // throw new Error("Foreign Key Associations must be turned on for this model"); // } } - + var name = NamingConvention.ServerPropertyNameToClient(nameOnServer, parentType); var np = parentType.GetNavigationProperty(name); if (np == null) { @@ -252,8 +256,8 @@ private NavigationProperty ParseCsdlNavigationProperty(EntityType parentType, JO CheckProperty(np, np.EntityType.Name, dataEtName, "EntityTypeName"); CheckProperty(np, np.IsScalar, isScalar, "IsScalar"); - np.AssociationName = (String) association["name"]; - + np.AssociationName = (String)association["name"]; + var principal = constraintVal["principal"]; var dependent = constraintVal["dependent"]; @@ -361,7 +365,7 @@ private void AddValidators(DataProperty dp) { dp._validators.Add(new RequiredValidator()); } if (dp.DataType == DataType.String && dp.MaxLength.HasValue) { - var vr = new MaxLengthValidator((Int32) dp.MaxLength.Value); + var vr = new MaxLengthValidator((Int32)dp.MaxLength.Value); dp._validators.Add(vr); } } @@ -410,9 +414,9 @@ private IEnumerable ToEnumerable(T d) { private JToken _schema; private String _namespace; - + private Dictionary _cSpaceOSpaceMap; - - + + } } diff --git a/Breeze.Sharp/DataType.cs b/Breeze.Sharp/DataType.cs index 7f63ca7..b07690c 100644 --- a/Breeze.Sharp/DataType.cs +++ b/Breeze.Sharp/DataType.cs @@ -229,6 +229,8 @@ protected static String FmtUndefined(Object val) { return val.ToString(); } - + public override string ToString() { + return $"{nameof(DataType)}: {Name}"; + } } } From e05a1714f9e738d638222fb11a06e0d84b2de4a0 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Sat, 22 Jan 2022 10:43:02 +0100 Subject: [PATCH 15/17] fix DomainException message --- Breeze.Sharp/DomainException.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Breeze.Sharp/DomainException.cs b/Breeze.Sharp/DomainException.cs index 7a42c0c..492f480 100644 --- a/Breeze.Sharp/DomainException.cs +++ b/Breeze.Sharp/DomainException.cs @@ -4,8 +4,8 @@ namespace Breeze.Sharp { public class DomainException : Exception { public string Description { get; } - public DomainException(string description) : base() { - Description = description; + public DomainException(string message) : base(message) { + } public DomainException(string description, string message) : base(message) { From 27579acc2f3850c7e92295aaf127c848b16b37eb Mon Sep 17 00:00:00 2001 From: GioviQ Date: Sat, 2 Apr 2022 11:35:54 +0200 Subject: [PATCH 16/17] MigrationBackup folder deleted --- .../Breeze.Sharp.Internal.Tests.csproj | 159 ---------------- .../NuGetUpgradeLog.html | 163 ---------------- .../packages.config | 8 - .../502076ed/Breeze.Sharp/Breeze.Sharp.csproj | 178 ------------------ .../Breeze.Sharp/NuGetUpgradeLog.html | 169 ----------------- .../502076ed/Breeze.Sharp/packages.config | 12 -- 6 files changed, 689 deletions(-) delete mode 100644 MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/Breeze.Sharp.Internal.Tests.csproj delete mode 100644 MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/NuGetUpgradeLog.html delete mode 100644 MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/packages.config delete mode 100644 MigrationBackup/502076ed/Breeze.Sharp/Breeze.Sharp.csproj delete mode 100644 MigrationBackup/502076ed/Breeze.Sharp/NuGetUpgradeLog.html delete mode 100644 MigrationBackup/502076ed/Breeze.Sharp/packages.config diff --git a/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/Breeze.Sharp.Internal.Tests.csproj b/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/Breeze.Sharp.Internal.Tests.csproj deleted file mode 100644 index 6f77be0..0000000 --- a/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/Breeze.Sharp.Internal.Tests.csproj +++ /dev/null @@ -1,159 +0,0 @@ - - - - Debug - AnyCPU - {C291ECD4-FD4E-43C2-A6BE-BAB5B7BA8E6E} - Library - Properties - Breeze.Sharp.Tests - Breeze.Sharp.Tests - v4.5 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - ..\ - true - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NOT_NHIBERNATE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - ..\..\..\packages\Microsoft.Data.Edm.5.6.3\lib\net40\Microsoft.Data.Edm.dll - - - ..\..\..\packages\Microsoft.Data.OData.5.6.3\lib\net40\Microsoft.Data.OData.dll - - - ..\..\..\packages\Microsoft.Data.Services.Client.5.6.3\lib\net40\Microsoft.Data.Services.Client.dll - - - ..\..\..\packages\Newtonsoft.Json.6.0.5\lib\net45\Newtonsoft.Json.dll - - - - - ..\..\..\packages\System.Spatial.5.6.3\lib\net40\System.Spatial.dll - - - - - - - - - - - - - - - - - - - - - - - - Code - - - Code - - - - - - - - - - - - - - - - - - - - - {54b1ce10-2847-4565-839a-26da3a51af38} - Breeze.Sharp - - - {3115a147-39a3-4e8f-a8ba-265bb2571acc} - Model.CodeCamper - - - {c365d56f-1bd4-4d6c-88ef-84f78de9243d} - Model.Edmunds - - - {58b41cc6-8d34-40c4-9342-db55ae6cca02} - Model.Inheritance.Billing.Sharp - - - {73c7f5c5-53ee-4f8c-af80-2fd74a9cf553} - Model.Inheritance.Produce.Sharp - - - {e357f17b-6a21-4682-b7c8-3ebfebf862f3} - Model.Northwind.Sharp - - - - - - - False - - - False - - - False - - - False - - - - - - - - - \ No newline at end of file diff --git a/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/NuGetUpgradeLog.html b/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/NuGetUpgradeLog.html deleted file mode 100644 index 3997d17..0000000 --- a/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/NuGetUpgradeLog.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - NuGetMigrationLog -

- NuGet Migration Report - Breeze.Sharp.Internal.Tests

Overview

Migration to PackageReference was completed successfully. Please build and run your solution to verify that all packages are available.
- If you run into any problems, have feedback, questions, or concerns, please - file an issue on the NuGet GitHub repository.
- Changed files and this report have been backed up here: - C:\GitHub\breeze.sharp\MigrationBackup\45389cf9\Breeze.Sharp.Internal.Tests

Packages processed

Top-level dependencies:

Package IdVersion
Microsoft.Data.Services.Client - v5.6.3
Newtonsoft.Json - v6.0.5

Transitive dependencies:

Package IdVersion
Microsoft.Data.Edm - v5.6.3
Microsoft.Data.OData - v5.6.3
System.Spatial - v5.6.3

Package compatibility issues

Description
Newtonsoft.Json - v6.0.5
install.ps1 script will be ignored when the package is installed after the migration.
\ No newline at end of file diff --git a/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/packages.config b/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/packages.config deleted file mode 100644 index eec737d..0000000 --- a/MigrationBackup/45389cf9/Breeze.Sharp.Internal.Tests/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/MigrationBackup/502076ed/Breeze.Sharp/Breeze.Sharp.csproj b/MigrationBackup/502076ed/Breeze.Sharp/Breeze.Sharp.csproj deleted file mode 100644 index bafac03..0000000 --- a/MigrationBackup/502076ed/Breeze.Sharp/Breeze.Sharp.csproj +++ /dev/null @@ -1,178 +0,0 @@ - - - - - 11.0 - Debug - AnyCPU - {54B1CE10-2847-4565-839A-26DA3A51AF38} - Library - Properties - Breeze.Sharp - Breeze.Sharp - v4.5 - Profile259 - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - ..\ - true - b5fec290 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Breeze.Sharp.xml - 1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Breeze.Sharp.XML - 1591 - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ..\packages\Microsoft.Data.Edm.5.6.3\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.Edm.dll - - - ..\packages\Microsoft.Data.OData.5.6.3\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.OData.dll - - - ..\packages\Microsoft.Data.Services.Client.5.6.3\lib\portable-net45+wp8+win8+wpa\Microsoft.Data.Services.Client.dll - - - ..\packages\Newtonsoft.Json.6.0.4\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll - - - ..\packages\Microsoft.Net.Http.2.2.22\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.dll - - - ..\packages\Microsoft.Net.Http.2.2.22\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.Extensions.dll - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\portable-wp8+netcore45+net45+wp81+wpa81\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.Net.Http.2.2.22\lib\portable-net40+sl4+win8+wp71+wpa81\System.Net.Http.Primitives.dll - - - ..\packages\System.Spatial.5.6.3\lib\portable-net45+wp8+win8+wpa\System.Spatial.dll - - - - - - Designer - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MigrationBackup/502076ed/Breeze.Sharp/NuGetUpgradeLog.html b/MigrationBackup/502076ed/Breeze.Sharp/NuGetUpgradeLog.html deleted file mode 100644 index af934f2..0000000 --- a/MigrationBackup/502076ed/Breeze.Sharp/NuGetUpgradeLog.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - NuGetMigrationLog -

- NuGet Migration Report - Breeze.Sharp

Overview

Migration to PackageReference was completed successfully. Please build and run your solution to verify that all packages are available.
- If you run into any problems, have feedback, questions, or concerns, please - file an issue on the NuGet GitHub repository.
- Changed files and this report have been backed up here: - C:\GitHub\breeze.sharp\MigrationBackup\502076ed\Breeze.Sharp

Packages processed

Top-level dependencies:

Package IdVersion
Microsoft.AspNet.WebApi.Client - v5.2.3
Microsoft.Data.Services.Client - v5.6.3

Transitive dependencies:

Package IdVersion
Microsoft.Bcl - v1.1.9
Microsoft.Bcl.Build - v1.0.14
Microsoft.Data.Edm - v5.6.3
Microsoft.Data.OData - v5.6.3
Microsoft.Net.Http - v2.2.22
Newtonsoft.Json - v6.0.4
System.Spatial - v5.6.3

Package compatibility issues

Description
Microsoft.Bcl - v1.1.9
'content' assets will not be available when the package is installed after the migration.
Microsoft.Bcl.Build - v1.0.14
install.ps1 script will be ignored when the package is installed after the migration.
'content' assets will not be available when the package is installed after the migration.
Newtonsoft.Json - v6.0.4
install.ps1 script will be ignored when the package is installed after the migration.
\ No newline at end of file diff --git a/MigrationBackup/502076ed/Breeze.Sharp/packages.config b/MigrationBackup/502076ed/Breeze.Sharp/packages.config deleted file mode 100644 index 176d1eb..0000000 --- a/MigrationBackup/502076ed/Breeze.Sharp/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file From 5ac674e8d20ef126cee35d96e4b02fb2d494d050 Mon Sep 17 00:00:00 2001 From: GioviQ Date: Sat, 2 Apr 2022 11:39:39 +0200 Subject: [PATCH 17/17] AutoGeneratedKeyType in its own file --- Breeze.Sharp/AutoGeneratedKeyType.cs | 10 ++++++++++ Breeze.Sharp/EntityType.cs | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 Breeze.Sharp/AutoGeneratedKeyType.cs diff --git a/Breeze.Sharp/AutoGeneratedKeyType.cs b/Breeze.Sharp/AutoGeneratedKeyType.cs new file mode 100644 index 0000000..190d918 --- /dev/null +++ b/Breeze.Sharp/AutoGeneratedKeyType.cs @@ -0,0 +1,10 @@ +namespace Breeze.Sharp { + /// + /// AutoGeneratedKeyType is an enum containing all of the valid states for an automatically generated key. + /// + public enum AutoGeneratedKeyType { + None = 0, + Identity = 1, + KeyGenerator = 2 + } +} diff --git a/Breeze.Sharp/EntityType.cs b/Breeze.Sharp/EntityType.cs index eb7a32b..ce8dc02 100644 --- a/Breeze.Sharp/EntityType.cs +++ b/Breeze.Sharp/EntityType.cs @@ -356,13 +356,4 @@ protected override String GetKeyForItem(EntityType item) { return item.ShortName + ":#" + item.Namespace; } } - - /// - /// AutoGeneratedKeyType is an enum containing all of the valid states for an automatically generated key. - /// - public enum AutoGeneratedKeyType { - None = 0, - Identity = 1, - KeyGenerator = 2 - } }