Acumatica - Cross Referencing - foreign-keys

My DAC and tables are defined as follows
Data structure
ParentTableDAC (FormView)
ChildDac1 (TAb1/Grid)
ChildDac2 (Tab2/Grid)
in ChildDAc2, ChildDAc1ID need to be shown as Selector, How this can be done?
We are facing issue if Data is not saved for Parent/ChildDAc1 then it is not available to ChildDAc2 Lookup
Update -
Business Scenario -
A work item has Multiple Task and multiple Steps to perform the work item.
now WORKITEMDAC is a Parent
TASKDAC and STEPDAC are the Child of the Parent WORKITEMDAC.
OK till yet everything is ok and Usual..
Now each step is suppose to be linked with the Task of the Parent WORKITEM.
SO in STEPS Grid a Selector is required to select the TASK.
Here I have proper Parent child relationship there is no issue with that,I have only one issue that is, I can select only the TASK which was already saved with the Parent WORKITEM, unsaved Tasks are not displayed in the selector.
SO My Question was, do we have any way to get the Task in the PXSelector query which is not yet saved?
Following Selector is used on STEPDAC on TaskID Column -
[PXSelector(typeof(Search<TASKDAC.TaskID, Where<TASKDAC.taskID, Equal<Current>>>), typeof(TASKDAC.taskCD), SubstituteKey = typeof(TASKDAC.taskCD))]
Note - TaskID column in TASKDAC is identity column and this DAC has WorkItemID defined as PArent.

