From f8c40428b8bd0a16ba9b7e6d3c935c8797a40932 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Tue, 15 Sep 2026 18:00:09 -0500 Subject: [PATCH 1/7] #4828 Remove legacy data portal operation method name fallback (Stage A) - Remove the DataPortal_XYZ/Child_XYZ name-matching fallback from ServiceProviderMethodCaller and the DataPortalOptions.UseLegacyOperationMethods option - Add operation attributes to legacy-named test operation methods - Replace the legacy fallback tests with tests that attribute-less legacy-named methods are not found - Draft release notes, upgrade guide, and analyzer doc updates Base class changes (Phase 2) and analyzer changes (Phase 3) follow after #4925. Co-Authored-By: Claude Opus 5 (1M context) --- .../Configuration/Fluent/DataPortalOptions.cs | 7 -- .../Reflection/ServiceProviderMethodCaller.cs | 28 +------ .../Csla.Ios.Test/Nullable/NullableEntity.cs | 1 + .../ModelBinderTest/Child.cs | 1 + .../ModelBinderTest/RootList.cs | 1 + .../ModelBinderTest/RootWithChildren.cs | 2 + .../ModelBinderTest/RootWithValidation.cs | 1 + .../ModelBinderTest/SingleRoot.cs | 2 + .../tests/Csla.test/AppContext/SimpleRoot.cs | 2 + .../tests/Csla.test/Basic/CollectionTests.cs | 1 + Source/tests/Csla.test/Basic/GenRootBase.cs | 2 + .../tests/Csla.test/Basic/NameValueListObj.cs | 1 + Source/tests/Csla.test/Basic/ReadOnlyList.cs | 1 + Source/tests/Csla.test/BasicModern/Child.cs | 5 ++ Source/tests/Csla.test/BasicModern/Root.cs | 1 + .../ChildChanged/OptimizeChildChangedTests.cs | 3 + .../Csla.test/DataPortal/DataPortalTests.cs | 1 + Source/tests/Csla.test/DataPortal/DpRoot.cs | 2 + .../Csla.test/DataPortal/InterceptorTests.cs | 5 ++ Source/tests/Csla.test/DataPortal/Legacy.cs | 1 + .../Csla.test/DataPortal/LegacySplitBase.cs | 1 + .../Csla.test/DataPortal/SingleOverload.cs | 2 + .../tests/Csla.test/DataPortal/SplitBase.cs | 1 + .../Csla.test/DataPortal/SplitOverloadBase.cs | 2 + .../Csla.test/DataPortal/StronglyTypedDP.cs | 2 + .../Csla.test/DataPortal/TransactionalRoot.cs | 1 + .../Fakes/Server/CslaDataProvider/Customer.cs | 2 + .../CslaDataProvider/CustomerContact.cs | 4 + .../CslaDataProvider/CustomerContactList.cs | 1 + .../Server/CslaDataProvider/CustomerList.cs | 1 + .../CslaDataProvider/CustomerWithError.cs | 2 + .../CslaDataProvider/CustomerWithErrorList.cs | 1 + .../DataPortal/PrimitiveCriteriaSingle.cs | 4 + .../Fakes/Server/DataPortal/Single.cs | 5 ++ .../Fakes/Server/DataPortal/TestBO.cs | 3 + .../ReadOnlyTests/ReadOnlyPerson.partial.cs | 1 + .../ReadOnlyTests/ReadOnlyPersonList.cs | 1 + Source/tests/Csla.test/FieldManager/Child.cs | 1 + .../tests/Csla.test/FieldManager/ChildList.cs | 1 + .../tests/Csla.test/GraphMerge/TestClasses.cs | 2 + .../Csla.test/GraphMergeAsync/TestClasses.cs | 2 + .../Csla.test/IO/DPMethodOverloadTests.cs | 3 + .../Linq/LinqObservableCollectionTest.cs | 1 + .../Csla.test/Nullable/NullableObject.cs | 1 + .../tests/Csla.test/RollBack/RollbackRoot.cs | 2 + .../HasRulesManager2.partial.cs | 1 + .../ValidationRules/RuleBaseClassesRoot.cs | 1 + .../DataPortal/InheritedOperationTests.cs | 3 + .../ServiceProviderMethodCallerTests.cs | 62 +++++---------- docs/Abstractions-in-CSLA.md | 9 ++- docs/Data-Access.md | 4 +- docs/Upgrading to CSLA 11.md | 79 +++++++++++++++++++ ...SLA0002-IsOperationMethodPublicAnalyzer.md | 3 +- ...SLA0009-IsOperationMethodPublicAnalyzer.md | 3 +- ...onsWithNonSerializableArgumentsAnalyzer.md | 3 +- ...rationsWithIncorrectReturnTypesAnalyzer.md | 3 +- ...-DoesChildOperationHaveRunLocalAnalyzer.md | 3 +- ...0014-DoesOperationHaveAttributeAnalyzer.md | 4 +- releasenotes.md | 6 ++ 59 files changed, 211 insertions(+), 88 deletions(-) create mode 100644 docs/Upgrading to CSLA 11.md diff --git a/Source/Csla/Configuration/Fluent/DataPortalOptions.cs b/Source/Csla/Configuration/Fluent/DataPortalOptions.cs index 70d2d25349..afaa718b1f 100644 --- a/Source/Csla/Configuration/Fluent/DataPortalOptions.cs +++ b/Source/Csla/Configuration/Fluent/DataPortalOptions.cs @@ -60,13 +60,6 @@ public string VersionRoutingTag /// internal DataPortalServerOptions DataPortalServerOptions { get; } - /// - /// Gets or sets a value indicating whether legacy DataPortal_XYZ and - /// Child_XYZ method names are used as a fallback when no attributed - /// data portal operation methods are found. Default is true. - /// - public bool UseLegacyOperationMethods { get; set; } = true; - internal CslaOptions CslaOptions { get; } } } diff --git a/Source/Csla/Reflection/ServiceProviderMethodCaller.cs b/Source/Csla/Reflection/ServiceProviderMethodCaller.cs index 5a040c29e8..60a8e6117f 100644 --- a/Source/Csla/Reflection/ServiceProviderMethodCaller.cs +++ b/Source/Csla/Reflection/ServiceProviderMethodCaller.cs @@ -118,9 +118,6 @@ public bool TryFindDataPortalMethod([DynamicallyAccessedMembers(DynamicallyAc var activator = _applicationContext.GetRequiredService(); targetType = activator.ResolveType(targetType); - var cslaOptions = _applicationContext.GetRequiredService(); - var useLegacyMethods = cslaOptions.DataPortalOptions.UseLegacyOperationMethods; - var typeOfOperation = typeof(T); // Resolve the factory type (if any) up front so it can participate in the cache key. @@ -134,7 +131,7 @@ public bool TryFindDataPortalMethod([DynamicallyAccessedMembers(DynamicallyAc if (factoryInfo != null && !TryGetFactoryType(factoryInfo, _applicationContext, throwOnError, out factoryType)) return null; - var cacheKey = GetCacheKeyName(targetType, typeOfOperation, criteria, useLegacyMethods, factoryType); + var cacheKey = GetCacheKeyName(targetType, typeOfOperation, criteria, factoryType); #if NET8_0_OR_GREATER if (_methodCache.TryGetValue(cacheKey, out var unloadableCachedMethodInfo)) @@ -195,24 +192,6 @@ public bool TryFindDataPortalMethod([DynamicallyAccessedMembers(DynamicallyAc tt = tt.BaseType; level--; } - - // if no attribute-based methods found, look for legacy methods - if (!candidates.Any() && useLegacyMethods) - { - var attributeName = typeOfOperation.Name.Substring(0, typeOfOperation.Name.IndexOf("Attribute")); - var methodName = attributeName.Contains("Child") ? - "Child_" + attributeName.Substring(0, attributeName.IndexOf("Child")) : - "DataPortal_" + attributeName; - tt = targetType; - level = 0; - while (tt != null) - { - var ttList = tt.GetMethods(_bindingAttr).Where(m => m.Name == methodName); - candidates.AddRange(ttList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level })); - tt = tt.BaseType; - level--; - } - } } static bool TryGetFactoryType(ObjectFactoryAttribute factoryAttribute, ApplicationContext context, bool throwOnError, [NotNullWhen(true)] out Type? factoryType) @@ -437,11 +416,10 @@ private static int CalculateParameterScore(ParameterInfo methodParam, object? c) return 0; } - private static string GetCacheKeyName(Type targetType, Type operationType, object?[]? criteria, bool useLegacyMethods, Type? factoryType = null) + private static string GetCacheKeyName(Type targetType, Type operationType, object?[]? criteria, Type? factoryType = null) { - var legacy = useLegacyMethods ? "" : "|nolegacy"; var factory = factoryType is null ? "" : $"|{factoryType.FullName}"; - return $"{targetType.FullName}.[{operationType.Name.Replace("Attribute", "")}]{GetCriteriaTypeNames(criteria)}{legacy}{factory}"; + return $"{targetType.FullName}.[{operationType.Name.Replace("Attribute", "")}]{GetCriteriaTypeNames(criteria)}{factory}"; } private static string GetCriteriaTypeNames(object?[]? criteria) diff --git a/Source/tests/Csla.Ios.Test/Nullable/NullableEntity.cs b/Source/tests/Csla.Ios.Test/Nullable/NullableEntity.cs index 9cfbde633a..ae46f9ce64 100644 --- a/Source/tests/Csla.Ios.Test/Nullable/NullableEntity.cs +++ b/Source/tests/Csla.Ios.Test/Nullable/NullableEntity.cs @@ -63,6 +63,7 @@ public static NullableEntity NewNullableEntity() return Csla.DataPortal.Create(); } + [Create] protected override void DataPortal_Create() { Csla.ApplicationContext.GlobalContext.Add("NullableObject", "Created"); diff --git a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/Child.cs b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/Child.cs index d6f94cc4ea..ab5fc40270 100644 --- a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/Child.cs +++ b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/Child.cs @@ -32,6 +32,7 @@ public string Max5Chars private Child() {} + [FetchChild] private void Child_Fetch(int idx) { using (BypassPropertyChecks) diff --git a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootList.cs b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootList.cs index f7a2f4e952..c8a677c450 100644 --- a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootList.cs +++ b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootList.cs @@ -22,6 +22,7 @@ public static RootList Get(int itemsCount) { return DataPortal.Fetch(new SingleCriteria(itemsCount)); } + [Fetch] private void DataPortal_Fetch(SingleCriteria criteria) { for (int i = 0; i < criteria.Value; i++) diff --git a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithChildren.cs b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithChildren.cs index 28f0c655b4..29d1432ee6 100644 --- a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithChildren.cs +++ b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithChildren.cs @@ -37,6 +37,7 @@ public static RootWithChildren Get(int childCount) { return DataPortal.Fetch(new SingleCriteria(childCount)); } + [Fetch] private void DataPortal_Fetch(SingleCriteria criteria) { using (BypassPropertyChecks) @@ -53,6 +54,7 @@ public class ChildList : Csla.BusinessListBase private ChildList() {} + [FetchChild] private void Child_Fetch(int childCount) { for (int i = 0; i < childCount; i++) diff --git a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithValidation.cs b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithValidation.cs index b6b6fc7aeb..dd2a3bf2e4 100644 --- a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithValidation.cs +++ b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/RootWithValidation.cs @@ -51,6 +51,7 @@ public static RootWithValidation NewRootWithValidation() { return DataPortal.Create(); } + [Create] protected override void DataPortal_Create() { diff --git a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/SingleRoot.cs b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/SingleRoot.cs index 0dba559baf..141846d82f 100644 --- a/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/SingleRoot.cs +++ b/Source/tests/Csla.Web.Mvc.Test/ModelBinderTest/SingleRoot.cs @@ -41,10 +41,12 @@ public static SingleRoot GetSingleRoot(int id) { return DataPortal.Fetch(new SingleCriteria(id)); } + [Create] protected override void DataPortal_Create() { ID = 1; } + [Fetch] private void DataPortal_Fetch(SingleCriteria criteria) { using (BypassPropertyChecks) diff --git a/Source/tests/Csla.test/AppContext/SimpleRoot.cs b/Source/tests/Csla.test/AppContext/SimpleRoot.cs index 9f6e3e500b..22a81b846a 100644 --- a/Source/tests/Csla.test/AppContext/SimpleRoot.cs +++ b/Source/tests/Csla.test/AppContext/SimpleRoot.cs @@ -68,6 +68,7 @@ public Criteria(string Data) /// Handles new DataPortal Create calls /// /// + [Create] private void DataPortal_Create(object criteria) { Criteria crit = criteria as Criteria; @@ -80,6 +81,7 @@ private void DataPortal_Create(object criteria) /// Handles DataPortal fetch calls /// /// + [Fetch] protected void DataPortal_Fetch(object criteria) { Criteria crit = criteria as Criteria; diff --git a/Source/tests/Csla.test/Basic/CollectionTests.cs b/Source/tests/Csla.test/Basic/CollectionTests.cs index 07d90b6c8b..1c54630167 100644 --- a/Source/tests/Csla.test/Basic/CollectionTests.cs +++ b/Source/tests/Csla.test/Basic/CollectionTests.cs @@ -62,6 +62,7 @@ private void Create() { } + [Fetch] private void DataPortal_Fetch([Inject] IChildDataPortal childDataPortal) { Add(childDataPortal.FetchChild(123)); diff --git a/Source/tests/Csla.test/Basic/GenRootBase.cs b/Source/tests/Csla.test/Basic/GenRootBase.cs index 3b6c363dbe..bb89fb09e7 100644 --- a/Source/tests/Csla.test/Basic/GenRootBase.cs +++ b/Source/tests/Csla.test/Basic/GenRootBase.cs @@ -47,6 +47,7 @@ public Criteria(string data) } } + [Create] private void DataPortal_Create(object criteria) { Criteria crit = (Criteria)(criteria); @@ -54,6 +55,7 @@ private void DataPortal_Create(object criteria) TestResults.Add("GenRoot", "Created"); } + [Fetch] protected void DataPortal_Fetch(object criteria) { var crit = (Criteria)(criteria); diff --git a/Source/tests/Csla.test/Basic/NameValueListObj.cs b/Source/tests/Csla.test/Basic/NameValueListObj.cs index 78e86f94a3..96e7e28855 100644 --- a/Source/tests/Csla.test/Basic/NameValueListObj.cs +++ b/Source/tests/Csla.test/Basic/NameValueListObj.cs @@ -13,6 +13,7 @@ public class NameValueListObj : NameValueListBase { #region "Data Access" + [Fetch] protected void DataPortal_Fetch() { TestResults.Reinitialise(); diff --git a/Source/tests/Csla.test/Basic/ReadOnlyList.cs b/Source/tests/Csla.test/Basic/ReadOnlyList.cs index eb684acd85..9e40af5109 100644 --- a/Source/tests/Csla.test/Basic/ReadOnlyList.cs +++ b/Source/tests/Csla.test/Basic/ReadOnlyList.cs @@ -36,6 +36,7 @@ public static ReadOnlyList GetReadOnlyList() #region "Data access" + [Fetch] protected override void DataPortal_Fetch(object criteria) { TestResults.Reinitialise(); diff --git a/Source/tests/Csla.test/BasicModern/Child.cs b/Source/tests/Csla.test/BasicModern/Child.cs index 13f0049e9a..358dc78a64 100644 --- a/Source/tests/Csla.test/BasicModern/Child.cs +++ b/Source/tests/Csla.test/BasicModern/Child.cs @@ -20,6 +20,7 @@ public string Name set { SetProperty(NameProperty, value); } } + [CreateChild] private void Child_Create(int id, string name) { using (BypassPropertyChecks) @@ -29,6 +30,7 @@ private void Child_Create(int id, string name) } } + [FetchChild] private void Child_Fetch(int id, string name) { using (BypassPropertyChecks) @@ -38,12 +40,15 @@ private void Child_Fetch(int id, string name) } } + [InsertChild] private void Child_Insert() { } + [UpdateChild] private void Child_Update() { } + [DeleteSelfChild] private void Child_DeleteSelf() { } } diff --git a/Source/tests/Csla.test/BasicModern/Root.cs b/Source/tests/Csla.test/BasicModern/Root.cs index 8dbdd92783..7011fdb55d 100644 --- a/Source/tests/Csla.test/BasicModern/Root.cs +++ b/Source/tests/Csla.test/BasicModern/Root.cs @@ -39,6 +39,7 @@ protected void DataPortal_Create([Inject] IChildDataPortal childDataP BusinessRules.CheckRules(); } + [Fetch] private void DataPortal_Fetch(int id, [Inject] IChildDataPortal childDataPortal) { Children = childDataPortal.CreateChild(); diff --git a/Source/tests/Csla.test/ChildChanged/OptimizeChildChangedTests.cs b/Source/tests/Csla.test/ChildChanged/OptimizeChildChangedTests.cs index de154ca7f9..9d433a9259 100644 --- a/Source/tests/Csla.test/ChildChanged/OptimizeChildChangedTests.cs +++ b/Source/tests/Csla.test/ChildChanged/OptimizeChildChangedTests.cs @@ -75,6 +75,7 @@ protected override void AddBusinessRules() } + [Fetch] private void DataPortal_Fetch([Inject] IChildDataPortal simpleBOPortal, [Inject] IChildDataPortal simpleBOListPortal) { Depth = 0; @@ -86,6 +87,7 @@ private void DataPortal_Fetch([Inject] IChildDataPortal simpleBOPortal } + [FetchChild] private void Child_Fetch(int depth, [Inject] IChildDataPortal simpleBOPortal, [Inject] IChildDataPortal simpleBOListPortal) { Depth = depth; @@ -121,6 +123,7 @@ public class SimpleBOList : BusinessListBase public int Depth { get; private set; } public int UniqueID = NextUniqueID(); + [FetchChild] private void Child_Fetch(int depth, [Inject] IChildDataPortal childDataPortal) { Depth = depth; diff --git a/Source/tests/Csla.test/DataPortal/DataPortalTests.cs b/Source/tests/Csla.test/DataPortal/DataPortalTests.cs index 541b3ee22c..ddbf7de9e7 100644 --- a/Source/tests/Csla.test/DataPortal/DataPortalTests.cs +++ b/Source/tests/Csla.test/DataPortal/DataPortalTests.cs @@ -361,6 +361,7 @@ protected void DataPortal_Create() MarkBusy(); } + [Fetch] private void DataPortal_Fetch() { MarkBusy(); diff --git a/Source/tests/Csla.test/DataPortal/DpRoot.cs b/Source/tests/Csla.test/DataPortal/DpRoot.cs index e55392aa3c..d8390ee1b5 100644 --- a/Source/tests/Csla.test/DataPortal/DpRoot.cs +++ b/Source/tests/Csla.test/DataPortal/DpRoot.cs @@ -83,6 +83,7 @@ public DpRoot CloneThis() #region "DataPortal" + [Create] private void DataPortal_Create(object criteria) { Criteria crit = (Criteria)(criteria); @@ -90,6 +91,7 @@ private void DataPortal_Create(object criteria) Data = crit.Data; } + [Fetch] protected void DataPortal_Fetch(object criteria) { Criteria crit = (Criteria)(criteria); diff --git a/Source/tests/Csla.test/DataPortal/InterceptorTests.cs b/Source/tests/Csla.test/DataPortal/InterceptorTests.cs index 02f526f934..6330d7acb8 100644 --- a/Source/tests/Csla.test/DataPortal/InterceptorTests.cs +++ b/Source/tests/Csla.test/DataPortal/InterceptorTests.cs @@ -178,16 +178,19 @@ public string Name set { SetProperty(NameProperty, value); } } + [Create] private void DataPortal_Create(string name) { Fetch(name); } + [Fetch] private void DataPortal_Fetch(string name) { Fetch(name); } + [FetchChild] private void Child_Fetch(string name) { Fetch(name); @@ -209,6 +212,7 @@ protected void DataPortal_Update() { } + [UpdateChild] private void Child_Update() { } @@ -217,6 +221,7 @@ private void Child_Update() [Serializable] public class InitializeListRoot : BusinessListBase { + [Fetch] private void DataPortal_Fetch([Inject] IChildDataPortal childDataPortal) { using (SuppressListChangedEvents) diff --git a/Source/tests/Csla.test/DataPortal/Legacy.cs b/Source/tests/Csla.test/DataPortal/Legacy.cs index 371f89e395..0cdf50ac06 100644 --- a/Source/tests/Csla.test/DataPortal/Legacy.cs +++ b/Source/tests/Csla.test/DataPortal/Legacy.cs @@ -42,6 +42,7 @@ protected void DataPortal_Create() TestResults.Add("Legacy", "Created"); } + [Fetch] protected void DataPortal_Fetch(object criteria) { _id = ((Criteria)criteria).Id; diff --git a/Source/tests/Csla.test/DataPortal/LegacySplitBase.cs b/Source/tests/Csla.test/DataPortal/LegacySplitBase.cs index 11ac79b87a..2c35fb9087 100644 --- a/Source/tests/Csla.test/DataPortal/LegacySplitBase.cs +++ b/Source/tests/Csla.test/DataPortal/LegacySplitBase.cs @@ -43,6 +43,7 @@ protected void DataPortal_Create() TestResults.Add("LegacySplit", "Created"); } + [Fetch] protected void DataPortal_Fetch(object criteria) { _id = ((Criteria)criteria).Id; diff --git a/Source/tests/Csla.test/DataPortal/SingleOverload.cs b/Source/tests/Csla.test/DataPortal/SingleOverload.cs index 5f4e97a3fc..4047cd49a7 100644 --- a/Source/tests/Csla.test/DataPortal/SingleOverload.cs +++ b/Source/tests/Csla.test/DataPortal/SingleOverload.cs @@ -95,6 +95,7 @@ private void DataPortal_Create(OtherCriteria criteria) BusinessRules.CheckRules(); } + [Fetch] private void DataPortal_Fetch(Criteria criteria) { using (BypassPropertyChecks) @@ -102,6 +103,7 @@ private void DataPortal_Fetch(Criteria criteria) TestResults.Reinitialise(); TestResults.Add("SingleOverload", "Fetched"); } + [Fetch] private void DataPortal_Fetch(OtherCriteria criteria) { using (BypassPropertyChecks) diff --git a/Source/tests/Csla.test/DataPortal/SplitBase.cs b/Source/tests/Csla.test/DataPortal/SplitBase.cs index 1699a3cfde..aaba16efa2 100644 --- a/Source/tests/Csla.test/DataPortal/SplitBase.cs +++ b/Source/tests/Csla.test/DataPortal/SplitBase.cs @@ -63,6 +63,7 @@ protected void DataPortal_Create() TestResults.Add("Split", "Created"); } + [Fetch] private void DataPortal_Fetch(Criteria criteria) { _id = criteria.Id; diff --git a/Source/tests/Csla.test/DataPortal/SplitOverloadBase.cs b/Source/tests/Csla.test/DataPortal/SplitOverloadBase.cs index ba161bf2cc..ed4532d077 100644 --- a/Source/tests/Csla.test/DataPortal/SplitOverloadBase.cs +++ b/Source/tests/Csla.test/DataPortal/SplitOverloadBase.cs @@ -84,12 +84,14 @@ private void DataPortal_Create(Criteria1 criteria) BusinessRules.CheckRules(); } + [Fetch] private void DataPortal_Fetch(Criteria criteria) { _id = criteria.Id; TestResults.Reinitialise(); TestResults.Add("SplitOverload", "Fetched"); } + [Fetch] private void DataPortal_Fetch(Criteria1 criteria) { _id = criteria.Id; diff --git a/Source/tests/Csla.test/DataPortal/StronglyTypedDP.cs b/Source/tests/Csla.test/DataPortal/StronglyTypedDP.cs index 14e3ede355..1522aec3b8 100644 --- a/Source/tests/Csla.test/DataPortal/StronglyTypedDP.cs +++ b/Source/tests/Csla.test/DataPortal/StronglyTypedDP.cs @@ -61,6 +61,7 @@ public static void DeleteStronglyTypedDP(int id, IDataPortal da dataPortal.Delete(new Criteria(id)); } + [Create] protected void DataPortal_Create(Criteria criteria) { using (BypassPropertyChecks) @@ -71,6 +72,7 @@ protected void DataPortal_Create(Criteria criteria) TestResults.Add("StronglyTypedDP", "Created"); } + [Fetch] protected void DataPortal_Fetch(Criteria criteria) { using (BypassPropertyChecks) diff --git a/Source/tests/Csla.test/DataPortal/TransactionalRoot.cs b/Source/tests/Csla.test/DataPortal/TransactionalRoot.cs index 7c0e2ed42f..6c89cb8bbf 100644 --- a/Source/tests/Csla.test/DataPortal/TransactionalRoot.cs +++ b/Source/tests/Csla.test/DataPortal/TransactionalRoot.cs @@ -101,6 +101,7 @@ protected void DataPortal_Create() Console.WriteLine("DataPortal_Create"); } + [Fetch] protected void DataPortal_Fetch(object criteria) { Criteria crit = (Criteria)(criteria); diff --git a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/Customer.cs b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/Customer.cs index 18f10a6d4b..c90c4114f0 100644 --- a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/Customer.cs +++ b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/Customer.cs @@ -204,6 +204,7 @@ protected override void OnSetState(Csla.Serialization.Mobile.SerializationInfo i } } + [Fetch] protected void DataPortal_Fetch(int criteria, [Inject] IChildDataPortal childDataPortal) { LoadProperty(IdProperty, criteria); @@ -217,6 +218,7 @@ protected void DataPortal_Fetch(int criteria, [Inject] IChildDataPortal childDataPortal) { LoadProperty(IdProperty, criteria); diff --git a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContact.cs b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContact.cs index f76b76d086..8d8342c042 100644 --- a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContact.cs +++ b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContact.cs @@ -107,11 +107,13 @@ protected override void AddBusinessRules() BusinessRules.AddRule(new Csla.Rules.CommonRules.MaxLength(LastNameProperty, 50)); } + [DeleteSelfChild] protected void Child_DeleteSelf() { TestResults.Add("CustomerContactDelete", "Deleted Customer Contact" + GetProperty(FirstNameProperty) + ", " + GetProperty(LastNameProperty)); } + [InsertChild] protected void Child_Insert() { TestResults.Add("CustomerContactInsert", "Inserted Customer Contact" + GetProperty(FirstNameProperty) + ", " + GetProperty(LastNameProperty)); @@ -119,6 +121,7 @@ protected void Child_Insert() Customer grandParent = parent.MyParent; LoadProperty(ParentNameProperty, grandParent.Name); } + [UpdateChild] protected void Child_Update() { TestResults.Add("CustomerContactUpdate", "Updated Customer Contact" + GetProperty(FirstNameProperty) + ", " + GetProperty(LastNameProperty)); @@ -127,6 +130,7 @@ protected void Child_Update() LoadProperty(ParentNameProperty,grandParent.Name); } + [FetchChild] private void Child_Fetch(int customerId, int id, string firstName, string lastName, DateTime birthday) { LoadProperty(CustomerIdProperty, customerId); diff --git a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContactList.cs b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContactList.cs index 18b06631ea..e3b78c8757 100644 --- a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContactList.cs +++ b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerContactList.cs @@ -21,6 +21,7 @@ public Customer MyParent private CustomerContactList() { } + [FetchChild] private void Child_Fetch(int customerID, [Inject] IChildDataPortal childDataPortal) { RaiseListChangedEvents = false; diff --git a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerList.cs b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerList.cs index fcf3ac380b..1078c83ebf 100644 --- a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerList.cs +++ b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerList.cs @@ -16,6 +16,7 @@ public class CustomerList : BusinessListBase { private CustomerList() { } + [Fetch] protected void DataPortal_Fetch([Inject] IDataPortal customerDataPortal) { int maxCustomer = (new Random()).Next(3, 10); diff --git a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithError.cs b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithError.cs index 0c4b7f8f47..21f8a0a463 100644 --- a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithError.cs +++ b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithError.cs @@ -125,6 +125,7 @@ internal static CustomerWithError GetCustomerWithError(int CustomerWithErrorID) return newCustomerWithError; } + [Fetch] protected void DataPortal_Fetch(int criteria) { LoadProperty(IdProperty, criteria); @@ -134,6 +135,7 @@ protected void DataPortal_Fetch(int criteria) throw new ApplicationException("Test for Silverlight DataSource Error!"); } + [Create] protected void DataPortal_Create(int criteria) { LoadProperty(IdProperty, criteria); diff --git a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithErrorList.cs b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithErrorList.cs index f697bfd930..b31849f32f 100644 --- a/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithErrorList.cs +++ b/Source/tests/Csla.test/Fakes/Server/CslaDataProvider/CustomerWithErrorList.cs @@ -16,6 +16,7 @@ public class CustomerWithErrorList : BusinessListBase(IdProperty, criteria); diff --git a/Source/tests/Csla.test/Fakes/Server/ReadOnlyTests/ReadOnlyPersonList.cs b/Source/tests/Csla.test/Fakes/Server/ReadOnlyTests/ReadOnlyPersonList.cs index c4dae4d24a..6f5b881c62 100644 --- a/Source/tests/Csla.test/Fakes/Server/ReadOnlyTests/ReadOnlyPersonList.cs +++ b/Source/tests/Csla.test/Fakes/Server/ReadOnlyTests/ReadOnlyPersonList.cs @@ -11,6 +11,7 @@ namespace Csla.Testing.Business.ReadOnlyTest [Serializable] public class ReadOnlyPersonList : ReadOnlyBindingListBase { + [Fetch] private void DataPortal_Fetch() { RaiseListChangedEvents = false; diff --git a/Source/tests/Csla.test/FieldManager/Child.cs b/Source/tests/Csla.test/FieldManager/Child.cs index 776c6c635e..9dac0d401a 100644 --- a/Source/tests/Csla.test/FieldManager/Child.cs +++ b/Source/tests/Csla.test/FieldManager/Child.cs @@ -51,6 +51,7 @@ public void DeleteChild() MarkDeleted(); } + [CreateChild] protected override void Child_Create() { LoadProperty(StatusProperty, "Created"); diff --git a/Source/tests/Csla.test/FieldManager/ChildList.cs b/Source/tests/Csla.test/FieldManager/ChildList.cs index d0d7d2ced6..5d5df885c4 100644 --- a/Source/tests/Csla.test/FieldManager/ChildList.cs +++ b/Source/tests/Csla.test/FieldManager/ChildList.cs @@ -34,6 +34,7 @@ protected void Child_Fetch() Status = "Fetched"; } + [UpdateChild] protected override void Child_Update(params object[] p) { base.Child_Update(); diff --git a/Source/tests/Csla.test/GraphMerge/TestClasses.cs b/Source/tests/Csla.test/GraphMerge/TestClasses.cs index b142749627..729d30b87d 100644 --- a/Source/tests/Csla.test/GraphMerge/TestClasses.cs +++ b/Source/tests/Csla.test/GraphMerge/TestClasses.cs @@ -138,6 +138,7 @@ private void DataPortal_Create() [Serializable] public class FooDynamicList : DynamicListBase { + [Create] private void DataPortal_Create() { } } @@ -145,6 +146,7 @@ private void DataPortal_Create() [Serializable] public class FooDynamicBindingList : DynamicBindingListBase { + [Create] private void DataPortal_Create() { } } diff --git a/Source/tests/Csla.test/GraphMergeAsync/TestClasses.cs b/Source/tests/Csla.test/GraphMergeAsync/TestClasses.cs index 5aea050c52..d85697dd57 100644 --- a/Source/tests/Csla.test/GraphMergeAsync/TestClasses.cs +++ b/Source/tests/Csla.test/GraphMergeAsync/TestClasses.cs @@ -138,6 +138,7 @@ private void DataPortal_Create() [Serializable] public class FooDynamicList : DynamicListBase { + [Create] private void DataPortal_Create() { } } @@ -145,6 +146,7 @@ private void DataPortal_Create() [Serializable] public class FooDynamicBindingList : DynamicBindingListBase { + [Create] private void DataPortal_Create() { } } diff --git a/Source/tests/Csla.test/IO/DPMethodOverloadTests.cs b/Source/tests/Csla.test/IO/DPMethodOverloadTests.cs index a3277c2315..4b2bb72dfe 100644 --- a/Source/tests/Csla.test/IO/DPMethodOverloadTests.cs +++ b/Source/tests/Csla.test/IO/DPMethodOverloadTests.cs @@ -202,11 +202,13 @@ private void DataPortal_Create(Criteria criteria) BusinessRules.CheckRules(); } + [Fetch] private void DataPortal_Fetch() { TestResults.Add("Fetch", "No criteria"); } + [Fetch] protected void DataPortal_Fetch(object criteria) { if (criteria == null) @@ -215,6 +217,7 @@ protected void DataPortal_Fetch(object criteria) TestResults.Add("Fetch", "Other criteria"); } + [Fetch] private void DataPortal_Fetch(Criteria criteria) { TestResults.Add("Fetch", "Criteria"); diff --git a/Source/tests/Csla.test/Linq/LinqObservableCollectionTest.cs b/Source/tests/Csla.test/Linq/LinqObservableCollectionTest.cs index 4ffc7f785f..15a7a320ac 100644 --- a/Source/tests/Csla.test/Linq/LinqObservableCollectionTest.cs +++ b/Source/tests/Csla.test/Linq/LinqObservableCollectionTest.cs @@ -487,6 +487,7 @@ public string Name set { SetProperty(NameProperty, value); } } + [FetchChild] private void Child_Fetch(int id, string name) { using (BypassPropertyChecks) diff --git a/Source/tests/Csla.test/Nullable/NullableObject.cs b/Source/tests/Csla.test/Nullable/NullableObject.cs index 4e7e74b4ed..23cf00bc7a 100644 --- a/Source/tests/Csla.test/Nullable/NullableObject.cs +++ b/Source/tests/Csla.test/Nullable/NullableObject.cs @@ -81,6 +81,7 @@ private void DataPortal_Create(Criteria criteria) TestResults.Add("NullableObject", "Created"); } + [Fetch] protected void DataPortal_Fetch(object criteria) { Criteria crit = (Criteria)(criteria); diff --git a/Source/tests/Csla.test/RollBack/RollbackRoot.cs b/Source/tests/Csla.test/RollBack/RollbackRoot.cs index e8c6a25c4d..2e8f7d559a 100644 --- a/Source/tests/Csla.test/RollBack/RollbackRoot.cs +++ b/Source/tests/Csla.test/RollBack/RollbackRoot.cs @@ -66,12 +66,14 @@ public static void DeleteRoot(string data, IDataPortal dataPortal) dataPortal.Delete(new Criteria(data)); } + [Create] private void DataPortal_Create(Criteria criteria) { Data = criteria.Data; TestResults.AddOrOverwrite("Root", "Created"); } + [Fetch] protected void DataPortal_Fetch(Criteria criteria) { Data = criteria.Data; diff --git a/Source/tests/Csla.test/ValidationRules/HasRulesManager2.partial.cs b/Source/tests/Csla.test/ValidationRules/HasRulesManager2.partial.cs index 1f249ff15f..d7a322e3a9 100644 --- a/Source/tests/Csla.test/ValidationRules/HasRulesManager2.partial.cs +++ b/Source/tests/Csla.test/ValidationRules/HasRulesManager2.partial.cs @@ -26,6 +26,7 @@ private void DataPortal_Create(object criteria) BusinessRules.CheckRules(); } + [Fetch] protected void DataPortal_Fetch(object criteria) { Criteria crit = (Criteria)(criteria); diff --git a/Source/tests/Csla.test/ValidationRules/RuleBaseClassesRoot.cs b/Source/tests/Csla.test/ValidationRules/RuleBaseClassesRoot.cs index 1f368e7497..b4210b5919 100644 --- a/Source/tests/Csla.test/ValidationRules/RuleBaseClassesRoot.cs +++ b/Source/tests/Csla.test/ValidationRules/RuleBaseClassesRoot.cs @@ -121,6 +121,7 @@ public static RuleBaseClassesRoot NewEditableRoot(IDataPortal { + [Fetch] private void DataPortal_Fetch(Criteria criteria) { } @@ -72,6 +73,7 @@ public class Criteria : ReadOnlyBase; [Serializable] public class PersonEdit : PersonEditBase { + [Create] private new void DataPortal_Create() { BusinessRules.CheckRules(); @@ -81,6 +83,7 @@ public class PersonEdit : PersonEditBase [Serializable] public class PersonEditBase : BusinessBase> { + [Create] protected void DataPortal_Create() { } diff --git a/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs b/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs index e81f0c7b0a..cf664fcc4c 100644 --- a/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs +++ b/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs @@ -257,7 +257,7 @@ public void FindMethodAmbiguousCriteria() } [TestMethod] - public void FindChildLegacyUpdate() + public void FindChildNoParamsUpdate() { var obj = new BasicChild(); var method = _systemUnderTest.FindDataPortalMethod(obj, null); @@ -513,38 +513,21 @@ await FluentActions.Invoking(async () => await portal.CreateAsync()) } [TestMethod] - public void FindLegacyMethod_DefaultEnabled_FindsLegacyFallback() + public void FindMethod_PrivateBaseWithLegacyNamedMethod_FindsAttributedMethod() { - // With default settings (legacy enabled), a subclass of a base with private [Execute] - // should fall back to the legacy DataPortal_Execute method - var method = _systemUnderTest.FindDataPortalMethod(typeof(LegacyFallbackConcrete), null); + // A subclass of a base with private [Execute] and an unattributed + // DataPortal_Execute must find the attributed Execute method (#4595) + var method = _systemUnderTest.FindDataPortalMethod(typeof(LegacyNamedConcrete), null); method.Should().NotBeNull(); - method.MethodInfo.Name.Should().Be("DataPortal_Execute"); + method.MethodInfo.Name.Should().Be("Execute"); } [TestMethod] - public void FindLegacyMethod_Disabled_FindsAttributedMethodInstead() + public void FindLegacyNamedMethodWithoutAttribute_NotFound() { - // With legacy disabled, a subclass of a base with private [Execute] - // should find the attributed Execute method via recursion, not the legacy DataPortal_Execute - using var testHost = CslaTestHost.Create(t => t.ConfigureCsla(o => o.DataPortal(dp => dp.UseLegacyOperationMethods = false))); - var caller = testHost.ApplicationContext.CreateInstanceDI(); - - var found = caller.TryFindDataPortalMethod(typeof(LegacyDisabledConcrete), null, out var method); - - found.Should().BeTrue(); - method!.MethodInfo.Name.Should().Be("Execute"); - } - - [TestMethod] - public void FindLegacyOnlyMethod_Disabled_NoFallback() - { - // A class using only DataPortal_Create (no attributes) should not be found when legacy is disabled - using var testHost = CslaTestHost.Create(t => t.ConfigureCsla(o => o.DataPortal(dp => dp.UseLegacyOperationMethods = false))); - var caller = testHost.ApplicationContext.CreateInstanceDI(); - - var found = caller.TryFindDataPortalMethod(typeof(LegacyOnlyCreate), null, out var method); + // A DataPortal_Create method without an operation attribute is never used + var found = _systemUnderTest.TryFindDataPortalMethod(typeof(LegacyOnlyCreate), null, out var method); found.Should().BeFalse(); method.Should().BeNull(); @@ -798,6 +781,7 @@ private void Create(int id, [Inject] ICloneable x, string foo) { } public class BasicChild : BusinessBase { + [UpdateChild] private void Child_Update() { // nada @@ -806,6 +790,7 @@ private void Child_Update() public class ParamsChild : BusinessBase { + [UpdateChild] private void Child_Update(params object[] parameters) { // nada @@ -866,6 +851,7 @@ private void Fetch(List values) public class Issue2109List : ReadOnlyListBase { + [Fetch] private void DataPortal_Fetch(ICriteriaBase criteria, [Inject] IDataPortal dp) { using (LoadListMode) @@ -874,6 +860,7 @@ private void DataPortal_Fetch(ICriteriaBase criteria, [Inject] IDataPortal criteria, [Inject] IDataPortal dp) { using (LoadListMode) @@ -892,6 +879,7 @@ public string Name set => SetProperty(NameProperty, value); } + [Fetch] private void DataPortal_Fetch(string name) { using (BypassPropertyChecks) @@ -914,6 +902,7 @@ public class Issue2287List : Issue2287ListBase public class Issue2287ListBase : Csla.BusinessBindingListBase { + [Fetch] private void DataPortal_Fetch(Criteria criteria) { } @@ -925,6 +914,7 @@ public class Criteria : Csla.CriteriaBase public class Issue2287Edit : Issue2287EditBase { + [Create] private new void DataPortal_Create() { BusinessRules.CheckRules(); @@ -933,6 +923,7 @@ public class Issue2287Edit : Issue2287EditBase public class Issue2287EditBase : BusinessBase> { + [Create] protected void DataPortal_Create() { } @@ -1051,22 +1042,7 @@ private void Create([Inject] IOptionalService requiredService) } } - public class LegacyFallbackBase : CommandBase - where T : CommandBase - { - [Execute] - private void Execute() - { } - - protected virtual void DataPortal_Execute() - { } - } - - public class LegacyFallbackConcrete : LegacyFallbackBase - { - } - - public class LegacyDisabledBase : CommandBase + public class LegacyNamedBase : CommandBase where T : CommandBase { [Execute] @@ -1077,7 +1053,7 @@ protected virtual void DataPortal_Execute() { } } - public class LegacyDisabledConcrete : LegacyDisabledBase + public class LegacyNamedConcrete : LegacyNamedBase { } diff --git a/docs/Abstractions-in-CSLA.md b/docs/Abstractions-in-CSLA.md index 95800dc949..1101e81fa3 100644 --- a/docs/Abstractions-in-CSLA.md +++ b/docs/Abstractions-in-CSLA.md @@ -326,10 +326,11 @@ public interface IDependentPerson internal sealed class DependentPerson : BusinessBaseScopeCore, IDependentPerson { - protected override void DataPortal_Create() + [Create] + private void Create() { - base.DataPortal_Create(); - this.logger.Log("DataPortal_Create on DependentPerson invoked."); + this.BusinessRules.CheckRules(); + this.logger.Log("Create on DependentPerson invoked."); } public static readonly PropertyInfo AgeProperty = @@ -409,7 +410,7 @@ As you can see, the dependency automatically got injected into the DependentPers To close out this article, I’ll demonstrate how you write unit tests for BOs to ensure dependencies are used correctly, and how you can use a CSLA BO as an injected dependency in the UI and write tests for that too. -The first half is to write a test for DependentPerson. This test will ensure that the logger is called in DataPortal_Create(): +The first half is to write a test for DependentPerson. This test will ensure that the logger is called in the `[Create]` operation method: ```c# public void Create() diff --git a/docs/Data-Access.md b/docs/Data-Access.md index d84360e168..08bc82e8d6 100644 --- a/docs/Data-Access.md +++ b/docs/Data-Access.md @@ -4,10 +4,10 @@ There are actually four data access models you can use with CSLA: -1. Encapsulated invoke: DataPortal_XYZ to abstract DAL +1. Encapsulated invoke: data portal operation methods (such as `[Fetch]`) to abstract DAL 2. Factory implementation: ObjectFactory direct to data source 3. Factory invoke: ObjectFactory to abstract DAL -4. Encapsulated implementation: DataPortal_XYZ direct to data source +4. Encapsulated implementation: data portal operation methods (such as `[Fetch]`) direct to data source These are listed in my general order of preference. diff --git a/docs/Upgrading to CSLA 11.md b/docs/Upgrading to CSLA 11.md new file mode 100644 index 0000000000..4856e94762 --- /dev/null +++ b/docs/Upgrading to CSLA 11.md @@ -0,0 +1,79 @@ +# Upgrading to CSLA 11 + +CSLA 11 is a major release and so there are a number of breaking changes. + +In this document I'll try to highlight the most common changes required when upgrading your codebase from CSLA 10 to CSLA 11. + +If you are upgrading from a version of CSLA prior to 10, you should review the [Upgrading to CSLA 10](https://github.com/MarimerLLC/csla/blob/main/docs/Upgrading%20to%20CSLA%2010.md) document, as most of its contents are relevant. This document only covers the changes from CSLA 10 to CSLA 11. + +## Platform Support + +CSLA 11 supports .NET 10 and .NET 11, and continues to support .NET Framework 4.6.2 and later. CSLA 11 no longer targets .NET 8 or .NET 9. + +## Legacy Data Portal Operation Method Names Removed + +In earlier versions, if the data portal couldn't find a method with an operation attribute (such as `[Fetch]`), it fell back to looking for a method by name (such as `DataPortal_Fetch` or `Child_Update`). This fallback was controlled by the `DataPortalOptions.UseLegacyOperationMethods` option. + +In CSLA 11 this fallback is removed. The data portal only invokes methods that have an operation attribute. The `UseLegacyOperationMethods` option is also removed. + +If your business classes have operation methods with no attribute, add the matching attribute: + +| Legacy method name | Attribute | +|---|---| +| `DataPortal_Create` | `[Create]` | +| `DataPortal_Fetch` | `[Fetch]` | +| `DataPortal_Insert` | `[Insert]` | +| `DataPortal_Update` | `[Update]` | +| `DataPortal_Delete` | `[Delete]` | +| `DataPortal_DeleteSelf` | `[DeleteSelf]` | +| `DataPortal_Execute` | `[Execute]` | +| `Child_Create` | `[CreateChild]` | +| `Child_Fetch` | `[FetchChild]` | +| `Child_Insert` | `[InsertChild]` | +| `Child_Update` | `[UpdateChild]` | +| `Child_DeleteSelf` | `[DeleteSelfChild]` | +| `Child_Execute` | `[ExecuteChild]` | + +The same applies to the `Async` variants of these names (for example `DataPortal_FetchAsync`). + +Before: + +```csharp +[Serializable] +public class Customer : BusinessBase +{ + private void DataPortal_Fetch(int id) + { + // ... + } +} +``` + +After: + +```csharp +[Serializable] +public class Customer : BusinessBase +{ + [Fetch] + private void DataPortal_Fetch(int id) + { + // ... + } +} +``` + +Once a method has an attribute, you can rename it to anything you like (for example `Fetch`). + +A method with a legacy name and no attribute is never called. Instead, the data portal throws an exception at runtime because it can't find a matching operation method. + +### Finding affected methods + +The `CSLA0014` analyzer finds methods that use a legacy operation name but have no operation attribute. In CSLA 11 it reports a warning instead of an informational message. The analyzer's code fix adds the correct attribute for you. See [CSLA0014](analyzers/CSLA0014-DoesOperationHaveAttributeAnalyzer.md). + + diff --git a/docs/analyzers/CSLA0002-IsOperationMethodPublicAnalyzer.md b/docs/analyzers/CSLA0002-IsOperationMethodPublicAnalyzer.md index 7c4977cac0..f70624aaf3 100644 --- a/docs/analyzers/CSLA0002-IsOperationMethodPublicAnalyzer.md +++ b/docs/analyzers/CSLA0002-IsOperationMethodPublicAnalyzer.md @@ -12,7 +12,8 @@ using System; public class Customer : BusinessBase { - public void DataPortal_Fetch(int id) { } + [Fetch] + public void Fetch(int id) { } } ``` diff --git a/docs/analyzers/CSLA0009-IsOperationMethodPublicAnalyzer.md b/docs/analyzers/CSLA0009-IsOperationMethodPublicAnalyzer.md index c2c72fb06b..3167f4f9a7 100644 --- a/docs/analyzers/CSLA0009-IsOperationMethodPublicAnalyzer.md +++ b/docs/analyzers/CSLA0009-IsOperationMethodPublicAnalyzer.md @@ -10,7 +10,8 @@ using Csla; public inteface ICustomer : IBusinessObject { - void DataPortal_Fetch(); + [Fetch] + void Fetch(); } ``` diff --git a/docs/analyzers/CSLA0010-FindOperationsWithNonSerializableArgumentsAnalyzer.md b/docs/analyzers/CSLA0010-FindOperationsWithNonSerializableArgumentsAnalyzer.md index c06728933b..c00db33252 100644 --- a/docs/analyzers/CSLA0010-FindOperationsWithNonSerializableArgumentsAnalyzer.md +++ b/docs/analyzers/CSLA0010-FindOperationsWithNonSerializableArgumentsAnalyzer.md @@ -14,7 +14,8 @@ public class NotSerializable { } public class Customer : BusinessBase { - private void DataPortal_Fetch(NotSerializable a); + [Fetch] + private void Fetch(NotSerializable a) { } } ``` diff --git a/docs/analyzers/CSLA0012-FindOperationsWithIncorrectReturnTypesAnalyzer.md b/docs/analyzers/CSLA0012-FindOperationsWithIncorrectReturnTypesAnalyzer.md index 8054918ee0..f3f862a580 100644 --- a/docs/analyzers/CSLA0012-FindOperationsWithIncorrectReturnTypesAnalyzer.md +++ b/docs/analyzers/CSLA0012-FindOperationsWithIncorrectReturnTypesAnalyzer.md @@ -11,7 +11,8 @@ using System; public class Customer : BusinessBase { - private string DataPortal_Fetch(int id) => string.Empty; + [Fetch] + private string Fetch(int id) => string.Empty; } ``` diff --git a/docs/analyzers/CSLA0013-DoesChildOperationHaveRunLocalAnalyzer.md b/docs/analyzers/CSLA0013-DoesChildOperationHaveRunLocalAnalyzer.md index 15e3231dcf..6c631d5020 100644 --- a/docs/analyzers/CSLA0013-DoesChildOperationHaveRunLocalAnalyzer.md +++ b/docs/analyzers/CSLA0013-DoesChildOperationHaveRunLocalAnalyzer.md @@ -11,8 +11,9 @@ using System; public class Customer : BusinessBase { + [FetchChild] [RunLocal] - private void Child_Fetch() => /* ... */ + private void FetchChild() => /* ... */ } ``` diff --git a/docs/analyzers/CSLA0014-DoesOperationHaveAttributeAnalyzer.md b/docs/analyzers/CSLA0014-DoesOperationHaveAttributeAnalyzer.md index 8e4ad11697..8bf7605f71 100644 --- a/docs/analyzers/CSLA0014-DoesOperationHaveAttributeAnalyzer.md +++ b/docs/analyzers/CSLA0014-DoesOperationHaveAttributeAnalyzer.md @@ -1,7 +1,7 @@ # Operations should have the appropriate operation attribute ## Issue -This analyzer is tripped if an operation uses the correct method name (e.g. `DataPortal_Fetch`), but doesn't have the correct attribute: +This analyzer is tripped if a method uses a legacy data portal operation method name (e.g. `DataPortal_Fetch` or `Child_Update`), but doesn't have an operation attribute: ``` using Csla; @@ -15,7 +15,7 @@ public class Customer } ``` -For now (CSLA version 5), this is only an informational analyzer. Future versions may change this to a warning and subsequently an error. +Starting with CSLA 11, the data portal only invokes methods that have an operation attribute (such as `[Fetch]` or `[UpdateChild]`). It no longer finds operation methods by name, so a method like the one above is never called. For this reason, this analyzer is a warning in CSLA 11 (it was informational in earlier versions). ## Code Fix A code fix will show up to add the correct attribute to the operation. diff --git a/releasenotes.md b/releasenotes.md index 81fd23bc27..a193345edc 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -17,6 +17,10 @@ CSLA 11 includes all of the changes released in CSLA 10.2.0. The full list of ch * `netstandard2.0` and .NET Framework 4.6.2 through 4.8 targets are unchanged * `Csla.Maui` targets `net11.0-android`, `net11.0-ios`, `net11.0-maccatalyst`, and `net11.0-windows` alongside the .NET 10 targets +**Attribute-only data portal operations** ([#4828](https://github.com/MarimerLLC/csla/issues/4828)) + +The data portal no longer finds operation methods by name (`DataPortal_Fetch`, `Child_Update`, and so on). Only methods marked with an operation attribute are invoked. Matching by name could call an unrelated method that happened to have an operation name ([#4595](https://github.com/MarimerLLC/csla/issues/4595)). + ### Changes **Build and Release** @@ -27,6 +31,8 @@ CSLA 11 includes all of the changes released in CSLA 10.2.0. The full list of ch * CSLA 11 no longer targets .NET 8 or .NET 9. Applications must target .NET 10 or later (or continue to use .NET Framework 4.6.2 through 4.8, or a `netstandard2.0`-compatible runtime). * On .NET 11, Android apps using `Csla.Maui` require Android API level 24 or later, because .NET 11 for Android requires it. The `net10.0-android` target still supports API level 21. +* The data portal no longer finds operation methods by name. Only methods with an operation attribute (such as `[Fetch]`, `[Update]`, or `[UpdateChild]`) are invoked, and the `DataPortalOptions.UseLegacyOperationMethods` option is removed. Add the matching attribute to any `DataPortal_XYZ` or `Child_XYZ` method that doesn't have one. The `CSLA0014` analyzer is now a warning and its code fix adds the attribute for you. See [Upgrading to CSLA 11](docs/Upgrading%20to%20CSLA%2011.md) ([#4828](https://github.com/MarimerLLC/csla/issues/4828)). + ### Supported Platforms From 76ea61b6a5f7c1d963d2e40df2999088e95a2873 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Thu, 8 Oct 2026 17:24:12 -0500 Subject: [PATCH 2/7] #4828 Attribute-only base class operations (Stage B: D1-D3) - D1: delete the private "not supported" DataPortal_XYZ stubs from CommandBase, ReadOnlyBase, ReadOnlyListBase, ReadOnlyBindingListBase, NameValueListBase, DynamicListBase, and DynamicBindingListBase - D2: mark the default Child_Create methods on BusinessBase, BusinessListBase, and BusinessBindingListBase with [CreateChild] - D3: child create with an ObjectFactory no longer looks for a method named Child_Create; it uses the [CreateChild] method on the business type - D4: leave the sync Child_Update helpers as ordinary protected methods that sync [Update] methods can call; only Child_UpdateAsync is an operation Co-Authored-By: Claude Opus 5.5 --- Source/Csla/BusinessBindingListBase.cs | 1 + Source/Csla/BusinessListBase.cs | 1 + Source/Csla/CommandBase.cs | 32 ------------------- Source/Csla/Core/BusinessBase.cs | 13 +++++--- Source/Csla/DynamicBindingListBase.cs | 12 ------- Source/Csla/DynamicListBase.cs | 12 ------- Source/Csla/NameValueListBase.cs | 12 ------- Source/Csla/ReadOnlyBase.cs | 24 -------------- Source/Csla/ReadOnlyBindingListBase.cs | 12 ------- Source/Csla/ReadOnlyListBase.cs | 12 ------- .../Reflection/ServiceProviderMethodCaller.cs | 13 +++----- 11 files changed, 14 insertions(+), 130 deletions(-) diff --git a/Source/Csla/BusinessBindingListBase.cs b/Source/Csla/BusinessBindingListBase.cs index 2d1f0d25c4..3fa1dfe568 100644 --- a/Source/Csla/BusinessBindingListBase.cs +++ b/Source/Csla/BusinessBindingListBase.cs @@ -875,6 +875,7 @@ public override bool IsBusy /// with default values. /// [EditorBrowsable(EditorBrowsableState.Advanced)] + [CreateChild] protected virtual void Child_Create() { /* do nothing - list self-initializes */ } diff --git a/Source/Csla/BusinessListBase.cs b/Source/Csla/BusinessListBase.cs index df8017ed87..dd25d02851 100644 --- a/Source/Csla/BusinessListBase.cs +++ b/Source/Csla/BusinessListBase.cs @@ -927,6 +927,7 @@ public override bool IsBusy /// with default values. /// [EditorBrowsable(EditorBrowsableState.Advanced)] + [CreateChild] protected virtual void Child_Create() { /* do nothing - list self-initializes */ } diff --git a/Source/Csla/CommandBase.cs b/Source/Csla/CommandBase.cs index 1c69800578..980056c6bf 100644 --- a/Source/Csla/CommandBase.cs +++ b/Source/Csla/CommandBase.cs @@ -75,38 +75,6 @@ protected virtual void Initialize() #region Data Access - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private void DataPortal_Create(object criteria) - { - throw new NotSupportedException(Resources.CreateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private void DataPortal_Fetch(object criteria) - { - throw new NotSupportedException(Resources.FetchNotSupportedException); - } - - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private void DataPortal_Update() - { - throw new NotSupportedException(Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_xyz method. diff --git a/Source/Csla/Core/BusinessBase.cs b/Source/Csla/Core/BusinessBase.cs index 838da17f02..f02085d273 100644 --- a/Source/Csla/Core/BusinessBase.cs +++ b/Source/Csla/Core/BusinessBase.cs @@ -1312,14 +1312,17 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E { } /// - /// Override this method to load a new business object with default - /// values from the database. + /// Initializes a new child object with default values + /// and checks its business rules. /// /// - /// Normally you will overload this method to accept a strongly-typed - /// criteria parameter, rather than overriding the method with a - /// loosely-typed criteria parameter. + /// The child data portal calls this method when a create child + /// operation has no criteria and the business class doesn't + /// declare a better matching + /// method. Normally you will implement your own method marked with + /// , rather than overriding this one. /// + [CreateChild] protected virtual void Child_Create() { BusinessRules.CheckRules(); diff --git a/Source/Csla/DynamicBindingListBase.cs b/Source/Csla/DynamicBindingListBase.cs index 5d3410c27d..90d31cf384 100644 --- a/Source/Csla/DynamicBindingListBase.cs +++ b/Source/Csla/DynamicBindingListBase.cs @@ -444,18 +444,6 @@ protected virtual void OnChildPropertyChanged(object? sender, PropertyChangedEve #region Data Access - private void DataPortal_Update() - { - throw new NotSupportedException(Properties.Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Properties.Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_xyz method. diff --git a/Source/Csla/DynamicListBase.cs b/Source/Csla/DynamicListBase.cs index b7eaace928..933465d968 100644 --- a/Source/Csla/DynamicListBase.cs +++ b/Source/Csla/DynamicListBase.cs @@ -460,18 +460,6 @@ public override bool IsBusy #region Data Access - private void DataPortal_Update() - { - throw new NotSupportedException(Properties.Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Properties.Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_xyz method. diff --git a/Source/Csla/NameValueListBase.cs b/Source/Csla/NameValueListBase.cs index 7815f772ca..c61fa8b3f9 100644 --- a/Source/Csla/NameValueListBase.cs +++ b/Source/Csla/NameValueListBase.cs @@ -322,18 +322,6 @@ public async Task WaitForIdle() await WaitForIdle(TimeSpan.FromSeconds(cslaOptions.DefaultWaitForIdleTimeoutInSeconds)).ConfigureAwait(false); } - private void DataPortal_Update() - { - throw new NotSupportedException(Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_XYZ method. diff --git a/Source/Csla/ReadOnlyBase.cs b/Source/Csla/ReadOnlyBase.cs index 312c6b2d6a..fdb29756ac 100644 --- a/Source/Csla/ReadOnlyBase.cs +++ b/Source/Csla/ReadOnlyBase.cs @@ -427,30 +427,6 @@ public T Clone() #region Data Access - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private void DataPortal_Create(object criteria) - { - throw new NotSupportedException(Resources.CreateNotSupportedException); - } - - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private void DataPortal_Update() - { - throw new NotSupportedException(Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_xyz method. diff --git a/Source/Csla/ReadOnlyBindingListBase.cs b/Source/Csla/ReadOnlyBindingListBase.cs index ae84c39b65..6cb35f9a29 100644 --- a/Source/Csla/ReadOnlyBindingListBase.cs +++ b/Source/Csla/ReadOnlyBindingListBase.cs @@ -94,18 +94,6 @@ public T Clone() #region Data Access - private void DataPortal_Update() - { - throw new NotSupportedException(Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_xyz method. diff --git a/Source/Csla/ReadOnlyListBase.cs b/Source/Csla/ReadOnlyListBase.cs index 9fba34f375..baad816002 100644 --- a/Source/Csla/ReadOnlyListBase.cs +++ b/Source/Csla/ReadOnlyListBase.cs @@ -120,18 +120,6 @@ public C[] ToArray() #region Data Access - private void DataPortal_Update() - { - throw new NotSupportedException(Resources.UpdateNotSupportedException); - } - - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "criteria")] - [Delete] - private void DataPortal_Delete(object criteria) - { - throw new NotSupportedException(Resources.DeleteNotSupportedException); - } - /// /// Called by the server-side DataPortal prior to calling the /// requested DataPortal_xyz method. diff --git a/Source/Csla/Reflection/ServiceProviderMethodCaller.cs b/Source/Csla/Reflection/ServiceProviderMethodCaller.cs index 60a8e6117f..5914285e10 100644 --- a/Source/Csla/Reflection/ServiceProviderMethodCaller.cs +++ b/Source/Csla/Reflection/ServiceProviderMethodCaller.cs @@ -147,7 +147,9 @@ public bool TryFindDataPortalMethod([DynamicallyAccessedMembers(DynamicallyAc } var candidates = new List(); - if (factoryInfo != null) + // A factory has no create child method; child create always + // uses the [CreateChild] method on the business type. + if (factoryInfo != null && typeOfOperation != typeof(CreateChildAttribute)) { var factoryWalkType = factoryType; var ftList = new List(); @@ -163,21 +165,14 @@ public bool TryFindDataPortalMethod([DynamicallyAccessedMembers(DynamicallyAc ftList.AddRange(factoryWalkType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.DeleteMethodName)); else if (typeOfOperation == typeof(ExecuteAttribute)) ftList.AddRange(factoryWalkType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.ExecuteMethodName)); - else if (typeOfOperation == typeof(CreateChildAttribute)) - ftList.AddRange(factoryWalkType.GetMethods(_factoryBindingAttr).Where(m => m.Name == "Child_Create")); else ftList.AddRange(factoryWalkType.GetMethods(_factoryBindingAttr).Where(m => m.Name == factoryInfo.UpdateMethodName)); factoryWalkType = factoryWalkType.BaseType; candidates.AddRange(ftList.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = level })); level--; } - if (!candidates.Any() && typeOfOperation == typeof(CreateChildAttribute)) - { - var ftlist = targetType.GetMethods(_bindingAttr).Where(m => m.Name == "Child_Create"); - candidates.AddRange(ftlist.Select(r => new ScoredMethodInfo { MethodInfo = r, Score = 0 })); - } } - else // not using factory types + else // not using factory types, or child create { var tt = targetType; var level = 0; From 9fc714c1553435cf89719fcb8e4424072bba5bd8 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Thu, 8 Oct 2026 17:27:45 -0500 Subject: [PATCH 3/7] #4828 Analyzers detect data portal operations by attribute only - A method qualifies as a data portal operation only when it has an operation attribute, so the operation analyzers (CSLA0002, 0009, 0010, 0012, 0013, 0019) no longer act on attribute-less DataPortal_XYZ and Child_XYZ names - CSLA0014 still finds legacy-named methods without an attribute, is now a Warning (the data portal never calls them), and skips overrides of attributed base methods such as BusinessBase.Child_Create - Update the analyzer tests for attribute-only detection Co-Authored-By: Claude Opus 5.5 --- .../AnalyzerReleases.Unshipped.md | 2 +- .../DoesOperationHaveAttributeAnalyzer.cs | 18 ++++++- .../DataPortalOperationQualification.cs | 7 ++- .../Csla.Analyzers/Properties/Resources.resx | 2 +- ...DoesOperationHaveAttributeAnalyzerTests.cs | 37 ++++++++++++- .../DataPortalOperationQualificationTests.cs | 2 +- .../IMethodSymbolExtensionsTests.cs | 52 +++++++++---------- 7 files changed, 85 insertions(+), 35 deletions(-) diff --git a/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md b/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md index c78e476580..3030ee5dd1 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md +++ b/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md @@ -16,7 +16,7 @@ CSLA0010 | Design | Warning | FindOperationsWithNonSerializableArgumentsAnalyzer CSLA0011 | Usage | Error | FindBusinessObjectCreationAnalyzer CSLA0012 | Design | Error | FindOperationsWithIncorrectReturnTypesAnalyzer CSLA0013 | Usage | Warning | DoesChildOperationHaveRunLocalAnalyzer -CSLA0014 | Usage | Info | DoesOperationHaveAttributeAnalyzer +CSLA0014 | Usage | Warning | DoesOperationHaveAttributeAnalyzer CSLA0015 | Usage | Error | EvaluateOperationAttributeUsageAnalyzer CSLA0016 | Usage | Error | AsynchronousBusinessRuleInheritingFromBusinessRuleAnalyzer CSLA0017 | Usage | Warning | BusinessRuleDoesNotUseAddMethodsOnContextAnalyzer diff --git a/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs b/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs index c7c9ecfe5f..102a6ca010 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs @@ -18,7 +18,7 @@ public sealed class DoesOperationHaveAttributeAnalyzer new( Constants.AnalyzerIdentifiers.DoesOperationHaveAttribute, DoesOperationHaveAttributeAnalyzerConstants.Title, DoesOperationHaveAttributeAnalyzerConstants.Message, Constants.Categories.Usage, - DiagnosticSeverity.Info, true, + DiagnosticSeverity.Warning, true, helpLinkUri: HelpUrlBuilder.Build( Constants.AnalyzerIdentifiers.DoesOperationHaveAttribute, nameof(DoesOperationHaveAttributeAnalyzer))); @@ -52,12 +52,26 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { var qualification = methodSymbol.IsDataPortalOperation(); - if(qualification.ByNamingConvention && !qualification.ByAttribute) + if (qualification.ByNamingConvention && !qualification.ByAttribute && !OverridesOperation(methodSymbol)) { context.ReportDiagnostic(Diagnostic.Create( operationAttributeRule, methodSymbol.Locations[0])); } } } + + // An override inherits the operation attribute of the method it overrides + // (such as BusinessBase.Child_Create), so the data portal still calls it. + private static bool OverridesOperation(IMethodSymbol methodSymbol) + { + for (var overridden = methodSymbol.OverriddenMethod; overridden is not null; overridden = overridden.OverriddenMethod) + { + if (overridden.IsDataPortalOperation().ByAttribute) + { + return true; + } + } + return false; + } } } \ No newline at end of file diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Extensions/DataPortalOperationQualification.cs b/Source/Csla.Analyzers/Csla.Analyzers/Extensions/DataPortalOperationQualification.cs index bbaf532052..264e66d613 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Extensions/DataPortalOperationQualification.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/Extensions/DataPortalOperationQualification.cs @@ -20,10 +20,13 @@ public DataPortalOperationQualification Combine(DataPortalOperationQualification qualification.ByAttribute | ByAttribute); /// - /// + /// The data portal only invokes methods that have an operation attribute, + /// so a method qualifies as an operation only by attribute. + /// is used only to find methods that + /// look like legacy operations but are missing their attribute. /// public static implicit operator bool(DataPortalOperationQualification qualification) => - qualification.ByAttribute | qualification.ByNamingConvention; + qualification.ByAttribute; /// /// diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx b/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx index 031868b675..812f4546cf 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx +++ b/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx @@ -169,7 +169,7 @@ Find Operations That Do Not Have an Operation Attribute - Operations should have the appropriate operation attribute + This method is named like a data portal operation but has no operation attribute, so the data portal never calls it. Add the operation attribute. Add attribute and using statement diff --git a/Source/tests/Csla.Analyzers.Tests/DoesOperationHaveAttributeAnalyzerTests.cs b/Source/tests/Csla.Analyzers.Tests/DoesOperationHaveAttributeAnalyzerTests.cs index f581e007e3..07e095516b 100644 --- a/Source/tests/Csla.Analyzers.Tests/DoesOperationHaveAttributeAnalyzerTests.cs +++ b/Source/tests/Csla.Analyzers.Tests/DoesOperationHaveAttributeAnalyzerTests.cs @@ -18,11 +18,11 @@ public void VerifySupportedDiagnostics() nameof(DiagnosticDescriptor.Id)); Assert.AreEqual("Find Operations That Do Not Have an Operation Attribute", diagnostic.Title.ToString(), nameof(DiagnosticDescriptor.Title)); - Assert.AreEqual("Operations should have the appropriate operation attribute", diagnostic.MessageFormat.ToString(), + Assert.AreEqual("This method is named like a data portal operation but has no operation attribute, so the data portal never calls it. Add the operation attribute.", diagnostic.MessageFormat.ToString(), nameof(DiagnosticDescriptor.MessageFormat)); Assert.AreEqual(Constants.Categories.Usage, diagnostic.Category, nameof(DiagnosticDescriptor.Category)); - Assert.AreEqual(DiagnosticSeverity.Info, diagnostic.DefaultSeverity, + Assert.AreEqual(DiagnosticSeverity.Warning, diagnostic.DefaultSeverity, nameof(DiagnosticDescriptor.DefaultSeverity)); Assert.AreEqual(HelpUrlBuilder.Build(Constants.AnalyzerIdentifiers.DoesOperationHaveAttribute, nameof(DoesOperationHaveAttributeAnalyzer)), diagnostic.HelpLinkUri, @@ -83,5 +83,38 @@ private void DataPortal_Fetch() { } await TestHelpers.RunAnalysisAsync( code, [Constants.AnalyzerIdentifiers.DoesOperationHaveAttribute]); } + + [TestMethod] + public async Task AnalyzeWhenOperationOverridesAttributedBaseOperation() + { + var code = + """ + using Csla; + + public class A : BusinessBase + { + protected override void Child_Create() { } + } + """; + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenOperationOverridesBaseMethodWithoutAttribute() + { + var code = + """ + using Csla; + + public class A : BusinessListBase + { + protected override void Child_Update(params object[] parameters) { } + } + + public class B : BusinessBase { } + """; + await TestHelpers.RunAnalysisAsync( + code, [Constants.AnalyzerIdentifiers.DoesOperationHaveAttribute]); + } } } \ No newline at end of file diff --git a/Source/tests/Csla.Analyzers.Tests/Extensions/DataPortalOperationQualificationTests.cs b/Source/tests/Csla.Analyzers.Tests/Extensions/DataPortalOperationQualificationTests.cs index 3059dcacce..27e136b89e 100644 --- a/Source/tests/Csla.Analyzers.Tests/Extensions/DataPortalOperationQualificationTests.cs +++ b/Source/tests/Csla.Analyzers.Tests/Extensions/DataPortalOperationQualificationTests.cs @@ -40,7 +40,7 @@ public void CompareWhenByAttributeFlagIsTrue() => [TestMethod] public void CompareWhenByNamingConventionFlagIsTrue() => - Assert.IsTrue(new DataPortalOperationQualification(true, false)); + Assert.IsFalse(new DataPortalOperationQualification(true, false)); [TestMethod] public void CombineFalseAndFalseWithFalseAndFalse() diff --git a/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs b/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs index 32e0b8ce70..cbb2d91665 100644 --- a/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs +++ b/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs @@ -283,12 +283,12 @@ public async Task IsChildDataPortalOperationForMethodThatIsNotADataPortalOperati [TestMethod] public async Task IsDataPortalOperationForDataPortalCreate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalCreate)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalCreate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalCreate)).IsRootDataPortalOperation()); [TestMethod] @@ -313,12 +313,12 @@ public async Task IsChildDataPortalOperationForDataPortalCreateWithAttribute() = [TestMethod] public async Task IsDataPortalOperationForDataPortalFetch() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalFetch)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalFetch() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalFetch)).IsRootDataPortalOperation()); [TestMethod] @@ -343,12 +343,12 @@ public async Task IsChildDataPortalOperationForDataPortalFetchWithAttribute() => [TestMethod] public async Task IsDataPortalOperationForDataPortalInsert() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalInsert)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalInsert() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalInsert)).IsRootDataPortalOperation()); [TestMethod] @@ -373,12 +373,12 @@ public async Task IsChildDataPortalOperationForDataPortalInsertWithAttribute() = [TestMethod] public async Task IsDataPortalOperationForDataPortalUpdate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalUpdate)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalUpdate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalUpdate)).IsRootDataPortalOperation()); [TestMethod] @@ -403,12 +403,12 @@ public async Task IsChildDataPortalOperationForDataPortalUpdateWithAttribute() = [TestMethod] public async Task IsDataPortalOperationForDataPortalDelete() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalDelete)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalDelete() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalDelete)).IsRootDataPortalOperation()); [TestMethod] @@ -433,12 +433,12 @@ public async Task IsChildDataPortalOperationForDataPortalDeleteWithAttribute() = [TestMethod] public async Task IsDataPortalOperationForDataPortalDeleteSelf() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalDeleteSelf)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalDeleteSelf() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalDeleteSelf)).IsRootDataPortalOperation()); [TestMethod] @@ -463,12 +463,12 @@ public async Task IsChildDataPortalOperationForDataPortalDeleteSelfWithAttribute [TestMethod] public async Task IsDataPortalOperationForDataPortalExecute() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalExecute)).IsDataPortalOperation()); [TestMethod] public async Task IsRootDataPortalOperationForDataPortalExecute() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.DataPortalExecute)).IsRootDataPortalOperation()); [TestMethod] @@ -493,7 +493,7 @@ public async Task IsChildDataPortalOperationForDataPortalExecuteWithAttribute() [TestMethod] public async Task IsDataPortalOperationForChildCreate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildCreate)).IsDataPortalOperation()); [TestMethod] @@ -503,7 +503,7 @@ public async Task IsRootDataPortalOperationForChildCreate() => [TestMethod] public async Task IsChildDataPortalOperationForChildCreate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildCreate)).IsChildDataPortalOperation()); [TestMethod] @@ -523,7 +523,7 @@ public async Task IsChildDataPortalOperationForChildCreateWithAttribute() => [TestMethod] public async Task IsDataPortalOperationForChildFetch() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildFetch)).IsDataPortalOperation()); [TestMethod] @@ -533,7 +533,7 @@ public async Task IsRootDataPortalOperationForChildFetch() => [TestMethod] public async Task IsChildDataPortalOperationForChildFetch() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildFetch)).IsChildDataPortalOperation()); [TestMethod] @@ -553,7 +553,7 @@ public async Task IsChildDataPortalOperationForChildFetchWithAttribute() => [TestMethod] public async Task IsDataPortalOperationForChildInsert() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildInsert)).IsDataPortalOperation()); [TestMethod] @@ -563,7 +563,7 @@ public async Task IsRootDataPortalOperationForChildInsert() => [TestMethod] public async Task IsChildDataPortalOperationForChildInsert() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildInsert)).IsChildDataPortalOperation()); [TestMethod] @@ -583,7 +583,7 @@ public async Task IsChildDataPortalOperationForChildInsertWithAttribute() => [TestMethod] public async Task IsDataPortalOperationForChildUpdate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildUpdate)).IsDataPortalOperation()); [TestMethod] @@ -593,7 +593,7 @@ public async Task IsRootDataPortalOperationForChildUpdate() => [TestMethod] public async Task IsChildDataPortalOperationForChildUpdate() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildUpdate)).IsChildDataPortalOperation()); [TestMethod] @@ -613,7 +613,7 @@ public async Task IsChildDataPortalOperationForChildUpdateWithAttribute() => [TestMethod] public async Task IsDataPortalOperationForChildDeleteSelf() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildDeleteSelf)).IsDataPortalOperation()); [TestMethod] @@ -623,12 +623,12 @@ public async Task IsRootDataPortalOperationForChildDeleteSelf() => [TestMethod] public async Task IsChildDataPortalOperationForChildDeleteSelf() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildDeleteSelf)).IsChildDataPortalOperation()); [TestMethod] public async Task IsDataPortalOperationForChildExecute() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildExecute)).IsDataPortalOperation()); [TestMethod] @@ -638,7 +638,7 @@ public async Task IsRootDataPortalOperationForChildExecute() => [TestMethod] public async Task IsChildDataPortalOperationForChildExecute() => - Assert.IsTrue((await GetMethodSymbolAsync( + Assert.IsFalse((await GetMethodSymbolAsync( DataPortalOperationCode, CslaMemberConstants.Operations.ChildExecute)).IsChildDataPortalOperation()); [TestMethod] From a81c0ed62a21854d2841750512d748d42752b1e0 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Thu, 8 Oct 2026 17:29:37 -0500 Subject: [PATCH 4/7] #4828 Update XML docs, upgrade guide, and release notes for attribute-only operations - XML doc comments on CommandBase, Core.BusinessBase, DynamicListBase, DynamicBindingListBase, IDataPortal, and IDataPortalT refer to operation attributes instead of DataPortal_XYZ method names - Upgrade guide and release notes describe the base class changes (default Child_Create, removed "not supported" stubs, ObjectFactory child create, sync Child_Update) and when a legacy-named method is silently skipped rather than failing Co-Authored-By: Claude Opus 5.5 --- Source/Csla/CommandBase.cs | 6 +++--- Source/Csla/Core/BusinessBase.cs | 12 ++++++------ Source/Csla/DynamicBindingListBase.cs | 4 ++-- Source/Csla/DynamicListBase.cs | 4 ++-- Source/Csla/IDataPortal.cs | 2 +- Source/Csla/IDataPortalT.cs | 2 +- docs/Upgrading to CSLA 11.md | 19 ++++++++++++------- releasenotes.md | 4 +++- 8 files changed, 30 insertions(+), 23 deletions(-) diff --git a/Source/Csla/CommandBase.cs b/Source/Csla/CommandBase.cs index 980056c6bf..a987bedfed 100644 --- a/Source/Csla/CommandBase.cs +++ b/Source/Csla/CommandBase.cs @@ -30,13 +30,13 @@ namespace Csla /// type of stateless, atomic call to the server instead. /// /// To implement a command object, inherit from CommandBase and - /// override the DataPortal_Execute method. In this method you can - /// implement any server-side code as required. + /// implement a method marked with the . + /// In this method you can implement any server-side code as required. /// /// To pass data to/from the server, use instance variables within /// the command object itself. The command object is instantiated on /// the client, and is passed by value to the server where the - /// DataPortal_Execute method is invoked. The command object is then + /// method is invoked. The command object is then /// returned to the client by value. /// /// diff --git a/Source/Csla/Core/BusinessBase.cs b/Source/Csla/Core/BusinessBase.cs index f02085d273..a22535fbe2 100644 --- a/Source/Csla/Core/BusinessBase.cs +++ b/Source/Csla/Core/BusinessBase.cs @@ -240,8 +240,8 @@ protected virtual void SetParent(IParent? parent) /// /// /// Newly created objects are marked new by default. You should call - /// this method in the implementation of DataPortal_Update when the - /// object is deleted (due to being marked for deletion) to indicate + /// this method in the implementation of your + /// or method when the object is deleted (due to being marked for deletion) to indicate /// that the object no longer reflects data in the database. /// /// If you override this method, make sure to call the base @@ -263,12 +263,12 @@ protected virtual void MarkNew() /// /// /// - /// You should call this method in the implementation of - /// DataPortal_Fetch to indicate that an existing object has been + /// You should call this method in the implementation of your + /// method to indicate that an existing object has been /// successfully retrieved from the database. /// - /// You should call this method in the implementation of - /// DataPortal_Update to indicate that a new object has been successfully + /// You should call this method in the implementation of your + /// method to indicate that a new object has been successfully /// inserted into the database. /// /// If you override this method, make sure to call the base diff --git a/Source/Csla/DynamicBindingListBase.cs b/Source/Csla/DynamicBindingListBase.cs index 90d31cf384..1929542432 100644 --- a/Source/Csla/DynamicBindingListBase.cs +++ b/Source/Csla/DynamicBindingListBase.cs @@ -26,8 +26,8 @@ namespace Csla /// /// /// Your subclass should implement a factory method - /// and should override or overload - /// DataPortal_Fetch() to implement data retrieval. + /// and a method marked with the + /// to implement data retrieval. /// /// Saving (inserts or updates) of items in the collection /// should be handled through the SaveItem() method on diff --git a/Source/Csla/DynamicListBase.cs b/Source/Csla/DynamicListBase.cs index 933465d968..73e341029c 100644 --- a/Source/Csla/DynamicListBase.cs +++ b/Source/Csla/DynamicListBase.cs @@ -28,8 +28,8 @@ namespace Csla /// /// /// Your subclass should implement a factory method - /// and should override or overload - /// DataPortal_Fetch() to implement data retrieval. + /// and a method marked with the + /// to implement data retrieval. /// /// Saving (inserts or updates) of items in the collection /// should be handled through the SaveItem() method on diff --git a/Source/Csla/IDataPortal.cs b/Source/Csla/IDataPortal.cs index 61e0b37522..db947b2288 100644 --- a/Source/Csla/IDataPortal.cs +++ b/Source/Csla/IDataPortal.cs @@ -87,7 +87,7 @@ public interface IDataPortal /// different object from the original, and all object references MUST be updated /// to use this new object. /// - /// On the server, the Command object's DataPortal_Execute() method will + /// On the server, the Command object's method will /// be invoked and on an ObjectFactory the Execute method will be invoked. /// Write any server-side code in that method. /// diff --git a/Source/Csla/IDataPortalT.cs b/Source/Csla/IDataPortalT.cs index 73f643cf99..fa7f3cf8d2 100644 --- a/Source/Csla/IDataPortalT.cs +++ b/Source/Csla/IDataPortalT.cs @@ -90,7 +90,7 @@ public interface IDataPortal<[DynamicallyAccessedMembers(DynamicallyAccessedMemb /// different object from the original, and all object references MUST be updated /// to use this new object. /// - /// On the server, the Command object's DataPortal_Execute() method will + /// On the server, the Command object's method will /// be invoked and on an ObjectFactory the Execute method will be invoked. /// Write any server-side code in that method. /// diff --git a/docs/Upgrading to CSLA 11.md b/docs/Upgrading to CSLA 11.md index 4856e94762..e4e58ad620 100644 --- a/docs/Upgrading to CSLA 11.md +++ b/docs/Upgrading to CSLA 11.md @@ -65,15 +65,20 @@ public class Customer : BusinessBase Once a method has an attribute, you can rename it to anything you like (for example `Fetch`). -A method with a legacy name and no attribute is never called. Instead, the data portal throws an exception at runtime because it can't find a matching operation method. +A method with a legacy name and no attribute is never called. Usually the data portal then throws an exception at runtime because it can't find a matching operation method. But when a CSLA base class has its own operation method, the base class method runs instead and your method is silently skipped. For example: + +* A child class with an attribute-less `Child_Create()` gets `BusinessBase.Child_Create()`, which only checks business rules. +* A list class that overrides `Child_Update(params object[])` without `[UpdateChild]` gets the base `Child_UpdateAsync`, which updates the child items but not anything else your override did. + +Use the `CSLA0014` analyzer (below) to find these methods rather than relying on runtime errors. ### Finding affected methods The `CSLA0014` analyzer finds methods that use a legacy operation name but have no operation attribute. In CSLA 11 it reports a warning instead of an informational message. The analyzer's code fix adds the correct attribute for you. See [CSLA0014](analyzers/CSLA0014-DoesOperationHaveAttributeAnalyzer.md). - +### Base class changes + +* **Default child create:** `BusinessBase`, `BusinessListBase`, and `BusinessBindingListBase` each have a `protected virtual Child_Create()` method that is now marked with `[CreateChild]`. It's still called when you create a child with no criteria and your class has no better matching `[CreateChild]` method. `BusinessBase.Child_Create()` checks the object's business rules. If you override `Child_Create()`, the override inherits the attribute, so you don't need to add one. +* **"Not supported" operations:** `CommandBase`, `ReadOnlyBase`, `ReadOnlyListBase`, `ReadOnlyBindingListBase`, `NameValueListBase`, `DynamicListBase`, and `DynamicBindingListBase` no longer have private `DataPortal_XYZ` methods that throw `NotSupportedException` (for example, calling update on a read-only object). Those calls now fail with the data portal's normal "method not found" error. +* **ObjectFactory child create:** creating a child of a type that uses `[ObjectFactory]` no longer looks for a method named `Child_Create` on the factory or the business class. The data portal calls the business class's `[CreateChild]` method (or the base class default described above). +* **Sync `Child_Update`:** the synchronous `Child_Update(params object[])` helper on `BusinessListBase`, `BusinessBindingListBase`, and `BusinessDocumentBase` has no operation attribute. You can still call it from your own synchronous `[Update]`, `[Insert]`, or `[DeleteSelf]` methods. The data portal itself calls `Child_UpdateAsync`, which is marked with `[UpdateChild]`. If you override `Child_Update` so the data portal calls your override, add `[UpdateChild]` to it, or override `Child_UpdateAsync` instead. diff --git a/releasenotes.md b/releasenotes.md index a193345edc..04f6c441f4 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -32,7 +32,9 @@ The data portal no longer finds operation methods by name (`DataPortal_Fetch`, ` * CSLA 11 no longer targets .NET 8 or .NET 9. Applications must target .NET 10 or later (or continue to use .NET Framework 4.6.2 through 4.8, or a `netstandard2.0`-compatible runtime). * On .NET 11, Android apps using `Csla.Maui` require Android API level 24 or later, because .NET 11 for Android requires it. The `net10.0-android` target still supports API level 21. * The data portal no longer finds operation methods by name. Only methods with an operation attribute (such as `[Fetch]`, `[Update]`, or `[UpdateChild]`) are invoked, and the `DataPortalOptions.UseLegacyOperationMethods` option is removed. Add the matching attribute to any `DataPortal_XYZ` or `Child_XYZ` method that doesn't have one. The `CSLA0014` analyzer is now a warning and its code fix adds the attribute for you. See [Upgrading to CSLA 11](docs/Upgrading%20to%20CSLA%2011.md) ([#4828](https://github.com/MarimerLLC/csla/issues/4828)). - + * The default `Child_Create()` methods on `BusinessBase`, `BusinessListBase`, and `BusinessBindingListBase` are now marked with `[CreateChild]`, so a child with no create method of its own still gets its business rules checked. + * The private "not supported" `DataPortal_XYZ` methods on `CommandBase`, `ReadOnlyBase`, and the read-only, name/value, and dynamic list base classes are removed. Unsupported operations now fail with the data portal's normal "method not found" error. + * Creating a child of an `[ObjectFactory]` type no longer looks for a method named `Child_Create`. It calls the business class's `[CreateChild]` method. ### Supported Platforms From 118f33be8571f2201133c4f04cfc60cfa9d81430 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Thu, 8 Oct 2026 17:29:37 -0500 Subject: [PATCH 5/7] #4828 Test child create method lookup for default and ObjectFactory types Co-Authored-By: Claude Opus 5.5 --- .../ServiceProviderMethodCallerTests.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs b/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs index cf664fcc4c..90671634a0 100644 --- a/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs +++ b/Source/tests/csla.netcore.test/DataPortal/ServiceProviderMethodCallerTests.cs @@ -533,6 +533,51 @@ public void FindLegacyNamedMethodWithoutAttribute_NotFound() method.Should().BeNull(); } + [TestMethod] + public void FindCreateChild_NoCreateChildMethod_FindsBaseChildCreate() + { + var method = _systemUnderTest.FindDataPortalMethod(new ChildWithoutCreate(), null); + + method.MethodInfo.Name.Should().Be("Child_Create"); + method.MethodInfo.DeclaringType.Should().Be(typeof(Csla.Core.BusinessBase)); + } + + [TestMethod] + public void FindCreateChild_OwnCreateChildMethod_WinsOverBaseChildCreate() + { + var method = _systemUnderTest.FindDataPortalMethod(new ChildWithCreate(), null); + + method.MethodInfo.Name.Should().Be("Create"); + method.MethodInfo.DeclaringType.Should().Be(typeof(ChildWithCreate)); + } + + [TestMethod] + public void FindCreateChild_ListWithoutCreateChildMethod_FindsBaseChildCreate() + { + var method = _systemUnderTest.FindDataPortalMethod(new ListWithoutCreate(), null); + + method.MethodInfo.Name.Should().Be("Child_Create"); + method.MethodInfo.DeclaringType.Should().Be(typeof(BusinessListBase)); + } + + [TestMethod] + public void FindCreateChild_ObjectFactoryType_FindsCreateChildOnBusinessType() + { + var method = _systemUnderTest.FindDataPortalMethod(new FactoryChildWithCreate(), [123]); + + method.MethodInfo.Name.Should().Be("Create"); + method.MethodInfo.DeclaringType.Should().Be(typeof(FactoryChildWithCreate)); + } + + [TestMethod] + public void FindCreateChild_ObjectFactoryType_IgnoresFactoryChildCreateMethod() + { + var method = _systemUnderTest.FindDataPortalMethod(new FactoryChildWithoutCreate(), null); + + method.MethodInfo.Name.Should().Be("Child_Create"); + method.MethodInfo.DeclaringType.Should().Be(typeof(Csla.Core.BusinessBase)); + } + #if NET8_0_OR_GREATER [TestMethod] public void FindMethodWithKeyedServiceInjection() @@ -1065,6 +1110,51 @@ private void DataPortal_Create() } } + public class ChildWithoutCreate : BusinessBase + { + } + + public class ChildWithCreate : BusinessBase + { + [CreateChild] + private void Create() + { + BusinessRules.CheckRules(); + } + } + + public class ListWithoutCreate : BusinessListBase + { + } + + [Csla.Server.ObjectFactory(typeof(ChildCreateFactory))] + public class FactoryChildWithCreate : BusinessBase + { + [CreateChild] + private void Create(int id) + { + BusinessRules.CheckRules(); + } + } + + [Csla.Server.ObjectFactory(typeof(ChildCreateFactory))] + public class FactoryChildWithoutCreate : BusinessBase + { + } + + public class ChildCreateFactory : Csla.Server.ObjectFactory + { + public ChildCreateFactory(ApplicationContext applicationContext) + : base(applicationContext) + { } + + // Before CSLA 11 the data portal looked for a factory method with this + // name for child create. It is now never used. + public object Child_Create() => throw new NotSupportedException(); + + public object Child_Create(int id) => throw new NotSupportedException(); + } + #if NET8_0_OR_GREATER // Tests for keyed service injection (NET8+ only) public class KeyedServiceInjection : BusinessBase From e895c6fc9e76ec6271fb0f5c877efa52e53637e9 Mon Sep 17 00:00:00 2001 From: Rockford lhotka Date: Mon, 12 Oct 2026 12:49:49 -0500 Subject: [PATCH 6/7] #4828 Analyzers recognize operation attributes inherited by overrides The data portal reads operation attributes with inherit: true, so an override such as BusinessBase.Child_Create() is still an operation. Operation qualification now walks the override chain, so analyzers like CSLA0013 report these overrides again. CSLA0014 no longer needs its own override check. Co-Authored-By: Claude Opus 5.5 --- .../DoesOperationHaveAttributeAnalyzer.cs | 18 ++----- .../Extensions/IMethodSymbolExtensions.cs | 19 ++++++- ...ChildOperationHaveRunLocalAnalyzerTests.cs | 17 ++++++ .../IMethodSymbolExtensionsTests.cs | 54 +++++++++++++++++++ 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs b/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs index 102a6ca010..7186026629 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/DoesOperationHaveAttributeAnalyzer.cs @@ -52,26 +52,14 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { var qualification = methodSymbol.IsDataPortalOperation(); - if (qualification.ByNamingConvention && !qualification.ByAttribute && !OverridesOperation(methodSymbol)) + // ByAttribute includes attributes inherited from an overridden method + // (such as BusinessBase.Child_Create), which the data portal still calls. + if (qualification.ByNamingConvention && !qualification.ByAttribute) { context.ReportDiagnostic(Diagnostic.Create( operationAttributeRule, methodSymbol.Locations[0])); } } } - - // An override inherits the operation attribute of the method it overrides - // (such as BusinessBase.Child_Create), so the data portal still calls it. - private static bool OverridesOperation(IMethodSymbol methodSymbol) - { - for (var overridden = methodSymbol.OverriddenMethod; overridden is not null; overridden = overridden.OverriddenMethod) - { - if (overridden.IsDataPortalOperation().ByAttribute) - { - return true; - } - } - return false; - } } } \ No newline at end of file diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Extensions/IMethodSymbolExtensions.cs b/Source/Csla.Analyzers/Csla.Analyzers/Extensions/IMethodSymbolExtensions.cs index b0abf5a674..11f42b0543 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Extensions/IMethodSymbolExtensions.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/Extensions/IMethodSymbolExtensions.cs @@ -53,7 +53,7 @@ internal static DataPortalOperationQualification IsRootDataPortalOperation(this @this.Name == CslaMemberConstants.Operations.DataPortalDelete || @this.Name == CslaMemberConstants.Operations.DataPortalDeleteSelf || @this.Name == CslaMemberConstants.Operations.DataPortalExecute; - var byAttribute = @this.GetAttributes().Any(_ => _.AttributeClass.IsDataPortalRootOperationAttribute()); + var byAttribute = @this.HasOperationAttribute(_ => _.IsDataPortalRootOperationAttribute()); return new DataPortalOperationQualification(byNamingConvention, byAttribute); } } @@ -73,11 +73,26 @@ internal static DataPortalOperationQualification IsChildDataPortalOperation(this @this.Name == CslaMemberConstants.Operations.ChildUpdate || @this.Name == CslaMemberConstants.Operations.ChildDeleteSelf || @this.Name == CslaMemberConstants.Operations.ChildExecute; - var byAttribute = @this.GetAttributes().Any(_ => _.AttributeClass.IsDataPortalChildOperationAttribute()); + var byAttribute = @this.HasOperationAttribute(_ => _.IsDataPortalChildOperationAttribute()); return new DataPortalOperationQualification(byNamingConvention, byAttribute); } } + // The data portal reads operation attributes with inherit: true, so an + // override (such as of BusinessBase.Child_Create) inherits the attribute + // of the method it overrides. + private static bool HasOperationAttribute(this IMethodSymbol @this, Func isOperationAttribute) + { + for (var method = @this; method is not null; method = method.OverriddenMethod) + { + if (method.GetAttributes().Any(_ => isOperationAttribute(_.AttributeClass))) + { + return true; + } + } + return false; + } + internal static CslaOperationQualification IsAddObjectAuthorizationRulesOperation(this IMethodSymbol @this) { if (@this is null) diff --git a/Source/tests/Csla.Analyzers.Tests/DoesChildOperationHaveRunLocalAnalyzerTests.cs b/Source/tests/Csla.Analyzers.Tests/DoesChildOperationHaveRunLocalAnalyzerTests.cs index 2b30373d7e..4f75b30ac3 100644 --- a/Source/tests/Csla.Analyzers.Tests/DoesChildOperationHaveRunLocalAnalyzerTests.cs +++ b/Source/tests/Csla.Analyzers.Tests/DoesChildOperationHaveRunLocalAnalyzerTests.cs @@ -86,5 +86,22 @@ private void FetchChild() { } await TestHelpers.RunAnalysisAsync( code, [Constants.AnalyzerIdentifiers.DoesChildOperationHaveRunLocal]); } + + [TestMethod] + public async Task AnalyzeWhenClassIsMobileObjectAndInheritedChildOperationHasRunLocal() + { + var code = + """ + using Csla; + + public class A : BusinessBase + { + [RunLocal] + protected override void Child_Create() { } + } + """; + await TestHelpers.RunAnalysisAsync( + code, [Constants.AnalyzerIdentifiers.DoesChildOperationHaveRunLocal]); + } } } \ No newline at end of file diff --git a/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs b/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs index cbb2d91665..e9b704ad8a 100644 --- a/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs +++ b/Source/tests/Csla.Analyzers.Tests/Extensions/IMethodSymbolExtensionsTests.cs @@ -672,6 +672,60 @@ public async Task IsChildDataPortalOperationForChildExecuteWithAttribute() => return (compilation.GetSemanticModel(tree), await tree.GetRootAsync().ConfigureAwait(false)); } + private const string InheritedOperationCode = + """ + using Csla; + + namespace Csla.Analyzers.Tests.Targets.IMethodSymbolExtensionsTests + { + public abstract class OperationBase : BusinessBase + where T : OperationBase + { + [Fetch] + protected virtual void Fetch(int id) { } + } + + public class InheritedOperations : OperationBase + { + protected override void Fetch(int id) { } + protected override void Child_Create() { } + } + + public class InheritedListOperations : BusinessListBase + { + protected override void Child_Update(params object[] parameters) { } + } + } + """; + + [TestMethod] + public async Task IsRootDataPortalOperationForOverrideOfAttributedBaseMethod() + { + var method = await GetMethodSymbolAsync(InheritedOperationCode, "Fetch"); + Assert.IsTrue(method.IsRootDataPortalOperation()); + Assert.IsTrue(method.IsDataPortalOperation()); + } + + [TestMethod] + public async Task IsChildDataPortalOperationForOverrideOfAttributedCslaBaseMethod() + { + var method = await GetMethodSymbolAsync(InheritedOperationCode, CslaMemberConstants.Operations.ChildCreate); + var (byNamingConvention, byAttribute) = method.IsChildDataPortalOperation(); + Assert.IsTrue(byNamingConvention); + Assert.IsTrue(byAttribute); + Assert.IsTrue(method.IsDataPortalOperation()); + } + + [TestMethod] + public async Task IsChildDataPortalOperationForOverrideOfBaseMethodWithoutAttribute() + { + var method = await GetMethodSymbolAsync(InheritedOperationCode, CslaMemberConstants.Operations.ChildUpdate); + var (byNamingConvention, byAttribute) = method.IsChildDataPortalOperation(); + Assert.IsTrue(byNamingConvention); + Assert.IsFalse(byAttribute); + Assert.IsFalse(method.IsDataPortalOperation()); + } + private async Task GetMethodSymbolAsync(string code, string name) { var (model, root) = await ParseFileAsync(code); From 6a6075a6c106029615f2e8ed5220fd3ddb471d77 Mon Sep 17 00:00:00 2001 From: Rockford lhotka Date: Mon, 12 Oct 2026 12:49:49 -0500 Subject: [PATCH 7/7] #4828 Replace remaining DataPortal_XYZ references in XML docs Data portal hook docs now refer to the "data portal operation method" (or "child data portal operation method" for Child_ hooks), as do the RunLocal and Transactional attribute docs. Co-Authored-By: Claude Opus 5.5 --- Source/Csla/BusinessBindingListBase.cs | 8 ++++---- Source/Csla/BusinessListBase.cs | 8 ++++---- Source/Csla/CommandBase.cs | 4 ++-- Source/Csla/Core/BusinessBase.cs | 8 ++++---- Source/Csla/DataPortalT.cs | 2 +- Source/Csla/DynamicBindingListBase.cs | 4 ++-- Source/Csla/DynamicListBase.cs | 4 ++-- Source/Csla/NameValueListBase.cs | 4 ++-- Source/Csla/ReadOnlyBase.cs | 8 ++++---- Source/Csla/ReadOnlyBindingListBase.cs | 8 ++++---- Source/Csla/ReadOnlyListBase.cs | 8 ++++---- Source/Csla/RunLocalAttribute.cs | 4 ++-- Source/Csla/TransactionalAttribute.cs | 6 +++--- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Source/Csla/BusinessBindingListBase.cs b/Source/Csla/BusinessBindingListBase.cs index ade6a7a34f..89ddebced5 100644 --- a/Source/Csla/BusinessBindingListBase.cs +++ b/Source/Csla/BusinessBindingListBase.cs @@ -1043,7 +1043,7 @@ public async Task SaveAndMergeAsync() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -1053,7 +1053,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -1074,7 +1074,7 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -1084,7 +1084,7 @@ protected virtual void Child_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] diff --git a/Source/Csla/BusinessListBase.cs b/Source/Csla/BusinessListBase.cs index fc0d811eb5..5a98b21fb1 100644 --- a/Source/Csla/BusinessListBase.cs +++ b/Source/Csla/BusinessListBase.cs @@ -1095,7 +1095,7 @@ public async Task SaveAndMergeAsync() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -1104,7 +1104,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -1123,7 +1123,7 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -1132,7 +1132,7 @@ protected virtual void Child_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] diff --git a/Source/Csla/CommandBase.cs b/Source/Csla/CommandBase.cs index 720b83fc8a..12c6975995 100644 --- a/Source/Csla/CommandBase.cs +++ b/Source/Csla/CommandBase.cs @@ -77,7 +77,7 @@ protected virtual void Initialize() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -89,7 +89,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] diff --git a/Source/Csla/Core/BusinessBase.cs b/Source/Csla/Core/BusinessBase.cs index a22535fbe2..a0775b4b35 100644 --- a/Source/Csla/Core/BusinessBase.cs +++ b/Source/Csla/Core/BusinessBase.cs @@ -1285,7 +1285,7 @@ public virtual Task WaitForIdle(CancellationToken ct) /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -1294,7 +1294,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -1330,7 +1330,7 @@ protected virtual void Child_Create() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -1339,7 +1339,7 @@ protected virtual void Child_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [EditorBrowsable(EditorBrowsableState.Advanced)] diff --git a/Source/Csla/DataPortalT.cs b/Source/Csla/DataPortalT.cs index 88d59492f2..1f2219e5bf 100644 --- a/Source/Csla/DataPortalT.cs +++ b/Source/Csla/DataPortalT.cs @@ -309,7 +309,7 @@ private async Task DoUpdateAsync(T obj, bool isSync, CancellationToken ct = d if (factoryType != null) method = Server.DataPortalMethodCache.GetMethodInfo(factoryType, factoryInfo.DeleteMethodName, [obj]); } - // must check the same authorization rules as for DataPortal_XYZ methods + // must check the same authorization rules as for data portal operation methods else if (bbase.IsNew) { if (!await Rules.BusinessRules.HasPermissionAsync(_applicationContext, Rules.AuthorizationActions.CreateObject, obj, ct)) diff --git a/Source/Csla/DynamicBindingListBase.cs b/Source/Csla/DynamicBindingListBase.cs index 26315a2f23..99a0507341 100644 --- a/Source/Csla/DynamicBindingListBase.cs +++ b/Source/Csla/DynamicBindingListBase.cs @@ -446,7 +446,7 @@ protected virtual void OnChildPropertyChanged(object? sender, PropertyChangedEve /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member"), EditorBrowsable(EditorBrowsableState.Advanced)] @@ -457,7 +457,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member"), EditorBrowsable(EditorBrowsableState.Advanced)] diff --git a/Source/Csla/DynamicListBase.cs b/Source/Csla/DynamicListBase.cs index 48848907db..60f81c75c3 100644 --- a/Source/Csla/DynamicListBase.cs +++ b/Source/Csla/DynamicListBase.cs @@ -462,7 +462,7 @@ public override bool IsBusy /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member"), EditorBrowsable(EditorBrowsableState.Advanced)] @@ -473,7 +473,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member"), EditorBrowsable(EditorBrowsableState.Advanced)] diff --git a/Source/Csla/NameValueListBase.cs b/Source/Csla/NameValueListBase.cs index 7bc3d4e102..2fee91ce76 100644 --- a/Source/Csla/NameValueListBase.cs +++ b/Source/Csla/NameValueListBase.cs @@ -324,7 +324,7 @@ public async Task WaitForIdle() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -336,7 +336,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] diff --git a/Source/Csla/ReadOnlyBase.cs b/Source/Csla/ReadOnlyBase.cs index 9623913516..8a0df2fbc7 100644 --- a/Source/Csla/ReadOnlyBase.cs +++ b/Source/Csla/ReadOnlyBase.cs @@ -429,7 +429,7 @@ public T Clone() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -441,7 +441,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -466,7 +466,7 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -477,7 +477,7 @@ protected virtual void Child_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] diff --git a/Source/Csla/ReadOnlyBindingListBase.cs b/Source/Csla/ReadOnlyBindingListBase.cs index 2e59fa8b75..71f1916a38 100644 --- a/Source/Csla/ReadOnlyBindingListBase.cs +++ b/Source/Csla/ReadOnlyBindingListBase.cs @@ -96,7 +96,7 @@ public T Clone() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -108,7 +108,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -133,7 +133,7 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -144,7 +144,7 @@ protected virtual void Child_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] diff --git a/Source/Csla/ReadOnlyListBase.cs b/Source/Csla/ReadOnlyListBase.cs index 30ff10740f..46d20d9a6e 100644 --- a/Source/Csla/ReadOnlyListBase.cs +++ b/Source/Csla/ReadOnlyListBase.cs @@ -122,7 +122,7 @@ public C[] ToArray() /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -134,7 +134,7 @@ protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_xyz method. + /// requested data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -159,7 +159,7 @@ protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, E /// /// Called by the server-side DataPortal prior to calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] @@ -170,7 +170,7 @@ protected virtual void Child_OnDataPortalInvoke(DataPortalEventArgs e) /// /// Called by the server-side DataPortal after calling the - /// requested DataPortal_XYZ method. + /// requested child data portal operation method. /// /// The DataPortalContext object passed to the DataPortal. [SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")] diff --git a/Source/Csla/RunLocalAttribute.cs b/Source/Csla/RunLocalAttribute.cs index 70d2abd8e4..c2f087bf3d 100644 --- a/Source/Csla/RunLocalAttribute.cs +++ b/Source/Csla/RunLocalAttribute.cs @@ -3,13 +3,13 @@ // Copyright (c) Marimer LLC. All rights reserved. // Website: https://cslanet.com // -// Marks a DataPortal_XYZ method to +// Marks a data portal operation method to //----------------------------------------------------------------------- namespace Csla { /// - /// Marks a DataPortal_XYZ method to + /// Marks a data portal operation method to /// be run on the client even if the server-side /// DataPortal is configured for remote use. /// diff --git a/Source/Csla/TransactionalAttribute.cs b/Source/Csla/TransactionalAttribute.cs index 1482dc6fba..603c13dd23 100644 --- a/Source/Csla/TransactionalAttribute.cs +++ b/Source/Csla/TransactionalAttribute.cs @@ -3,7 +3,7 @@ // Copyright (c) Marimer LLC. All rights reserved. // Website: https://cslanet.com // -// Marks a DataPortal_XYZ method to run within +// Marks a data portal operation method to run within //----------------------------------------------------------------------- using System.Transactions; @@ -11,7 +11,7 @@ namespace Csla { /// - /// Marks a DataPortal_XYZ method to run within + /// Marks a data portal operation method to run within /// the specified transactional context. /// /// @@ -23,7 +23,7 @@ namespace Csla /// TransactionalTypes enum. /// /// If the Transactional attribute is not applied to a - /// DataPortal_XYZ method then the + /// data portal operation method then the /// Manual option /// is assumed. ///