I was trying to create a new object with multicolumn primary key formed by a foreign key and a self-generated field and I found this error:
Ids can not be autogenerated for entities with multipart keys.
For now, and although not the most appropriate, I will change the key but the question is:
Are you planning support multicolumn primary key autogenerated soon?
I will add the request to uservoice too.
A greeting.
Edit to explain the use case:
Hello,
True, it may not make sense to have a primary key composed by a foreign key and a self-generated field.
My idea was to build a table like this:
ParentID ChildID Data
1 1 Some Data...
1 2 Some Data...
2 1 Some Data...
2 2 Some Data...
As a first step I did a table like this:
ParentID ChildID Data
1 1 Some Data...
1 2 Some Data...
2 3 Some Data...
2 4 Some Data...
Where ChildID was a self-generated field.
So you can ignore my question.
A greeting.
I've had the same problem but I have successfully solved it without changing the Primary Key (PK).
My PK had 2 columns
ProductId (identity)
TenantId (multitenant application)
so the first problem was Breeze couldn't generate new PK when the new item was added to the EntityManager. And I have defined custom entity initialization function:
var manager = new breeze.EntityManager(breezeServiceUrl);
var store = manager.metadataStore;
function itemInitializer(item) {
if (item.ProductId() == 0) {
item.ProductId(/*Some counter value*/);
}
if (item.TenantId() == 0) {
item.TenantId(TenantId);
}
if (item.isBeingEdited === undefined) {
item.isBeingEdited = ko.observable(false);
}
};
store.registerEntityTypeCtor("ProductItem", function() {/*Leave empty to use default constructor*/}, itemInitializer);
The second problem is that Breeze couldn't update the ProductId with the real value from EntityFramework because it gets the entity by PK and the returned value had only ProductId. So you need to override the function:
var entityType = store.getEntityType("ProductItem");
var entityGroup = manager.findEntityGroup(entityType);
entityGroup._fixupKey = function (tempValue, realValue) {
var ix = this._indexMap[tempValue + ":::" + TenantId]; //Changed line
if (ix === undefined) {
throw new Error("Internal Error in key fixup - unable to locate entity");
}
var entity = this._entities[ix];
var keyPropName = entity.entityType.keyProperties[0].name;
entity.setProperty(keyPropName, realValue);
delete entity.entityAspect.hasTempKey;
delete this._indexMap[tempValue];
this._indexMap[realValue] = ix;
};
If you have any other questions about multipart keys, you can read it in the blog.
This feature is not currently on the roadmap, but I want to better understand your use case for this. As you already know, Breeze supports auto-generated keys, and Breeze supports multi-part keys, but what scenarios do you find using both at the same time to be helpful?
Thanks!
Actually having a multipart key where part of the key is autogenerated is actually pretty common. This usually occurs with legacy databases where the primary key consists of a foreign key property and a 'autogenerated' sequence number property. Usually this autogenerated key is not globally unique by itself, but only in conjunction with the foreign key property. Think of an orderDetail with a foreign key of "OrderId, SequenceNumber".
What doesn't seem to make as much sense is when the primary key consists of more than one autogenerated property.
In Breeze the autogenerated keys are intended to be globally unique. Whereas, in the multipart key mentioned above the SequenceNumber would not be globally unique ( and if it were then why not make it the primary key all by itself).
Does this make sense?
Related
We want to alter a table to include a non-primary key reference to a new table. The old definition is:
#schema
class SpikeSortingParameters(dj.Manual):
definition = """
# Table for holding parameters for each spike sorting run
-> SortGroup
-> SpikeSorterParameters
-> SortInterval
---
-> SpikeSortingMetrics
-> IntervalList
import_path = '': varchar(200) # optional path to previous curated sorting output
"""
We'd like to add
-> SpikeSortingArtifactParameters
as a non primary key, and before we spent time trying to get this to work, we wanted to know if it was possible given that we don't know of a way to assign a default value here.
thanks in advance...
Loren
Unfortunately, foreign key updates are not currently supported with table.alter(). There is a workaround that can be used but there are a few steps and those should be taken carefully. It would be best to file this as a feature request in the issue tracker.
Using Alter
For instance, consider the following:
If you have 2 tables defined as follows:
import datajoint as dj
schema = dj.Schema('rguzman_alter_example')
#schema
class Student(dj.Lookup):
definition = """
student_first_name: varchar(30)
student_last_name: varchar(30)
---
student_location: varchar(30)
"""
contents = [('Joe', 'Schmoe', 'Los Angeles, CA'), ('Suzie', 'Queue', 'Miami, FL')]
#schema
class Assignment (dj.Lookup):
definition = """
assignment_id: int
---
assignment_due_date: date
#-> [nullable] Student # Standard way to define a foreign key on secondary attributes with NULL as default
"""
contents = [dict(assignment_id=100, assignment_due_date='2021-04-21')]
Now suppose that you'd like to have a foreign key on secondary attributes with NULL as the default. You can pass options to foreign keys in secondary attributes (see the comment above where we allow it to default to NULL). Initializing a table from scratch in this way works just fine. For the case where we want to add a foreign key on a secondary attribute after a table has been initialized with existing data, Assignment.alter() would be the best means to achieve this so long as we establish a default value to fill existing records with. Let's see what happens when we uncomment the foreign key on secondary attributes, redefine the Assignment table class, and try to alter.
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-23-09997168281c> in <module>
----> 1 Assignment.alter()
~/.local/lib/python3.7/site-packages/datajoint/table.py in alter(self, prompt, context)
84 del frame
85 old_definition = self.describe(context=context, printout=False)
---> 86 sql, external_stores = alter(self.definition, old_definition, context)
87 if not sql:
88 if prompt:
~/.local/lib/python3.7/site-packages/datajoint/declare.py in alter(definition, old_definition, context)
367 raise NotImplementedError('table.alter cannot alter the primary key (yet).')
368 if foreign_key_sql != foreign_key_sql_:
--> 369 raise NotImplementedError('table.alter cannot alter foreign keys (yet).')
370 if index_sql != index_sql_:
371 raise NotImplementedError('table.alter cannot alter indexes (yet)')
NotImplementedError: table.alter cannot alter foreign keys (yet).
Oh, there's an exception... So it turns out that it is not implemented yet for this use case. However, there is a manual workaround that can be leveraged so let me detail those steps until there is support.
Workaround
Instead of defining the foreign key on secondary attributes as above, define it in this way:
#schema
class Assignment (dj.Lookup):
definition = f"""
assignment_id: int
---
assignment_due_date: date
{newline.join(['' + a.name + '=null: ' + a.type for a in Student.heading.attributes.values() if a.in_key])}
"""
contents = [dict(assignment_id=100, assignment_due_date='2021-04-21')]
This 'copies' over the primary attributes of the parent table into the secondary attributes of your child table.
Perform alter as normal: Assignment.alter()
Manually add the foreign key using a SQL query directly. Like this:
q = """
ALTER TABLE {table}
ADD FOREIGN KEY ({fk})
REFERENCES {ref} ({pk})
ON UPDATE CASCADE
ON DELETE RESTRICT
""".format(table=Assignment.full_table_name,
fk=','.join([f'`{k}`' for k in Student.primary_key]),
ref=f'`{Student.table_name}`',
pk=','.join([f'`{k}`' for k in Student.primary_key]))
dj.conn().query(q)
Make sure to remove the portion that was added in step 1 and replace it with the proper specification i.e. -> [nullable] Student
Restart your kernel
To verify it has been properly set, check Assignment.describe(). If everything worked properly, the result should be:
assignment_id : int
---
assignment_due_date : date
-> [nullable] Student
Additionally, any pre-existing records should now be prefilled with NULL.
I am creating my first Association Mapping for a Join. This is also the first time I've used a Foreign Key in pgSQL.
I am working with ZF3. The error I am receiving is:
An exception occurred while executing 'SELECT p0_.reference AS reference_0, p0_.meta_keyword_reference AS meta_keyword_reference_1, p0_.add_date AS add_date_2, p0_.add_membership_reference AS add_membership_reference_3, p0_.remove_date AS remove_date_4, p0_.remove_membership_reference AS remove_membership_reference_5 FROM page_about_meta_keyword_link p0_ INNER JOIN meta_keyword m1_':
SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at end of input LINE 1: ...page_about_meta_keyword_link p0_ INNER JOIN meta_keyword m1_
The query I am trying to create is
SELECT MetaKeywords.Keyword FROM PageAboutMetaKeywordLink INNER JOIN MetaKeywords ON PageAboutMetaKeywordLink.MetaKeywordReference = MetaKeywords.Reference WHERE PageAboutMetaKeywordLink.RemoveDate IS NULL ORDER BY MetaKeywords.Keyword ASC
From my database experience I expect it is creating the error due to the missing
ON p0_.meta_keyword_reference = m1_reference
I don't understand how to communicate the Join. Based on the documentation I had expected this was automatic. Maybe I misunderstood.
The tables I am trying to Join are page_about_meta_keyword_link.meta_keyword_reference ON meta_keyword.reference . This is the first time I've created a foreign key in pgSQL.
This is the table structure for page_about_meta_keyword_link
CREATE TABLE public.page_about_meta_keyword_link
(
reference bigint NOT NULL DEFAULT nextval('page_about_meta_keyword_link_reference_seq'::regclass),
meta_keyword_reference bigint,
add_date timestamp with time zone DEFAULT now(), -- UTC
add_membership_reference bigint,
remove_date timestamp with time zone, -- UTC
remove_membership_reference bigint,
CONSTRAINT page_about_meta_keyword_link_pkey PRIMARY KEY (reference),
CONSTRAINT page_about_meta_keyword_link_fk FOREIGN KEY (meta_keyword_reference)
REFERENCES public.meta_keyword (reference) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT page_about_meta_keyword_link_reference_unique UNIQUE (reference)
)
This is the meta_keyword
CREATE TABLE public.meta_keyword
(
reference bigint NOT NULL DEFAULT nextval('meta_keyword_reference_seq'::regclass),
keyword text,
effective_date timestamp with time zone DEFAULT now(), -- UTC
membership_reference bigint,
CONSTRAINT meta_keyword_pkey PRIMARY KEY (reference),
CONSTRAINT meta_keyword_reference_unique UNIQUE (reference)
)
This is the query I've created in the Service; The complete Service is found here.
$repository = $this->entityManager->getRepository(PageAboutMetaKeywordLink::class);
$keywords = $this->entityManager->getRepository(MetaKeyword::class);
$qb = $repository->createQueryBuilder('l');
$qb ->join('\Application\Entity\MetaKeyword' , 'k')
->expr()->isNull('l.removeDate');
return $qb->getQuery()->getResult();
The Association Mapping I created is for meta_keyword_reference; The complete Entity is found here.
/**
* #var int|null
*
* #ORM\ManyToOne(targetEntity="MetaKeyword")
* #ORM\JoinColumn(name="meta_keyword_reference", referencedColumnName="reference")
* #ORM\Column(name="meta_keyword_reference", type="bigint", nullable=true)
*/
private $metaKeywordReference;
I have not made any changes to the MetaKeywords Entity. It is found here.
Overall the various sections of the web site will share the meta_keywords. If I understand correctly the connection I am trying to make is ManyToOne.
I am wanting to leave a good reference for other newbies as they are their journey with Zend Framework 3 - Doctrine. Please advise of edits I should be making to this post so it is clear, understandable and concise so I receive the help I need and others will benefit from this in the future.
You double declared a column (meta_keyword_reference). Looking at the docs (same page you linked in question), you've made a mistake in your Annotation. Remove the ORM\Column line (the definition is already in JoinColumn). If you need it to be nullable (not required), add nullable=true to the JoinColumn; use either, not both
/**
* #var int|null
*
* #ORM\ManyToOne(targetEntity="MetaKeyword")
* #ORM\JoinColumn(name="meta_keyword_id", referencedColumnName="id", nullable=true)
*/
private $metaKeywordReference;
Do not worry about declaring a "type", Doctrine will automatically match it to the column you're referencing. Also, you should be referencing Primary Keys. I've assumed reference is not the PK, so I've changed it to id, change it to what it actually is.
Next, I think you're also using DBAL QueryBuilder instead of the ORM QueryBuilder.
The Query you need would be like this:
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
/** #var QueryBuilder $qb */
$qb = $this->entityManager->createQueryBuilder();
$qb->select('l')
->from(PageAboutMetaKeywordLink::class, 'l')
->join(MetaKeyword::class, 'k', Join::ON, 'l.reference = k.id') // check these property names (NOT DB COLUMNS!)
->where('l.removeDate is null');
Might be a few small errors in there, but that should be about it.
How can I call a stored procedure in Acumatica via PXDataBase which has as input parameter User defined type?
For example, I have the following type:
CREATE TYPE [dbo].[string_list_tblType] AS TABLE(
[RefNbr] [nvarchar](10) NOT NULL,
PRIMARY KEY CLUSTERED
(
[RefNbr] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)
GO
I have the following stored procedure:
CREATE PROCEDURE [dbo].[GetListOfAPInvoices]
#APInvoices as string_list_tblType readonly,
AS
BEGIN
select * from APInvoice a where a.RefNbr in (select RefNbr from #APInvoices)
END
and following fragment of C# code:
var par = new SqlParameter("APInvoices", dt);
par.SqlDbType = SqlDbType.Structured;
par.TypeName = "dbo.string_list_tblType";
par.UdtTypeName = "dbo.string_list_tblType";
par.ParameterName = "APInvoices";
PXSPParameter p1 = new PXSPInParameter("#APInvoices", PXDbType.Udt, par);
var pars = new List<PXSPParameter> { p1};
var results = PXDatabase.Execute(sqlCommand, pars.ToArray());
but when I execute my C# code I'm receiving error message:
UdtTypeName property must be set for UDT parameters
When I debugged with reflector class PXSqlDatabaseProvider, method
public override object[] Execute(string procedureName, params PXSPParameter[] pars)
I noticed that
using (new PXLongOperation.PXUntouchedScope(Thread.CurrentThread))
{
command.ExecuteNonQuery();
}
command.Parameters.Items has my method parameters, but item which is related to Udt type is null. I need to know how to pass user defined table type. Has anybody tried this approach?
Unfortunately UDT parameters are not supported in Acumatica's PXDatabase.Execute(..) method and there is no way to pass one to a stored procedure using the built-in functionality of the platform.
Besides, when writing data-retrieval procedures like the one in your example, you should acknowledge that BQL-based data-retrieval facilities do a lot of work to match company masks, filter out records marked as DeletedDatabaseRecord and apply some other internal logic. If you chose to fetch data with plain select wrapped into a stored procedure you bypass all this functionality. Hardly is this something that you want to achieve.
If you absolutely want to use a stored procedure to get some records from the database but don't want the above side-effect, one option is to create an auxiliary table in the DB and select records into it using a procedure. Then in the application you add a DAC mapped to this new table and use it to get data from the table by means of PXSelect or similar thing.
Coming back to your particular example of fetching some ARInvoices by the list of their numbers, you could try using dynamic BQL composition to achieve something like this with Acumatica data access facilities.
I want to get custom S.O. Invoice Template fields using QuickBooks QBFC.
Here's how to read custom fields from a sales order:
Add "0" to the OwnerIDList of the SalesOrderQuery.
Read custom header fields from the DataExtRetList that is attached to SalesOrderRet objects that are returned from the query.
Read custom line item fields from the DataExtRetList in the SalesOrderLineRet and SalesOrderLineGrouptRet objects that are included in each SalesOrderRet (if you're reading line items).
If you're already using the IncludeRetElementList, you must add DataExtRet to the list; if you're not then don't start using IncludeRetElementList until you have custom fields working. Just like any transaction query, you won't see any line item data unless you set the IncludeLineItems flag in the request.
Custom fields are well documented in the QuickBooks SDK Manual. I'd recommend you take a look at the section DataExt: Using Custom Fields and Private Data in the QBSDK Programmers Guide.
To elaborate on Paul Keister's answer, the reason you must add "0" to the query is because that is the Owner ID of the custom field you are attempting to retrieve. 0 is probably likely to be the value, but if the owner ID is different, you will have to use a different value here.
Some example C# code:
//set the owner id of the custom field you are trying to get back
IInvoiceQuery invoiceQuery = requestMsgSet.AppendInvoiceQueryRq();
invoiceQuery.OwnerIDList.Add("0");
//set up query parameters and actually call your query...
//call this method for each invoice to get its custom fields (if they exist)
static void GetInvoiceCustomFields(IInvoiceRet invoice)
{
if (invoice.DataExtRetList == null)
{
return;
}
for (int i = 0; i < invoice.DataExtRetList.Count; i++)
{
IDataExtRet extData = invoice.DataExtRetList.GetAt(i);
Console.WriteLine("external data name: " + extData.DataExtName.GetValue());
Console.WriteLine("external data value: " + extData.DataExtValue.GetValue());
}
}
I simplified the code a little while trying to debug:
[HttpPost]
public ActionResult Register(User model)
{
DateTime bla = new DateTime(2012, 12, 12);
try
{
User user = new User
{
gid = 1,
cid = 1,
firstName = model.firstName,
lastName = model.lastName,
email = model.email,
username = model.username,
password = model.password,
creationDate = bla,
active = 1
};
myContext.Users.AddObject(user);
myContext.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
return View();
}
The values are transmited accordingly. Users table:
[id] [int] IDENTITY(1,1) NOT NULL,
[cid] [int] NULL,
[gid] [int] NULL,
[firstName] [nvarchar](100) NOT NULL,
[lastName] [nvarchar](100) NOT NULL,
[email] [nvarchar](max) NOT NULL,
[username] [nvarchar](100) NOT NULL,
[password] [nvarchar](100) NOT NULL,
[creationDate] [datetime] NOT NULL,
[active] [int] NOT NULL,
CONSTRAINT [PK_Users_3213E83F0AD2A005] PRIMARY KEY CLUSTERED
I deleted all the foreign keys to be sure that nothing affects it. I am qute certain that at a previous moment it was working, but now I can not figure where the issue is.
It crashes while performing the savechanges:
{"An error occurred while updating the entries. See the inner exception for details."}
{"The member with identity '' does not exist in the metadata collection.\r\nParameter name: identity"}
I had the same error being thrown when I try to insert using EF, the error was
The member with identity 'Id' does not exist in the metadata collection.\r\nParameter name: identity
It wasn't obvious at first but the exception message was very concise because my database knows the column Id int but the property created for the object on my code was int ID so coming back to named mapping, Id is not mapped to ID.
So when an object with property ID is sent to database that only know Id you will get the above error.
I hope this helps, thanks
The issue was reproducing because of a trigger that was on the users table. Removed it and the issue is not reproducing anymore.
There is probably a trigger on the table being updated and it returns output. The output is thrown away but it conflicts with EF. Such output is often used to debug triggers (and forgotten to delete later):
select 'trigger called, i am here'
or there can be missing variable:
select column
instead of
select #variable=column
I think that the best solution is in this post. I used the 3rd option and works.
Here I report the reply in the link:
The issue could be related to a "instead of insert" trigger on one of
your tables.
The EF framework is performing validation on the inserted row of data
by calling scope_identity(). However, an "instead of insert" trigger
will change the context of the insertion in such a way that the EF
system's call to scope_identity will return null.
A couple ways around this:
Use a stored procedure to insert the data ( not tested )
Remove the instead of insert trigger ( triggers can cause other problems, so some people argue not to use them) ( tested, works!)
Turn off validation in the EF framework, so: context.Configuration.ValidateOnSaveEnabled = false ( tested, works!)
I had this same error today and spent a few frustrating hours trying to figure it out.
I was using Entity Framework to insert a record with an identity column into a SQL server database table. Simple enough.
The table had a trigger on it which in turn ran a stored procedure. The stored procedure had a line in it:
select newid()
This is the line that broke Entity Framework.
For tables with identity columns, Entity Framework expects to be returned a single row with a single field that is the identity column.
it's because of trigger pass back value to EF
if you are using trigger. in my problem i must check a value by selecting from other table and using of 'select' cause error in EF, so you must replace 'select' with 'set'.
you can not use this code.
select #any= any from tablename
you should use set instead of select
set #any= (select any from tablename)
Somedays, I hate M$.
The member with identity 'ChangeID' does not exist in the metadata collection.
Parameter name: identity
I've spent two days trying to get around this.
I'm using MVC.
To get all the data I need in one fell swoop, I created a view of the table in the DB for this application, and tables in other databases. (You may update views, with some constraints.)
I do a get, and all my data is present in the record, keys, descriptions, foreign keys, etc.
I created triggers on my view, to update the portion of the view that came from the local table.
Instead of Delete worked fine.
Instead of Update worked fine.
This error kept raising it's head on Instead of Insert. I could NOT get the insert trigger to successfully insert into my table. If I ran an insert on the view, with all fields provided in the SQL Management Studio, it worked fine. I know the exact values being passed because I ran SQL Server Profiler to see the code being passed.
But when the app attempted the update, it failed with The member with identity 'ChangeID' does not exist in the metadata collection.
Clue up above, someone said, "MVC expects table key to be ID"
I renamed ChangeID as ID in my view, changed it in the App, and BAM! Now it works.
What did NOT work:
db.Configuration.ValidateOnSaveEnabled = false;
adding a select to the trigger to get scope identity
Why do I have to modify my DB or my view to satisfy some hidden M$ assumption?
None the less, after two very frustrating days, code is now working. Maybe this will save someone else some time as well.
Try this
[HttpPost]
public ActionResult Register(User model)
{
DateTime bla = new DateTime(2012, 12, 12);
try
{
model.gid = 1;
model.cid = 1;
model.creationDate = bla;
model.active = 1;
myContext.Users.AddObject(model);
myContext.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
return View();
}
I was having this issue and my fix, was that in my connection-string metadata I did not specify my model in the .msl format.
See more info here
In my case, SetName of the entity was incorrect. Shortly, this worked for me:
Use
myContext.AddObject(nameOfSetEntity, user);
Instead of:
myContext.Users.AddObject(user);
To get the whole correct name of the entity (thanks to Nix's answer)
string className = typeof(User).Name;
var container = myContext.MetadataWorkspace.GetEntityContainer(myContext.DefaultContainerName, System.Data.Metadata.Edm.DataSpace.CSpace);
string nameOfSetEntity= (from meta in container.BaseEntitySets
where meta.ElementType.Name == className
select meta.Name).First();
Context.AddObject(nameOfSetEntity, user);