Update
Thanks for updating with more detail. Leaving the original response below for those that may need it regarding parent/child.
I've never seen the selector defined with reference to Current<> and no Current WHAT. To be honest, I'm surprised it compiled. "Where Field = Current View Field" directs the selector to limit results to only values associated to the Current value of whatever field you specified, and it is retrieved from the "ViewName.Current" record in the graph. Your where clause does not tell it what "current" to match. Since you direct the selector to Search, I suspect that this is causing the selector to be unable to find a matching record and short-circuiting the save of the result for that field.
It sounds like TASKDAC is a "master data" type record, defining all possible tasks to be performed. Therefore, TaskID is not a key field in STEPDAC but rather a key field in TASKDAC. If you want to select "any" task, you don't need the where clause at all.
[PXSelector(
typeof(TASKDAC.TaskID),
typeof(TASKDAC.taskCD),
SubstituteKey = typeof(TASKDAC.taskCD)
)]
Let's assume for a moment that you designate a WORKITEMDAC.WorkOrderType that is used to filter acceptable tasks by the same field. (Only repair tasks can be assigned to a repair WorkOrderType, assembly to an assembly WorkOrderType, etc.) In this case, you would use the syntax you stated, but you would designate Where<TASKDAC.WorkOrderType, Equal<Current<WORKITEMDAC.WorkOrderType>>. If the task is limited to something in the STEPDAC instead, just swap out WORKITEMDAC with the STEPDAC field in the example below.
[PXSelector(
typeof(Search<TASKDAC.TaskID,
Where<TASKDAC.WorkOrderType, Equal<Current<WORKITEMDAC.WorkOrderType>>>>),
typeof(TASKDAC.taskCD),
SubstituteKey = typeof(TASKDAC.taskCD)
)]
Please update your PXSelector and advise if that fixes the issue or what error you get next.
Original Response
With so little to your question, the nature of your question is very unclear. It sounds like you are attempting to do something that should perhaps take a different approach. The definition of a selector should not impact the saving of a field, but the structure of a parent child relationship is critical to define properly. As such, it seems very odd that you would have a Child ID that differs from the parent and that it would be used in a selector.
Typically, the child DAC shares an ID with the parent DAC. We often define a LineNbr field in the child which is given a value by PXLineNbrAttribute, and the Key fields would be ID + LineNbr to identify a specific child record. We also set SyncPosition = true in the ASPX document on the grid so that the user interface stays completely sync'd with whichever record the user has selected in the grid. Parent Child relationships can be daisy chained, such as with SOOrder -> SOLine -> SOLineSplit, but ultimately, the ID field of a child is not a selector... the ID of the parent is.
Think of a selector as a means of looking up master data. Master data may be related to other data, but it would not have a parent in most cases. There are exceptions, but let's keep it simple. A purchase order, for instance, would not have parent, and the key field would be a common selector field. However, since a child must always relate to its parent and cannot change its parent, the ID of the child field would not be a selector. Other tables in Acumatica would refer back to some field or combination of fields to relate to the data of a child DAC.
From the T210 training guides, I stripped away a lot to show just this point.
RSSVRepairPrice - Parent
using System;
using PX.Data;
namespace PhoneRepairShop
{
[PXCacheName("Repair Price")]
public class RSSVRepairPrice : IBqlTable
{
#region PriceID
[PXDBIdentity]
public virtual int? PriceID { get; set; }
public abstract class priceID : PX.Data.BQL.BqlInt.Field<priceID> { }
#endregion
#region ServiceID
[PXDBInt(IsKey = true)]
[PXDefault]
[PXUIField(DisplayName = "Service", Required = true)]
[PXSelector(
typeof(Search<RSSVRepairService.serviceID>),
typeof(RSSVRepairService.serviceCD),
typeof(RSSVRepairService.description),
DescriptionField = typeof(RSSVRepairService.description),
SelectorMode = PXSelectorMode.DisplayModeText)]
public virtual int? ServiceID { get; set; }
public abstract class serviceID : PX.Data.BQL.BqlInt.Field<serviceID> { }
#endregion
#region RepairItemLineCntr
[PXDBInt()]
[PXDefault(0)]
public virtual Int32? RepairItemLineCntr { get; set; }
public abstract class repairItemLineCntr : PX.Data.BQL.BqlInt.Field<repairItemLineCntr> { }
#endregion
}
}
RSSVRepairItem - Child
using System;
using PX.Data;
using PX.Objects.IN;
using PX.Data.BQL.Fluent;
namespace PhoneRepairShop
{
[PXCacheName("Repair Item")]
public class RSSVRepairItem : IBqlTable
{
#region ServiceID
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(RSSVRepairPrice.serviceID))]
[PXParent(
typeof(SelectFrom<RSSVRepairPrice>.
Where<RSSVRepairPrice.serviceID.IsEqual<
RSSVRepairItem.serviceID.FromCurrent>>
))]
public virtual int? ServiceID { get; set; }
public abstract class serviceID : PX.Data.BQL.BqlInt.Field<serviceID> { }
#endregion
#region LineNbr
[PXDBInt(IsKey = true)]
[PXLineNbr(typeof(RSSVRepairPrice.repairItemLineCntr))]
[PXUIField(DisplayName = "Line Nbr.", Visible = false)]
public virtual int? LineNbr { get; set; }
public abstract class lineNbr : PX.Data.BQL.BqlInt.Field<lineNbr> { }
#endregion
}
}
In the parent, notice the use of PXDBIdentityAttribute which specifies that the record identity is set by the database in PriceID. The "ID" field used by the child is taken from the ServiceID field which is defined as a selector for another master table. The use of IsKey = true on PXDBIntAttribute tells Acumatica that it can find the unique record by the combination of all the fields marked IsKey = true. (Note that the actual training guide includes another field as part of the key, but I simplified the example here.) Also notice the use of a PDXBInt field that has a default of 0 to be used as a Line Counter. (RepairItemLineCtr)
Now, in the child, notice that the key field of the parent is defined again in the DAC, but without the selector. The PXParentAttribute defines the relationship back to the parent DAC, and the field value is defaulted in from that parent. This is what causes the value to be saved in the record automatically. It also associates the relationship so that deleting the parent will delete the child as well. Notice the use of PXLineNbrAttribute to autoincrement the field of the parent DAC and set the value in the LineNbr field. It is the combination of these 2 fields that points you to the exact record.
There are cases where Acumatica uses the NoteID field (a unique GUID value) to identify a record of a child DAC, but this is a bit more complex. You can see it in the relationship of INItemPlan (Demand) to Purchase Orders.
If you are having trouble with Parent-Child (Master-Detail) relationships, I'd highly recommend reviewing the T210 training course.

