I have a projects table which has three associations to the contacts table, one for each type of contact (Sponsor, Broker, Logistics). I allow a user to select from an existing list of contacts. If the same contact is selected for two or more of these my code gets them correctly pointed to the existing entry, but I get a new unused contact entry in the database.
I only allow the first of any duplicates to be modified, I assemble them detached and then attach to the DbContext prior to saving.
if (newProject.SponsorContact != null && newProject.SponsorContact.Id > 0)
if (AttachContact(newProject.SponsorContact))
SdContext.Entry(newProject.SponsorContact).State = EntityState.Modified;
else
newProject.SponsorContact = _contactDataMapper.GetContactById(newProject.SponsorContact.Id);
if (newProject.BrokerContact != null && newProject.BrokerContact.Id > 0)
if (AttachContact(newProject.BrokerContact))
SdContext.Entry(newProject.BrokerContact).State = EntityState.Modified;
else
newProject.BrokerContact = _contactDataMapper.GetContactById(newProject.BrokerContact.Id);
if (newProject.LogisticsContact != null && newProject.LogisticsContact.Id > 0)
if (AttachContact(newProject.LogisticsContact))
SdContext.Entry(newProject.LogisticsContact).State = EntityState.Modified;
else
newProject.LogisticsContact = _contactDataMapper.GetContactById(newProject.LogisticsContact.Id);
AttachContact just keeps track of the attached contact id's and returns false if already attached so that I can grab the existing contact from the context.
protected bool AttachContact(Contact contact)
{
if (!_attachedContacts.Contains(contact.Id))
{
_attachedContacts.Add(contact.Id);
return true;
}
return false;
}
So if I choose Jacob Marley with ContactId 17 twice, my project saves with both entries pointing to the correctly modified Jacob Marley id 17, but I get a new Jacob Marley also saved in the contact table with no project pointing to that entry. Giving serious consideration to accessing the context during assembly so I know existing entries are already attached.
I am new to C# MVC and entity framework. This may be common knowledge, but if you are reading this your likely having the same issue I have described above.
The key elements are that the model in question has multiple fields that reference the same foreign table and during assembly these were detached models. In my case I have a project table which has three different types of contact and a user may select existing contacts for this which could mean that the same foreign contact is used for all three fields.
So the Project model has an int foreign key field for each one AND a virtual model.
public Nullable<int> SponsorContactId { get; set; }
public Nullable<int> BrokerContactId { get; set; }
public Nullable<int> LogisticsContactId { get; set; }
public virtual Contact SponsorContact { get; set; }
public virtual Contact BrokerContact { get; set; }
public virtual Contact LogisticsContact { get; set; }
Because I built my assembler to cascade down from Organization to Project to Contacts I was unable to assemble the contacts as attached.
There are two ways this can be solved.
The first is to make sure that you assemble them as attached, meaning you get the contact model from the context instead of creating a new contact model.
The second is the route I took. I only allowed a user to edit one of the same contact on the front end and in the view model I tracked if it was modified. During assembly if the contact was existing, meaning it had an Id > 0, I would only assemble the virtual contact if it was also modified. If it was not modified I just populated the int value. So if Sponsor Contact is existing but not modified I set int SponsorContactId and did not assemble the Contact SponsorContact.
If you have any questions please post a comment. I check in regularly and will get back to you.
Related
I am trying to add another address to a Branch in 21.203, similar to the how the DefAddress works. I have extended the Branch DAC and added the field to the extension. I also tried extending the BAccount DAC and table to get this to work but, nothing is working so far. The Address record is being created in the Address table but, the PXDBChildIdentity attribute doesn't seem to be sending back the ID to the Address record. On the screen, when I enter the values for the address, and click Save, all of the values are wiped out and the custom field (UsrCustomAddress) is never populated.
#region UsrCustomAddress
[PXDBInt()]
[PXUIField(DisplayName="Custom Address")]
[PXDBChildIdentity(typeof(Address.addressID))]
public virtual Int32? UsrCustomAddress { get; set; }
public abstract class usrCustomAddress : PX.Data.BQL.BqlInt.Field<usrCustomAddress> { }
#endregion
And, then, I added the View to the BranchMaint extension.
<code>
namespace PX.Objects.CS
{
public class BranchMaint_Extension : PXGraphExtension<BranchMaint>
{
public PXSelect<Address, Where<Address.bAccountID, Equal<Current<BAccount.bAccountID>>,
And<Address.addressID, Equal<Current<BAccountExt.usrCustomAddress>>>>> CustomAddress;
}
}
</code>
Not sure what I'm missing?
TIA!
I handled this by manually calling .Insert() on the specific Data View in the row inserting event and writing it to the field. PXDBChildIdentity then can update the field with the real identity value (on row persist) after the link is established.
protected void _(Events.RowInserting<Branch> e)
{
if (e.Row is null) return;
Address newAddress = CustomAddress.Insert();
BranchExt ext = e.Cache.GetExtension<BranchExt >(e.Row);
ext.UsrCustomAddress = newAddress.AddressID;
}
If anyone knows how to do this in an attribute id love to know. Note, this will only work for newly created records. Any existing records would not have a row inserting event fire and fill your custom field. You need to backfill the data with SQL or just a temporary custom action that you can invoke this code manually.
I am trying to make a mobile app, which will use the Azure database system. I am having alot of trouble making my own table, and have been running in coding circles for a couple of weeks. I just can't figure out what and how to change.
I can get the todolist up and running from azure, and i have tried to make my own table in the backend with a dataobject and a controller, but after adding the DbSet om the context, the todolist part breaks when i try to run the app.
How do i add my own stuff to the app, so that i can have a table of persons for example, instead of the todolist?
Thank you so much in advance. this is very confusing to me.
This is what i've done:
In the backend, i made a person class inhereting the EntityData class and have a firstname string property and a lastname string property
Then i added
public DbSet<Person> Persons { get; set; }
and then a Personcontroller through the Add -> Controller -> Azure Mobile
Apps Table Controller in visual studio 2017
Then in the app i downloaded from azure, i made the person class
public class Person
{
[JsonProperty(PropertyName = "firstName")]
public string firstName { get; set; }
[JsonProperty(PropertyName = "lastName")]
public string lastName { get; set; }
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
}
Then made the table
IMobileServiceTable<Person> PersonTable = client.GetTable<Person>();
Then tried to insert into the table
Person peter = new Person();
peter.firstName = "Peter";
peter.lastName = "Friis";
await personTable.InsertAsync(peter);
but that gives the error:
Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOperationException:
'The request could not be completed. (Internal Server Error)'
According to your description, I assumed that you are using C# backend with SQL database. I would recommend that you could add the following code under the ConfigureMobileApp method of Startup.MobileApp.cs file for collecting the detailed error message.
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
Before inserting the new record to your table via the mobile client SDK, you could leverage the postman or fiddler to simulate the insert operation as follows to narrow this issue:
For more details about http table interface, you could refer to here.
Additionally, since you are adding your custom tables, please make sure you have manually updated your database to support your new database model or configure the Automatic Code First Migrations. For more details, you could refer to adrian hall's book about Implementing Table Controllers.
I have already a model class called User.cs with some properties:
[JsonProperty(PropertyName = "admin")]
public bool Admin { get; set; }
[JsonProperty(PropertyName = "email")]
public string Email { get; set; }
This gives me the columns admin and email in the sqlite database.
Now I am making an update for my app. I want to add a new property and therefore a new column:
[JsonProperty(PropertyName = "owner")]
public bool Owner { get; set; }
When I add this to User.cs, the sqlite file will get a new column called owner. All existing rows in the User table of the sqlite file get the value NULL for this new column.
My question: What can I do give existing rows the value 0 (false).
There are a couple of things you can do, depending on requirements. The most obvious is:
Update the model on the server to add the Owner field,
Update the database with a default value.
On first run of your client (after an update), wipe the SQLite database and re-sync all data
I'm trying to create an API and a website client for it. Lately I've been reading a lot about OAuth2 as a security mechanism and companies that offers authentication as a service such as auth0.com or even Azure active Directory and I can see the advantages in using them
Because I'm used to always having the users in the same database and tables with relationships to the Users table in the form of One to Many such as below
public class User
{
public string subjectId { get; set; }
public virtual List<Invoice> Invoices { get; set; }
/*
More properties in here
*/
}
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public virtual User User { get; set; }
}
My questions is then.
If the users are stored in an external authentication service such as Auth0.com,
How the Invoice class will handle the relation to the user?
Would it be just adding a new property subjectId in the Invoice table and this will take the value of whatever id the authentication service assigned?
In the latter case, would the class Invoice be something like below?
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public string SubjectId{get;set;}
}
Also, if the users are stored someplace else, how do you make a query like,
Select * from Users u inner join Invoices i where Users.Name='John Doe' and i.Date>Somedate.
Since you have mentioned Auth0 as your Identity provider there are multiple ways to achieve the user table in your database.
1. Authenticating/ registering the user with Auth0 will send a response with Profile Object which will have all the basic profile information you need. Post this profile object back to your own API to save it to database. This API endpoint should be secured with the access token you received along with the profile object from Auth0.
2. You can create a custom rule in Auth0 that posts the user information back to your api. This rule gets executed on Auth0 server so this is a secure call.
3. Identity providers (Auth0 in our case) are required to expose an API endpoint that gives us user profile data (ex: https://yourdoamin.auth0.com/userinfo). You can make a call to this endpoint from your API to receive the user information.
When user Registers to your application, please use one of these techniques to establish a User profile information table in your database. It is always a good idea to treat the Identity Provider as a service responsible for authenticating the resource owner (the user of your application) and providing an access token for securely accessing your API/ application. If you have the profile of the user in your database, you do not have to depend on the Identity Provider once the user is authenticated.
Please let me know if you have any further questions.
Thank you,
Soma.
We have a similar setup for our website. We use Passport for our user database and our website doesn't have a user table at all. This makes life much simpler than having a a bunch of duplicate data between Passport and our website. I'll use our code as an example of what you are doing and hopefully it makes sense.
Our website has a License object that looks like this (Java not C#, but they are similar):
public class License {
public String companyName;
public List<User> users;
}
The License table looks like this (trimmed down):
CREATE TABLE licenses (
id UUID NOT NULL,
company_name VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
The License identifies the users that are associated with it via a join table like this (Passport uses UUIDs for user ids making life simple again):
CREATE TABLE users_licenses (
users_id UUID NOT NULL,
licenses_id UUID NOT NULL,
PRIMARY KEY (users_id, licenses_id),
CONSTRAINT users_licenses_fk_1 FOREIGN KEY (licenses_id) REFERENCES licenses (id)
);
Then we can select in either direction. If we know the user id, we can ask for all their licenses like this:
select * from licenses where users_id = ?
Or if we know the license id, we can ask for all the users that have access to the license:
select * from users_licenses where licenses_id = ?
Once we have one or more user ids, we can call the Passport /api/user endpoint or the /api/user/search endpoint to retrieve one or more user objects. We are actually using the Passport Java Client (https://github.com/inversoft/passport-java-client) which makes the API call for us and then returns a List<User>. This is what is stored in the License class from above. That code looks like this:
License license = licenseMapper.retrieveById(licenseId);
List<UUID> userIds = licenseMapper.retrieveUserIdsFor(licenseId);
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsers(userIds);
license.users = clientResponse.successResponse.users;
LicenseMapper is a MyBatis interface that executes the SQL and returns the License objects. C# ORMs use LINQ, but it would be similar.
The nice thing about this setup is that we don't have a user database table in our website database that we have to keep in sync. Everything is loaded from Passport via the API. We aren't ever concerned about performance either. Passport is on-premise and can do thousands of user lookups each second, so we always load the data instead of caching it.
The only piece of your question that requires additional code is handling the joins when you are searching for arbitrary users like name='John Doe'. The only way to handle this is to query your user database first, retrieve all the IDs, then load their invoices. This seems like it could be dangerous if you have a large user database, but still doable.
That could would look like this in our situation:
UserSearchCriteria criteria = new UserSearchCriteria().withName("John Doe");
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsersByQueryString(criteria);
List<User> users = clientResponse.successResponse.users;
Set<License> licenses = new HashSet<>();
for (User user : users) {
licenses.addAll(licenseMapper.retrieveByUserId(user.id));
}
I allowed the WAMS wizard to create the "test" table ("Items") it so congenially offers to create after setting up my WAMS.
Then I wanted to create a table that will actually be useful to me. The instructions in the wizard do say, "You can add and remove tables later by using the "Data" tab above."
So I did that, and I did create a table, but I can't see now where I can change the structure of the table (IOW, add columns). I've tried 2-clicking the service, 2-clicking the table, selecting the "New" button, right-clicking on the sole column name (id) in my table, etc., but all to no avail.
Something I'm confused about, too, is the relationship of the tables I create this way with my existing SQL DB tables - or can I do without that SQL DB now (once I get these WAMSical tables set up)?
Or why can't I associate my existing SQL DB tables to my WAMS? And if I can -- how?
UPDATE
Also, it seems there is a mismatch between what is written and what I'm actually experiencing. This (from http://msdn.microsoft.com/en-us/magazine/jj721590.aspx) is not true/did not happen for me:
"2.Create a relational table to store your data. When you click on the Create TodoItem Table button, the wizard will automatically create a table based on the Windows Azure SQL Database you created (or re-used) previously."
I tried "from scratch," creating a new WAMS. Again, I get when I select my existing SQL DB, "Database and Mobile Service Not in the Same Region - Performance will decrease...Additionally, the data sent from the db to the mobile service will be counted as billable bandwidth usage. We recommend that you choose a database in the same location as the mobile service."
I would like to, but how? Why didn't WAMS adjust this for me automatically - or at least give me the option to put my DB and Mobile Service in the same place?
UPDATE 2
What is interesting is that I CAN see the new tables in LINQPad. I already had two SQL DB tables that display under that connection info, but on the same level as those tables is my WAMS name, under which are the "default" Items table and one of my own I created (both of which, though, only have one column, specifically "Id (Int64)"
IOW, what I see in LINQPad is:
blaBlaBla.database.windows.net,1433.blaBla
BlaBla
BlaBlaSQLDB_Table1
BlaBlaSQLDB_Table2
wamsName
Items
Id (Int64)
BlaBlaWAMSTable
Id (Int64)
...so how do I extend/manage the "BlaBlaWAMSTable" is the problem now...
UPDATE 3
Well, looky here; LINQPad to the rescue again:
select * from <WAMSName>.<TableName>
...shows there is a record after I created the table I wanted via a class in my project (NOT in the Azure/WAMS management area)
...and, of course, LINQPad shows the newly added columns added that way.
All I needed to do was follow the steps provided (Reference the Azure SDK, add a corresponding using clause, etc.) and then added this method to test it out:
private async void InsertTestRecordIntoWAMSSQLDBTable()
{
<WAMS Table class name> invitation = new <WAMS Table class name> { SenderID = "donkeyKongSioux#supermax.gov", ReaderDeviceID = "00-AA-11-BB-01-AB-10-BA", ReaderName = "B. Clay Shannon", SenderUTCOffset = 5, SenderDeviceID = "BA-10-AB-01-BB-11-AA-00" };
await App.MobileService.GetTable<<WAMS Table class name>>().InsertAsync(invitation);
}
...and it worked. Now it would be nice to have some samples/examples for select queries as well as updates.
And my big remaining question (so far): can I decorate/annotate the columns/members of my table class? IOW, can I change this:
public class
{
public int Id { get; set; }
public string SenderID { get; set; }
public string ReaderDeviceID { get; set; }
public string ReaderName { get; set; }
public int SenderUTCOffset { get; set; }
public string SenderDeviceID { get; set; }
}
...to something like this:
public class
{
[Primary, AutoInc]
public int Id { get; set; }
[Indexed]
public string SenderID { get; set; }
[Unique]
public string ReaderDeviceID { get; set; }
[MaxLength(255)]
public string ReaderName { get; set; }
public int SenderUTCOffset { get; set; }
public string SenderDeviceID { get; set; }
}
?
I can't do exactly that, as those are SQLite annotations, but since I am unable to manage my table from the Azure/WAMS portal, how can I designate these attributes?
After altering the design of the table in code, I'm able to see that those columns have been added to my table in the WAMS portal, but it seems the only thing I can do to the columns is add an index...
UPDATE 4
It turns out that creating tables in WAMS is as easy as pie (but not as easy as pi/as hard as pi) - once you know how to do it.
With the WAMS created, select Data, and then Create to create a table. Give it a name, and select the permissions you want. This will give you a deadly dull but "living" database table with one, count 'em, one, column: ID, a BigInt, indexed.
THEN, to actually add more columns to the table, the easiest way I've found (the only way I've found that is, and it is easy) is to:
1) Create a class that corresponds to the database table, a la SQLite, such as:
public class WAMS_DUCKBILL
{
public int Id { get; set; }
public string PlatypusID { get; set; }
public DateTime UpdateTimeUTC { get; set; }
public double Income { get; set; }
public double Outgo { get; set; }
}
2) Write a method that will add a record to this table, such as:
private async void InsertTestRecordIntoWAMSDuckbillTable()
{
WAMS_DUCKBILL duckbill = new WAMS_DUCKBILL { PlatypusID = "42", UpdateTimeUTC =
DateTime.Now, Income = 3.85, Outgo = 8311.79 };
await MobileService.GetTable<WAMS_DUCKBILL>().InsertAsync(duckbill);
}
3) Call that method from App.xaml.cs' OnLaunched event
4) As can then be seen by running the following query in LINQPad (or however you want to pee[k,r] into the database):
SELECT * FROM platypi.WAMS_DUCKBILL
Call me old fashioned, but notice I'm using tired old SQL as opposed to LINQ here; so sue me. At any rate, LINQPad shows that the test record has indeed been inserted into the WAMS table. Voila! as the escargot-eating, beret-at-a-rakish-angle wearing cats say.
The focus for Windows Azure Mobile Services (WAMS) are app developers who does not want to invest a lot of time to develop a backend. Therefor the data model easier than you might think.
Tables in WAMS are always backed by tables in a SQL database. You can create and delete tables via the portal.
Creating columns is a bit different. You create columns by simply using them. As soon as you write data for a column that does not exist, WAMS creates that column automatically. That's called Dynamic Schema. After development you should disable Dynamic Schema. You find it in the Mobile Service, Configure.
Documentation: Data access in Windows Azure Mobile Services