Can a field be calculated based off another field? - dynamics-ax-2009

In Dynamics Ax 2009, we want a field that will be calculated based off another field in the same table.
Although it might be nice to use a display method or something, we have to actually store the value (management is scared of dynamic calculations because the previous product used them and was slow (like anything in AX is fast!)).
To put the icing on the cake, they want it to work two-way:
If we have FieldA, calculate and store FieldB.
If we have FieldB, calculate and store FieldA.
If we have both or none, do nothing.
Is there anything built into Dynamics AX that can help me?

First, override table's method insert(), e.g.:
public void insert()
{
;
this.FieldB = this.FieldA * 2;
super();
}
Then override update(), e.g.:
public void update()
{
if (this.FieldA == this.orig().FieldA && this.FieldB != this.orig().FieldB)
{
this.FieldA = this.FieldB / 2;
}
else
{
this.FieldB = this.FieldA * 2;
}
super();
}
These are only examples, use your own judgement how the methods should be overridden. Lastly, override modifiedField(), which will be used only when the fields are modified manually in forms:
public void modifiedField(fieldId _fieldId)
{
;
super(_fieldId);
switch (_fieldId)
{
case fieldnum(MyTable, FieldA) :
this.FieldB = this.FieldA * 2;
break;
case fieldnum(MyTable, FieldB) :
this.FieldA = this.FieldB / 2;
break;
}
if (this.isFormDataSource())
this.dataSource().refresh();
}
P.S. Keep in mind that insert() and update() are not called when you are using doinsert(), doupdate(), or skipDataMethods().

See another related answer here:
Automatic field values changed according to master table field modified In Axapta

Related

LINQ Distinct does not invoke IEquatable<T>.Equals

I have a set of domain object, deriving from a base, where I've overridden Equals, IEquatable<T>.Equals and equality operators. I've successfully used Contains, but now I am trying to use Distinct differently. Here's look at a sample code:
var a = new Test { Id = 1 };
var a2 = new Test { Id = 1 };
var list = new List<Test> { a, a2 };
var distinct = list.Distinct().ToList(); // both objects, Equal implementations not called
var containsA = list.Contains(a); // true, Equal implementations called
var containsA2 = list.Contains(a); // true
var containsNewObjectWithSameId = list.Contains(new Test { Id = 1 }); // true
public class Test : IEquatable<Test>
{
public int Id { get; init; }
public bool Equals(Test other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
if (this.GetType() != other.GetType())
return false;
return this.Id == other.Id;
}
public override int GetHashCode() => base.GetHashCode + this.Id;
}
Contains finds matches, but Distinct is feeling very inclusive and keeps them both. From MS docs:
The first search does not specify any equality comparer, which means FindFirst uses
EqualityComparer.Default to determine equality of boxes. That in turn uses the implementation
of the IEquatable.Equals method in the Box class.
What am I missing?
Thanks #JonSkeet for your insight in the comments.
The problem in this case is the way I wrote my GetHashCode method. It has nothing to do with LINQ, as I originally thought.
Explanation
GetHashCode has to be identical for objects that compare equally. In my case - since the base implementation of object.Equals only checks for reference equality and I am comparing two separate objects - a and b, their base.GetHashCode would result in different values, which in turn would render those two objects as not equal.
Solution
In this case, simply returning the Id value is enough as is shown in MS docs:
One of the simplest ways to compute a hash code for a numeric value that has the same or a smaller range than the Int32 type is to simply return that value.
So changing the above code sample like this:
public override int GetHashCode() => this.Id;
would solve the issue. Please keep in mind that if the value of Id is not unique, this will cause ill behavior. In such cases you'll need another property to check and you will have to compose GetHashCode from ALL those properties. For further info refer to MS docs

override GetAll to change Sort - example required

I am trying to override an AsyncCrudAppService call - "GetAll" in order to change the returned sort order.
Firstly, is overriding the method, in fact, the correct way to do this?
If yes, could I have an example as I'm a bit lost on how to do this with a PagedResultDto<T> return type?
If not please let me know where I can find out how.
Technically you can, but really shouldn't do sorting in GetAll() if it can be avoided. CRUD App Services also define virtual method ApplySorting() which is already used by GetAll and can also be overridden.
You can pass the string to dynamically sort by when calling the GetAll() and it will already work.
await _myAppService.GetAll(new PagedAndSortedResultRequestDto() { Sorting = "Name DESC", MaxResultCount = pageSize, SkipCount = skipCount })
If you'd like to have more control over sorting behavior, like use something default or pass in better-formatted query strings, override ApplySorting()
protected override IQueryable<SomeEntity> ApplySorting(IQueryable<SomeEntity> query, PagedAndSortedResultRequestDto input)
{
var sortBy = "LastModificationTime DESC,CreationTime DESC";
switch (input.Sorting?.ToLower())
{
case "name-asc":
sortBy = "Name";
break;
case "name-desc":
sortBy = "Name DESC";
break;
}
input.Sorting = sortBy;
return base.ApplySorting(query, input);
}