To refer the unsaved data use the ISDirty =true on PXSelector.
also to save the Identity key valueof the unsaved data at referenced place, use [PXDBDefault()]

Related

Cleanest way to identify an entity with Doctrine inheritance mapping in zf2

I am using Doctrine inheritance mapping in a project which produces a set of unique entities that each extend a base entity. Because the route is not aware of which entities go with which base rows, I have to query the database twice in order to grab the row I want from the right fieldset:
// in a controller action:
// locate the event entity record and determine the event type
$entity = 'AdminEvents\Entity\Event';
$event = $this->getEntityManager()->find($entity, $eventID);
$eventType = $this->getEntityManager()->getClassMetadata(get_class($event))->discriminatorValue;
// locate the record we're really looking for in the unique extended entity
$entity = 'AdminEvents\Entity\\' . $eventType;
$event = $this->getEntityManager()->find($entity, $eventID);
Is there a cleaner way to do this?
You should probably define an \AdminEvents\Entity\AbsractEvent class, if you haven't already. Then each of your STI entities should extend this, and you can do instanceof (or other logic) to find out what concrete type you got:
// locate the record using the AbstractEntity
$entity = 'AdminEvents\Entity\AbstractEntity';
$event = $this->getEntityManager()->find($entity, $eventID);
A word of caution: the SPL function, get_class will often return the Doctrine Proxy class, so don't rely on that directly to test the return type. You can use the Doctrine class 'ClassUtils'
\Doctrine\Common\Util\ClassUtils::getRealClass(get_class($event));

Map two templates for different sites

We are developing a multisite sitecore solution where each sites can have have their own News as well as able to display the combined news from other Sites.
Problem:
Each site have their unique News requirements where 90% of the template fields matches but rest 10% are different.
For example, Site-A has news template with Authors drop down list where Author List are authored on Configuration Node. Where as Site-B has news template where Author is a FREE TEXT Field.
Therefore, when Glass Mapper automatically tries to Map Authors field it fails for Free Text one.
Solution:
This can be resolved either by creating a Author as drop down on all Sites but Product owners don't want this.
The other solution is manual mapping of news fields from both sources or use AUTOMAP etc.
Desired Solution:
Glassmapper automatically resolves and populates the Author Text Field or Drop Down Field on the fly.
Is above possible?
Thank you.
I would solve this by "fluent configuration", http://glass.lu/Mapper/Sc/Tutorials/Tutorial8.aspx.
Combined with the new Delegate feature added to the Glass Mapper recently.
The Delegate feature was originally introduced and described here: http://cardinalcore.co.uk/2014/07/02/controlling-glass-fields-from-your-own-code/
Nuget package for the Delegate feature: https://www.nuget.org/packages/Cardinal.Glass.Extensions.Mapping/
You can use Infer types as follows:
public interface IBaseNews
{
string Author {get; set;}
//List all other shared fields below
}
[SitecoreType(TemplateId="....", AutoMap = true)]
public class NewsSiteA : IBaseNews
{
[SitecoreField]
public string Author {get; set;}
//List all fields which are unique for SiteA
}
[SitecoreType(TemplateId="....", AutoMap = true)]
public class NewsSiteB : IBaseNews
{
[SitecoreField]
public string Author {get; set;}
//List all fields which are unique for SiteB
}
Now, Your code should be:
IBaseNews newsClass = NewsItem.GlassCast<IBaseNews>(true,true);
//You can use Author property now
Firstly, I would recommend updating to the latest version of Glass for many other reasons including the delegate feature.
From the infer type example in the comment - you shouldn't use GlassCast, use CreateType(Item item) from the sitecore service / context. If you adopt the version with Delegate in, there is now an official Cast(Item item) on the sitecore service instead.
Also the example there uses a would not solve the difference in type. Delegate would make this very easy. Remember with delegate that there is no lazy loading, this shouldn't matter in this case.
public interface INews
{
// All my other fields
string Author { get; set; }
}
The fluent configuration would be something like (to be done in GlassScCustom)
SitecoreType<INews> = new SitecoreType<INews>();
sitecoreType.Delegate(y => y.Author).GetValue(GetAuthor);
fluentConfig.Add(sitecoreType);
private string GetAuthor(SitecoreDataMappingContext arg)
{
Item item = arg.Item;
if(item.TemplateID == <templateid>)
{
// return the value from the drop link
}
return item["Authors"];
}

