I'm trying to model a m:n relation which references entries which might not exist yet on one side.
I tried with the following schema (doesn't work):
model Foo {
id Int #id
}
model Bar {
id Int #id
}
model FooBar {
fooId Int
barId Int
foo Foo #relation(fields: [fooId], references: [id])
bar Bar? #relation(fields: [barId], references: [id])
##id([fooId, barId])
}
When trying to add a relation between fooId: 1 (existing) and barId: 1 (not existing (yet?)), I get the following error:
Type: undefined
Message:
Invalid prisma.fooBar.create() invocation:
An operation failed because it depends on one or more records that were required but not found. No 'Bar' record(s) (needed to inline the relation on 'fooBar' record(s)) was found for a nested connect on one-to-many relation 'fooBarToBar'.
Code: P2025
Is there a way to insert the relation anyway, and when querying Foo with { include: { fooBar: { include: { bar: true }}}} to receive an object like this when Bar doesn't exist:
{
id: 1,
fooBar: {
fooId: 1,
barId: 1,
bar: null
}
}
I tried with the above schema, but it doesn't work. I guess changing the relation to this would work:
model FooBar {
fooId Int
barId Int
barIdExisting Int?
foo Foo #relation(fields: [fooId], references: [id])
bar Bar? #relation(fields: [barIdExisting], references: [id])
}
And then only set barIdExisting to barId if bar exists, but this would mean to have redundant data.
Related
I have a list of products, and a list of users and I want to be able to relate users to what products they have but also what quantity they have of each product.
I've setup my user schema like this:
model users {
id String #id #default(uuid())
email String #unique
password String
test_products test_products[]
users_to_test_products users_to_test_products[]
}
I've implicitly stated the relation so that I can add in a quantity field
model users_to_test_products {
users users #relation(fields: [user_id], references: [id])
user_id String
test_products test_products #relation(fields: [product_id], references: [product_id])
product_id String
quantity Int
##id([user_id])
}
and I've setup my product list like this:
model test_products {
product_id String #id
name String?
users users[]
users_to_test_products users_to_test_products []
}
I assumed I could make an update call, connecting the user to the test product and passing in the quantity but it seems that I can't and my approach is completely wrong.
await prisma.users.update({
data: {
test_products: {
connect: UserProducts.map((product) => {
return {
product_id: product.product_id,
quantity: product.quantity
}
}),
},
},
where: {
id: userId
},
})
note: UserProducts is just an object array eg [{ product_id: "tTer3434", quantity: 8 }]
Can anyone point me in the right direction on the approach I need to take please?
I use Postgres with Prisma and I want to ask if there is a way to use upsert or create functions and define relations without the connect field.
I have these models:
model User {
id Int #id #default(autoincrement())
posts Post[]
}
model Post {
id Int #id #default(autoincrement())
author User #relation(fields: [authorId], references: [id])
authorId Int // relation scalar field (used in the `#relation` attribute above)
}
and I want to do something like this:
prisma.Post.upsert({where: { authorId: 12 }, update: {}, create: { authorId: 12 })
assuming User with ID 12 does exists in the DB I want it to create a new record successfully but it fails because it wants it in this syntax:
prisma.Post.upsert({where: { authorId: 12 }, update: {}, create: { author: { connect: { id: 12}}}})
is this achievable somehow?
Thanks.
I have the following data model:
public class Foo
{
public Foo(int barId)
{
BarId = barId;
}
private int BarId;
public Bar Bar { get; private set; }
}
public class FooTypeConfig : IEntityTypeConfiguration<Foo>
{
public void Configure(EntityTypeBuilder<Foo> builder)
{
builder.HasOne(x => x.Bar)
.WithMany()
.HasForeignKey("BarId");
}
}
public class Bar
{
public int Id { get; private set; }
}
This works great and according to my expectations, I have a Foo table containing Id and BarId. My private field BarId and my Bar property are also correctly materialized when reading Foo from the database.
The problem is that I would like to find a way to name my private field, and choose a different name for my database column. I would like to name my property _barId and still choose BarId as my column name in my database.
Is this possible?
I have tried renaming the field in my Foo class and specifying my (now non-conventionally-named) foreign key _barId in my EntityTypeConfiguration
builder.HasOne(x => x.Bar).WithMany().HasForeignKey("_barId");
But this resulted in EF still generating a BarId column, without using it as foreign key to the Bar table...
migrationBuilder.CreateTable(
name: "Foos",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
BarId = table.Column<int>(nullable: true),
_barId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Foos", x => x.Id);
table.ForeignKey(
name: "FK_Foos_Bars__barId",
column: x => x._barId,
principalTable: "Bars",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
First off, EF maps database columns and FKs to entity properties, not fields. The properties can be real or as in your case - shadow.
So the following line:
builder.HasOne(x => x.Bar).WithMany().HasForeignKey("BarId");
maps the Bar -> Foo relationship FK to a Foo shadow property called BarId and should stay as it is.
You use the Property method to configure the property type, backing field, column name, type and other attributes. For instance:
builder.Property<int>("BarId") // or int? etc.
.HasField("_barId")
.HasColumnName("BarId"); // or BazId or whatever you like
Just make sure you use one and the same property name when defining it and when specifying the FK. You can also use Entry(entity).Property(propertyName) to get/set the value, mark it as modified etc. as well as EF.Property(entity, propertyName) to access it inside LINQ to Entities queries.
Model:
class TableA
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID {get;set;}
}
class TableB
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID {get;set;}
public int TableAID {get;set;}
[ForeignKey("TableAID")]
public virtual TableA TableA {get;set;}
}
Code:
var copyB = sourceDb.TableB.Single(B => B.ID == TableBID_Param); //EG. B.ID == 17
copyB.TableAID = TableAID_Param; //EG. B.TableAID = 5;
destDb.TableB.Add(copyB);
destDb.SaveChanges(); //Error occurs here
I have changed the Foreign Key ID, and the Primary Key should be regenerated
The problem here, is the the copyB object is:
still associated with sourceDb
likely lazy loading TableA (if TableAID has a value)
The TableA navigation property will take precedence over the TableAID foreign key property. DestDb will try to insert the TableA object, and this likely already exists (possibly from a previous successful execution of this flawed code).
You should quarantine your objects between contexts, in a similar way that you should use a separate ViewModel when sending data to a web client - you have control how it gets serialised. This also makes it clear to a future developer that a new Primary Key is expected (not implied)
Extend TableB:
class TableB
{
public string SomeData {get;set;} //Let's pretend there's something interesting to clone
public TableA ContextClone()
{
var clone = new TableA();
clone.SomeData = SomeData;
//Nothing else get's assigned - it's up to the caller to map ForeignKeys
}
}
New Code:
var sourceB= sourceDb.TableB.Single(B => B.ID == TableBID_Param); //EG. B.ID == 17
var clone = sourceB.ContextClone();
clone.TableAID = TableAID_Param; //EG. B.TableAID = 5;
destDb.TableB.Add(clone);
destDb.SaveChanges();
I have the following classes generated from an edmx model:
public partial class A
{
public int Id { get; set; }
public virtual B B { get; set; }
}
public partial class B
{
public int Id { get; set; }
public virtual A A { get; set; }
}
The existing db doesn't use the EF default which expects A.Id to be the primary key of table B:
CREATE TABLE [dbo].[B] (
[Id] INT IDENTITY (1, 1) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[A] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[BId] INT NULL,
CONSTRAINT [fk] FOREIGN KEY ([BId]) REFERENCES [dbo].[B] ([Id])
);
With an edmx model, I can explicitly configure the multiplicity of each end, but I haven't found how to get the equivalent model using the fluent-api. When I do something like the following and generate a new db, the foreign key gets placed in table A instead of table B.
modelBuilder.Entity<A>().HasOptional(a => a.B).WithRequired(b => b.A);
I'm guessing I need to use a convention, but so far I've been unable to get the desired output.
UPDATE:
The closest solution I've found so far is to use the following which generates the correct SQL in the db:
modelBuilder.Entity<A>()
.HasOptional(a => a.B)
.WithOptionalDependent(b => b.A)
.Map(c => c.MapKey("BId"));
However, it's conceptually modeled as a 0..1:0..1 relationship and I haven't found how to set a CASCADE delete rule that deletes B when A is deleted.
I wasn't able to find a direct solution, but using the following code seems to meet my requirements of preserving the existing schema and creating a conceptual model that has the same multiplicities & delete behaviors as my original edmx model.
I'd still be interested in any solutions that don't require updating the conceptual model during the post-processing IStoreModelConvention.
{
var overridesConvention = new OverrideAssociationsConvention();
modelBuilder.Conventions.Add(overridesConvention);
modelBuilder.Conventions.Add(new OverrideMultiplictyConvention(overridesConvention));
}
private class OverrideAssociationsConvention : IConceptualModelConvention<AssociationType>
{
...
public List<AssociationEndMember> MultiplicityOverrides { get; } = new List<AssociationEndMember>();
public void Apply(AssociationType item, DbModel model)
{
if (multiplicityOverrides.Contains(item.Name))
{
// Defer actually updating the multiplicity until the store model is generated
// so that foreign keys are placed in the desired tables.
MultiplicityOverrides.Add(item.AssociationEndMembers.Last());
}
if (cascadeOverrides.Contains(item.Name))
{
item.AssociationEndMembers.Last().DeleteBehavior = OperationAction.Cascade;
}
}
}
private class OverrideMultiplictyConvention : IStoreModelConvention<EdmModel>
{
private readonly OverrideAssociationsConvention overrides;
public OverrideMultiplictyConvention(OverrideAssociationsConvention overrides)
{
this.overrides = overrides;
}
public void Apply(EdmModel item, DbModel model)
{
overrides.MultiplicityOverrides.ForEach(o => o.RelationshipMultiplicity = RelationshipMultiplicity.One);
}
}