.Max() method giving error when used in an if else

My application is in Asp.Net coded in C# and i'm using LINQ for database transactions. My requirement is to get the Max value of the records saved in a certain table, for this i'm using Max() method.
Below is my controller code :
[HttpPost]
public ActionResult Create(Entity_Name Entity_Object)
{
if (Entity_Object.Condition == true)
{
My required code
}
else
{
var get_Max_Number = db.Entity_Name.ToList();
long Max_Number = 0;
if (get_Max_Number.Count() > 0)
{
Max_Number = Convert.ToInt64(get_Max_Number.Max());
}
My required code
}
}
My issue is when i remove the If-else condition then the same Max() method query works perfect, but when i add the If-else statement then i gets the following error.
Error:
At least one object must implement IComparable.
What i tried :
I attempted to remove the If-Else
I placed the Max() method logic above the If-else
Placing the Max() method above If-Else
[HttpPost]
public ActionResult Create(Entity_Name Entity_Object)
{
var get_Max_Number = db.Entity_Name.ToList();
long Max_Number = 0;
if (get_Max_Number.Count() > 0)
{
Max_Number = Convert.ToInt64(get_Max_Number.Max());
}
if (Entity_Object.Condition == true)
{
My required code
}
else
{
My required code
}
}
Max() needs to know what you're getting the maximum of. If you're Entity_Name class contains a number of properties (strings, ints etc...) then you need to tell it what to get the Maximum on the basis of.
Another thing, you're connecting to a DB via Linq from the looks of things, but executing your Count() & Max() functions in memory after you've retrieved the entire contents of the database table. This will be very inefficient as the table grows in size. LinqToSql & LinqToEF support pushing those functions down to the database level. I'd recommend changing your code to the following.
[HttpPost]
public ActionResult Create(Entity_Name Entity_Object)
{
if (Entity_Object.Condition == true)
{
//My required code
}
else
{
long Max_Number = 0;
if(db.Entity_Name.Count() > 0)
{
Max_Number = Convert.ToInt64(
db.Entity_Name.Max(x => x.PropertyToGetMaxOf)
);
}
//My required code
}
}

Converting string to int, array of strings

I am having an issue with converting a string of id to an int when there are multiple strings passed in, the code shows the following:
for(int i = 0; i < Request.Params["service"].Length; i++)
{
int serviceID = int.Parse(Request.Params["service"]);
db.ServiceAssignments.Add(serviceAssignment);
serviceAssignment.locationID = locationID;
serviceAssignment.ServiceID = serviceID;
db.SaveChanges();
}
If you pass in one param, you get: int.Parse(Request.Params["Service"]); = 1, which then works and the database saves. however if you have the following you get:
'1,2' which three. What I want is 1 and then 2, not 1,2.
What is 1 and 2?
When you create anew location you get to select services for that location. The service id, in the case of this problem is 1 and 2. if I select one service then it saves and all is well. When I select two or more it doesnt work.
I though I could do:
Request.Params["Service"][i] because "Service" is an array after all. How ever this causes database problems and a whole other mess.
So what would you suggest I can do to make it save id 1 and id 2 when you select them for a location?
MVC 3 is quite powerful to figure out the binding, I don't know exactly what are you doing in the view that get the service Ids from user but I assume there is form there and if all Ids are int you can do like this and you don't need any conversion, or maybe you can use FormCollection. I don't think using Request in MVC 3 is a good idea, it does not really belong the whole MVC idea.
public void Add(int[] service)
{
foreach (var item in service)
{
int serviceID = item;
}
}
OR
public void Add(FormCollection frm)
{
foreach (var item in frm.AllKeys)
{
if (item.StartsWith("service"))
{
int serviceID = Int32.Parse(frm[item]);
}
}
}
anyway none of these are also MVC, these are should work but I recommend you to use Models in views and controllers
This will work. Just tested it:
string[] items = Request.Params["service"].Split(',');
for (int i = 0; i < items.Length; i++)
{
int serviceID = int.Parse(items[i]);
db.ServiceAssignments.Add(serviceAssignment);
serviceAssignment.locationID = locationID;
serviceAssignment.ServiceID = serviceID;
db.SaveChanges();
}
As a side note, I'd probably make two changes:
I'd use a foreach statement. No real difference; just less typing.
I'd put the SaveChanges() AFTER the for loop. This will make fewer calls to the database, but still accomplish the same thing.