EF Code First issue on CommitTransaction - using Repository pattern

I am having an issue with EF 4.1 using "Code First". Let me setup my situation before I start posting any code. I have my DBContext class, called MemberSalesContext, in a class library project called Data.EF. I have my POCOs in a seperate class library project called Domain. My Domain project knows nothing of Entity Framework, no references, no nothing. My Data.EF project has a reference to the Domain project so that my DB context class can wire up everything in my mapping classes located in Data.EF.Mapping. I am doing all of the mappings in this namespace using the EntityTypeConfiguration class from EntityFramework. All of this is pretty standard stuff. On top of Entity Framework, I am using the Repository pattern and the Specification pattern.
My SQL Server database table has a composite primary key defined. The three columns that are part of the key are Batch_ID, RecDate, and Supplier_Date. This table as an identity column (database generated value => +1) called XREF_ID, which is not part of the PK.
My mapping class, located in Data.EF.Mapping looks like the following:
public class CrossReferenceMapping : EntityTypeConfiguration<CrossReference>
{
public CrossReferenceMapping()
{
HasKey(cpk => cpk.Batch_ID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.Supplier_Date);
Property(p => p.XREF_ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("wPRSBatchXREF");
}
}
My MemberSalesContext class (inherits from DBContext) looks like the following:
public class MemberSalesContext : DbContext, IDbContext
{
//...more DbSets here...
public DbSet<CrossReference> CrossReferences { get; set; }
//...more DbSets here...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
//...more modelBuilder here...
modelBuilder.Configurations.Add<CrossReference>(new CrossReferenceMapping());
//...more modelBuilder here...
}
}
I have a private method in a class that uses my repository to return a list of objects that get iterated over. The list I am referring to is the outermost foreach loop in the example below.
private void CloseAllReports()
{
//* get list of completed reports and close each one (populate batches)
foreach (SalesReport salesReport in GetCompletedSalesReports())
{
try
{
//* aggregate sales and revenue by each distinct supplier_date in this report
var aggregates = BatchSalesRevenue(salesReport);
//* ensure that the entire SalesReport breaks out into Batches; success or failure per SalesReport
_repository.UnitOfWork.BeginTransaction();
//* each salesReport here will result in one-to-many batches
foreach (AggregateBySupplierDate aggregate in aggregates)
{
//* get the batch range (type) from the repository
BatchType batchType = _repository.Single<BatchType>(new BatchTypeSpecification(salesReport.Batch_Type));
//* get xref from repository, *if available*
//* some will have already populated the XREF
CrossReference crossReference = _repository.Single<CrossReference>(new CrossReferenceSpecification(salesReport.Batch_ID, salesReport.RecDate, aggregate.SupplierDate));
//* create a new batch
PRSBatch batch = new PRSBatch(salesReport,
aggregate.SupplierDate,
BatchTypeCode(batchType.Description),
BatchControlNumber(batchType.Description, salesReport.RecDate, BatchTypeCode(batchType.Description)),
salesReport.Zero_Sales_Flag == false ? aggregate.SalesAmount : 1,
salesReport.Zero_Sales_Flag == false ? aggregate.RevenueAmount : 0);
//* populate CrossReference property; this will either be a crossReference object, or null
batch.CrossReference = crossReference;
//* close the batch
//* see PRSBatch partial class for business rule implementations
batch.Close();
//* check XREF to see if it needs to be added to the repository
if (crossReference == null)
{
//*add the Xref to the repository
_repository.Add<CrossReference>(batch.CrossReference);
}
//* add batch to the repository
_repository.Add<PRSBatch>(batch);
}
_repository.UnitOfWork.CommitTransaction();
}
catch (Exception ex)
{
//* log the error
_logger.Log(User, ex.Message.ToString().Trim(), ex.Source.ToString().Trim(), ex.StackTrace.ToString().Trim());
//* move on to the next completed salesReport
}
}
}
All goes well on the first iteration of the outer loop. On the second iteration of the outer loop, the code fails at _repository.UnitOfWork.CommitTransaction(). The error message returned is the following:
"The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges."
In this situation, the database changes on the second iteration were not committed successfully, but the changes in the first iteration were. I have ensured that objects in the outer and inner loops are all unique, adhering to the database primary keys.
Is there something that I am missing here? I am willing to augment my code samples, if it proves helpful. I have done everything within my capabilities to troubleshoot this issue, minus modifying the composite primary key set on the database table.
Can anyone help??? Much thanks in advance! BTW, sorry for the long post!
I am answering my own question here...
My issue had to do with how the composite primary key was being defined in my mapping class. When defining a composite primary key using EF Code First, you must define it like so:
HasKey(cpk => new { cpk.COMPANYID, cpk.RecDate, cpk.BATTYPCD, cpk.BATCTLNO });
As opposed to how I had it defined previously:
HasKey(cpk => cpk.COMPANYID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.BATTYPCD);
HasKey(cpk => cpk.BATCTLNO);
The error I was receiving was that the ObjectContext contained multiple elements of the same type that were not unique. This became an issue in my UnitOfWork on CommitTransaction. This is because when the mapping class was instanciated from my DBContext class, it executed 4 HasKey statements shown above, with only the last one for property BATCTLNO becoming the primary key (not composite). Defining them inline, as in my first code sample above, resolves the issue.
Hope this helps someone!

How can I avoid duplicating data in a document database like RavenDB?

Given that document databases, such as RavenDB, are non-relational, how do you avoid duplicating data that multiple documents have in common? How do you maintain that data if it's okay to duplicate it?
With a document database you have to duplicate your data to some degree. What that degree is will depend on your system and use cases.
For example if we have a simple blog and user aggregates we could set them up as:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
public class Blog
{
public string Id { get; set; }
public string Title { get; set; }
public class BlogUser
{
public string Id { get; set; }
public string Name { get; set; }
}
}
In this example I have nested a BlogUser class inside the Blog class with the Id and Name properties of the User Aggregate associated with the Blog. I have included these fields as they are the only fields the Blog class is interested in, it doesn't need to know the users username or password when the blog is being displayed.
These nested classes are going to dependant on your systems use cases, so you have to design them carefully, but the general idea is to try and design Aggregates which can be loaded from the database with a single read and they will contain all the data required to display or manipulate them.
This then leads to the question of what happens when the User.Name gets updated.
With most document databases you would have to load all the instances of Blog which belong to the updated User and update the Blog.BlogUser.Name field and save them all back to the database.
Raven is slightly different as it support set functions for updates, so you are able to run a single update against RavenDB which will up date the BlogUser.Name property of the users blogs without you have to load them and update them all individually.
The code for doing the update within RavenDB (the manual way) for all the blog's would be:
public void UpdateBlogUser(User user)
{
var blogs = session.Query<Blog>("blogsByUserId")
.Where(b.BlogUser.Id == user.Id)
.ToList();
foreach(var blog in blogs)
blog.BlogUser.Name == user.Name;
session.SaveChanges()
}
I've added in the SaveChanges just as an example. The RavenDB Client uses the Unit of Work pattern and so this should really happen somewhere outside of this method.
There's no one "right" answer to your question IMHO. It truly depends on how mutable the data you're duplicating is.
Take a look at the RavenDB documentation for lots of answers about document DB design vs. relational, but specifically check out the "Associations Management" section of the Document Structure Design Considerations document. In short, document DBs use the concepts of reference by IDs when they don't want to embed shared data in a document. These IDs are not like FKs, they are entirely up to the application to ensure the integrity of and resolve.

How do I update with a newly-created detached entity using NHibernate?