How to call delegate only once / one time with moles?

How is it possible to call a delegated Method only once / one time with moles?
MyClass.AllInstances.ResultateGet = delegate { return new ResultatInfoCollection(); };
I want to call the Method "ResultateGet" only one time because the init is quite complex the first time without a delegate.
target.UpdateResultate(); //calls delegate "ResultateGet"
//Assert some stuff
target.Verify(); //needs original function "ResultateGet" so unit test is useful
I am generally interested how to call a moles delegate one time ore a specific number of times before the original function is called and not the delegate.
Update:
I found a way, that seems a little bit cumbersome. Any better Solution?
ResultatInfoCollection x = new ResultatInfoCollection();
MolesContext.ExecuteWithoutMoles(() => x = target.Resultate);
Also, see my answer to: How to assign/opt from multiple delegates for a 'moled' method? This provides an example of gating logic inside the anonymous method.
Ooh, good question! I have encountered this, myself. What you are looking for is called a "fallthrough" behavior (execution of the original code). The anonymous method to which Moles detours must contain a switching mechanism that falls through, after the first call. Unfortunately, I don't believe a fallthrough feature is included in Moles, at this time.
Your updated workaround is exactly what you need -- calling fallthrough would do the same thing. I suggest adding a sentinel value, doFallthrough, that gates the calls:
bool doFallthrough = false;
ResultatInfoCollection x = new ResultatInfoCollection();
MyClass.AllInstances.ResultateGet = delegate {
if (!doFallthrough)
{
doFallthrough = true;
return new ResultatInfoCollection();
}
MolesContext.ExecuteWithoutMoles(() => x = target.Resultate);
};
Calling a specific number of times simply requires a change to the sentinel value type:
int doFallthrough = 0;
ResultatInfoCollection x = new ResultatInfoCollection();
MyClass.AllInstances.ResultateGet = delegate {
if (++doFallthrough < 5)
return new ResultatInfoCollection();
MolesContext.ExecuteWithoutMoles(() => x = target.Resultate);
};
Old question, but since I found it when I was searching, I'll answer it for the next person with my solution.
Using MolesContext.ExecuteWithoutMoles to call the original function works just fine in most cases, however, if you are moling any other functions or classes downstream from this call, they won't be moled, either.
Given the following class:
public class TheClass
{
public int TheFunction(int input){
return input + TheOtherFunction();
}
public int TheOtherFunction(){
return DateTime.Now.Minutes;
}
}
If you use the MolesContext.ExecuteWithoutMoles approach:
MTheClass.AllInstances.TheOtherFunctionInt = (instance) => {
return 5;
};
MTheClass.AllInstances.TheFunctionInt = (instance, input) =>
{
//do your stuff here, for example:
Debug.WriteLine(input.ToString());
var result = MolesContext.ExecuteWithoutMoles<int>(() => instance.TheFunction(input));
//do more stuff, if desired
return result;
};
Your mole for OtherFunction will not be hit, because it was (indirectly) executed within the "without moles" scope.
However, you can add and remove moles delegates at any time, so that allows you to do the following, as outlined in the Moles Documentation (p. 24)
MTheClass.AllInstances.TheOtherFunctionInt = (instance) => {
return 5;
};
MolesDelegates.Func<TheClass, int, int> molesDelegate = null;
molesDelegate = (instance, input) =>
{
//do your stuff here, for example:
Debug.WriteLine(input.ToString());
int result = 0;
try{
MTheClass.AllInstances.TheFunctionInt = null;
result = instance.TheFunction(input);
}
finally{
MTheClass.AllInstances.TheFunctionInt = molesDelegate;
}
//do more stuff, if desired
return result;
};
MTheClass.AllInstances.TheFunctionInt = molesDelegate;
The OtherFunction moles is still hit. With this method, you can remove moling just from the specific method without impacting your other moles. I've used this, and it works. The only trouble I can see is that it won't work if you have a recursive function, or possibly a multi-threaded situation.

Resources