Explanation:
Let's say I have an object graph that's nested several levels deep and each entity has a bi-directional relationship with each other.
A -> B -> C -> D -> E
Or in other words, A has a collection of B and B has a reference back to A, and B has a collection of C and C has a reference back to B, etc...
Now let's say I want to edit some data for an instance ofC. In Winforms, I would use something like this:
var instanceOfC;
using (var session = SessionFactory.OpenSession())
{
// get the instance of C with Id = 3
instanceOfC = session.Linq<C>().Where(x => x.Id == 3);
}
SendToUIAndLetUserUpdateData(instanceOfC);
using (var session = SessionFactory.OpenSession())
{
// re-attach the detached entity and update it
session.Update(instanceOfC);
}
In plain English, we grab a persistent instance out of the database, detach it, give it to the UI layer for editing, then re-attach it and save it back to the database.
Problem:
This works fine for Winform applications because we're using the same entity all throughout, the only difference being that it goes from persistent to detached to persistent again.
The problem is that now I'm using a web service and a browser, sending over JSON data. The entity gets serialized into a string, and de-serialized into a new entity. It's no longer a detached entity, but rather a transient one that just happens to have the same ID as the persistent one (and updated fields). If I use this entity to update, it will wipe out the relationship to B and D because they don't exist in this new transient entity.
Question:
My question is, how do I serialize detached entities over the web to a client, receive them back, and save them, while preserving any relationships that I didn't explicitly change? I know about ISession.SaveOrUpdateCopy and ISession.Merge() (they seem to do the same thing?), but this will still wipe out the relationships if I don't explicitly set them. I could copy the fields from the transient entity to the persistent entity one by one, but this doesn't work too well when it comes to relationships and I'd have to handle version comparisons manually.
I solved this problem by using an intermediate class to hold data coming in from the web service, then copying its properties to the database entity. For example, let's say I have two entities like so:
Entity Classes
public class Album
{
public virtual int Id { get; set; }
public virtual ICollection Photos { get; set; }
}
public class Photo
{
public virtual int Id { get; set; }
public virtual Album Album { get; set; }
public virtual string Name { get; set; }
public virtual string PathToFile { get; set; }
}
Album contains a collection of Photo objects, and Photo has a reference back to the Album it's in, so it's a bidirectional relationship. I then create a PhotoDTO class:
DTO Class
public class PhotoDTO
{
public virtual int Id { get; set; }
public virtual int AlbumId { get; set; }
public virtual string Name { get; set; }
// note that the DTO does not have a PathToFile property
}
Now let's say I have the following Photo stored in the database:
Server Data
new Photo
{
Id = 15,
Name = "Fluffy Kittens",
Album = Session.Load<Album>(3)
};
The client now wants to update the photo's name. They send over the following JSON to the server:
Client Data
PUT http://server/photos/15
{
"id": 15,
"albumid": 3,
"name": "Angry Kittens"
}
The server then deserializes the JSON into a PhotoDTO object. On the server side, we update the Photo like this:
Server Code
var photoDTO = DeserializeJson();
var photoDB = Session.Load(photoDTO.Id); // or use the ID in the URL
// copy the properties from photoDTO to photoDB
photoDB.Name = photoDTO.Name;
photoDB.Album = Session.Load<Album>(photoDTO.AlbumId);
Session.Flush(); // save the changes to the DB
Explanation
This was the best solution I've found because:
You can choose which properties the client is allowed to modify. For example, PhotoDTO doesn't have a PathToFile property, so the client can never modify it.
You can also choose whether to update a property or not. For example, if the client didn't send over an AlbumId, it will be 0. You can check for that and not change the Album if the ID is 0. Likewise, if the user doesn't send over a Name, you can choose not to update that property.
You don't have to worry about the lifecycle of an entity because it will always be retrieved and updated within the scope of a single session.
AutoMapper
I recommend using AutoMapper to automatically copy the properties from the DTO to the entity, especially if your entites have a lot of properties. It saves you the trouble of having to write every property by hand, and has a lot of configurability.