Entity Framework - Inserting model with many to many mapping - entity-framework

How can I insert a model Tag that belongs to a model Post when I have the models setup like this:
Post
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new List<Tag>();
}
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
This question suggests to create a Post object then add Tags to the Tags collection, I couldn't get it working:
Insert/Update Many to Many Entity Framework . How do I do it?
I want to add Tag to Post already in the database, how can I do that with EF. I'm new to EF.
This is what I've tried, if I send this to the API it doesn't insert any records and I can see that the new tag Id = 0 which doesn't exist in the database, but I'd think that'd cause a foreign key constraint error, not sure If I need to do something to auto generate Id for the tag:
{
Name: "test"
}
API
[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(Tag tag)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var post = new Post();
var tags = new List<Tag>();
tags.Add(tag);
post.Tags.Add(tag);
post.Id = 10;
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = tag.Id }, tag);
}

If you said there is Many-To-Many relation which the PostTag is connection table between Tag and Post then your models don't show any many-to-many relation, so from what I have seen there is
one-to-many between Post and Tag because of your model definition.
if you want to make many-to-many relation between them you have to something like below:
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public Tag()
{
Posts = new HashSet<Post>();
}
}
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new HashSet<Tag>();
}
}
and in OnModelCreating make relation by fluent api as below :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasMany(s => s.Posts)
.WithMany(c => c.Tags)
.Map(cs =>
{
cs.MapLeftKey("TagId");//TagId
cs.MapRightKey("PostId");//PostId
cs.ToTable("PostTag");
});
}
or vice versa
modelBuilder.Entity<Post>()
.HasMany(s => s.Tags)
.WithMany(c => c.Posts)
.Map(cs =>
{
cs.MapLeftKey("PostId");//PostId
cs.MapRightKey("TagId");//TagId
cs.ToTable("PostTag");
});
as you can see and know there should be a table named PostTag in database which have two columns as keys which have a script like :
CREATE TABLE [dbo].[PostTag](
[TagId] [int] NOT NULL,
[PostId] [int] NOT NULL,
CONSTRAINT [PK_PostTag] PRIMARY KEY CLUSTERED
(
[TagId] ASC,
[PostId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[PostTag] WITH CHECK ADD CONSTRAINT [FK_PostTag_Post] FOREIGN KEY([PostId])
REFERENCES [dbo].[Post] ([Id])
GO
ALTER TABLE [dbo].[PostTag] CHECK CONSTRAINT [FK_PostTag_Post]
GO
ALTER TABLE [dbo].[PostTag] WITH CHECK ADD CONSTRAINT [FK_PostTag_Tag] FOREIGN KEY([TagId])
REFERENCES [dbo].[Tag] ([TagId])
GO
ALTER TABLE [dbo].[PostTag] CHECK CONSTRAINT [FK_PostTag_Tag]
GO
take a look at here for more info.
UPDATE:
if you want establish zero-to-many relation between Post and Tag then the models should be like below:
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public int? PostId { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new HashSet<Tag>();
}
}
and one to many relation with fluent api :
modelBuilder.Entity<Post>()
.HasMany(o1 => o1.Tags);
as your comment you don't want Tag have navigate property so you should define a property as Nullable in Tag which is feign key, if there is a relation between them you should establish relation at least by navigate property or Nullable property.
The correct answer came from here (with no change):
You want to assign a tag to exist post you should find that Post firstly then add a tag to it, if that tag does exist in DB a relation will be made between Tag and found Post if that Tag does not exist then Tag will be inserted to DB,
Take a look at this :
var post = context.Posts.SingleOrDefault(x => x.Id == 4);//That Id would be you specific Post Id
var existTag = context.Tags.SingleOrDefault(x => x.Id == 1); //Exist Tag in DB
post.Tags.Add(existTag);
context.SaveChanges();
//Inserting new tag to DB and assign it to Post also
Tag newTag = new Tag // Does not exist in DataBase
{
Name = "tag2"
};
post.Tags.Add(newTag);// By this tag2 will be insert into Tag table first and then added to post (insert new record to PostTag)
context.SaveChanges();

Related

EF Core One to one relathionship with AspNetUsers table and cascade delete

I am struggling to setup a scenario (using C# classes and OnModelCreating() method) where DB models would act as follows:
First assumption, the mandatory one:
I want to have the ability to create User (AspNetUsers table) without a reference to a Guest. It will be necessary while seeding a DB with an admin user - it will not belong to any of the Event-s.
In summary - at least that's my understanding - User will be PRINCIPAL, Guest will be DEPENDENT (?)
Cascading deletion: I want to delete Users from AspNetUsers table when I delete a given Event (cascade delete).
This functionality already exists for Guests. When I delete an Event, all related Guests are being deleted correctly.
Two questions:
1. How do I actually create Guests that are related to AspNetUsers table?
When it comes to Guests list and its assignement to a ceratin Event, I just do something like:
eventDbObject.Guests = GetGuestsList();
_dbContext.Events.Add(evenDbObject); //Event is created in Events table, Guests table is correctly populated as well
With users it's tricky - I have to Register them first, get their ID, and then assign that ID to a Guest object. Is that way correct?
foreach (var guest in weddingDbObject.Guests)
{
var userCreationResult = await _identityService.RegisterAsync("userName","password"); // my RegisterAsync() method returns actual User
guest.AppUser = userCreationResult.User;
}
2. How to set up cascade deletion in such a scenario?
builder
.Entity<Guest>()
.HasOne(e => e.AppUser)
.WithOne(e => e.Guest)
.OnDelete(DeleteBehavior.Cascade);
Something like this does not seem to work
My classes:
public class Event
{
// PK
public Guid Id {get;set;}
public List<Guest> Guests { get; set; }
}
public class Guest
{
// PK
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid AppUserId { get; set; }
public AppUser AppUser { get; set; }
}
public class AppUser : IdentityUser<Guid>
{
public WeddingGuest Guest { get; set; }
}
Ok, this is what I ended up with, assumptions are met, everything works as expected.
public async Task<string> AddEventAsync(Event event)
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
try
{
// Add an event to [Events] table and corresponding guests to [Guests] table
_dbContext.Events.Add(event);
// Add users to [AspNetUsers] table
foreach (var guest in event.Guests)
{
var userCreationResult = await _identityService.RegisterAsync(guest.Id, $"{event.Name}-{guest.FirstName}-{guest.LastName}", guest.GeneratedPassword);
if (!userCreationResult.Success)
throw new Exception();
guest.AppUser = userCreationResult.User;
}
transaction.Commit();
_dbContext.SaveChanges();
}
catch
{
transaction.Rollback();
}
}
return event.Name;
}
Classes look like this:
public class Event
{
// PK
public Guid Id {get;set;}
public List<Guest> Guests { get; set; }
}
// PRINCIPAL
public class Guest
{
// PK
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[JsonIgnore]
public AppUser AppUser { get; set; }
}
// DEPENDENT
public class AppUser : IdentityUser<Guid>
{
public Guid? GuestId { get; set; } // '?' allows for 1:0 relationship
[JsonIgnore]
public Guest Guest { get; set; }
}
Fluent API DbContext configuration:
builder.Entity<AppUser>(entity =>
{
entity.HasOne(wg => wg.Guest)
.WithOne(a => a.AppUser)
.HasForeignKey<AppUser>(a => a.GuestId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
});

One Primary key map to multiple table as foreign key EntityFrameworkCore

I have one table API which has two files key and url. Key is primary key. Another table API_Action which has APIKey(FK) and ActionKey(String). APIKey is PrimaryKey of API table and ActionKey is primary key. I have another table Permission which have columns like APIKey(FK), Name, Description, Key. APIKey is Primary key of API table.
I want to store the data using entityFramework core. But it gives me error.
<code>
public class API
{
public string Key { get; set; }
public string ServiceUri { get; set; }
public virtual List<Action> Actions { get; set; } = new List<Action>();
public virtual List<PermissionType> PermissionTypes { get; set; } = new List<PermissionType>();
}
public class Action
{
public string ActionKey { get; set; }
public string APIKey { get; set; }
public virtual API API { get; set; }
}
public class PermissionType
{
public string APIKey { get; set; }
public string Key { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual API API { get; set; }
}
</code>
I defined relation using FluentAPI.
<code>
modelBuilder
.Entity<Models.System.API>(b => b.ToTable("tbl_System_API").HasKey(o => o.Key))
.Entity<Models.System.Action>(b => b.ToTable("tbl_System_API_Action").HasKey(o => new { o.ActionKey, o.APIKey }))
.Entity<Models.System.Action>(b => b.ToTable("tbl_System_API_Action").HasOne(o => o.API).WithMany(o => o.Actions).HasForeignKey(o => o.APIKey))
.Entity<PermissionType>(b => b.ToTable("tbl_System_PermissionType").HasKey(o => o.Key))
.Entity<PermissionType>(b => b.ToTable("tbl_System_PermissionType").HasOne(o => o.API).WithMany(o => o.PermissionTypes).HasForeignKey(o => o.APIKey))
</code>
At the time of Saving I am using
<code>
foreach (var api in apis)
{
var actionss = actions.Where(o => o.APIKey == api.Key).ToList();
api.Actions.AddRange(actionss);
}
foreach (var api in apis)
{
var permissionTypess = permissionTypes.Where(o => o.APIKey == api.Key).ToList();
api.PermissionTypes.AddRange(permissionTypess);
}
dataContext.APIs.AddRange(apis);
dataContext.Save();
</code>
I am getting below error.
The instance of entity type 'PermissionType' cannot be tracked because another instance with the same key value for {'APIKey'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
I want to store parent table and child tables together with FK relation.
So API table should have values and Action and PermissionType tables should be filled with FK of API table and other fields.

Adding an entity without knowing the parent id

I need to insert a new entity without knowing it's PK. The parent entity has another property which is a guid and unique which is what we use to do cross db references and this is all I have. I have done it in the past but can't find a reference on how to do it again.
[Table("School")]
public class SchoolEntity
{
public SchoolEntity()
{
Students = new HashSet<StudentEntity>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public Guid ExternalId { get; set; }
[ForeignKey("SchoolId")]
public virtual ICollection<StudentEntity> Students { get; set; }
}
[Table("Student")]
public class StudentEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public SchoolEntity School { get; set; }
}
//ExternalId won't work cause is not the primary key.
var school = new School { ExternalId = Guid.Parse('68e05258-550a-40f3-b68a-5d27a0d825a0') };
context.Attach(school);
context.Schools.Add.Add(new Student());
context.SaveChanges();
Well, the PK of the referenced entity is required in order to set properly the FK of the referencing entity.
If you don't have it, apparently you should find it (get it from the database) based on what you have (secondary identifier in your case). For instance:
var school = context.Schools.Single(e => e.ExternalId == externalId);
var student = new Student { School = school, ... };
context.Students.Add(student);
context.SaveChanges();
There is no way to get that working without fetching. If you don't want to fetch the whole referenced entity (and you are sure it's not tracked by the context), then you can fetch the PK only and Attach a stub entity:
var schoolId = context.Schools.Where(e => e.ExternalId == externalId)
.Select(e => e.Id).Single();
var school = new School( Id = schoolId);
context.Attach(school);
// ...

Temporal property optimization with Entity Framework

I want to implement temporal properties using an approach similar to that described here, using Entity Framework code-first for database storage.
I want it optimized for getting the current value and have lazy loading for the history, but I don't want to have to add boilerplate code in the parent entity for every usage, as is the approach in the link above.
At the moment I have something like the code below, which by convention results in the database schema as shown below the code.
This will function as I need, but for performance reasons I'd like to avoid the join it requires to get the current property value (i.e. I want to move the TemporalStrings.CurrentValue DB column to Entities.Name instead).
If I try
modelBuilder.Entity<Entity>().Property(o => o.Name.CurrentValue).HasColumnName("Name");
it doesn't work. I get an exception like
The type 'ConsoleApplication1.TemporalString' has already been configured as an entity type. It cannot be reconfigured as a complex type.
Is there some way I can achieve this mapping, or is there a better approach for achieving this functionality?
Code:
public class TemporalString
{
public int Id { get; set; }
public string CurrentValue { get; set; } // Setter would be customized to append to History.
public virtual List<TemporalStringValue> History { get; set; }
// Other methods such as string ValueAt(DateTime) would exist.
}
public class TemporalStringValue
{
public int Id { get; set; }
public DateTime EffectiveFrom { get; set; }
public string Value { get; set; }
}
public class Entity
{
public int Id { get; set; }
public virtual TemporalString Name { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet<Entity> Entities { get; set; }
public DbSet<TemporalString> TemporalStrings { get; set; }
public DbSet<TemporalStringValue> TemporalStringValues { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//modelBuilder.Entity<Entity>().Property(o => o.Name.CurrentValue).HasColumnName("Name");
// TODO: Map DB column TemporalStrings.CurrentValue to DB column Entities.Name?
}
}
internal class Program
{
private static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TestDbContext>());
using (var context = new TestDbContext())
{
var entity = new Entity
{
Name = new TemporalString
{
CurrentValue = "Current Value",
History = new List<TemporalStringValue>
{
new TemporalStringValue
{
EffectiveFrom = DateTime.UtcNow,
Value = "Current Value"
},
new TemporalStringValue
{
EffectiveFrom = DateTime.UtcNow.AddMonths(-1),
Value = "Old Value"
},
new TemporalStringValue
{
EffectiveFrom = DateTime.UtcNow.AddMonths(-2),
Value = "Older Value"
}
}
}
};
context.Entities.Add(entity);
context.SaveChanges();
}
Console.Write("Done.");
Console.ReadKey();
}
}
Resulting schema:
Entities
(PK) Id
(FK) Name_Id (references TemporalStrings.Id)
TemporalStrings
(PK) Id
CurrentValue
TemporalStringValues
(PK) Id
EffectiveFrom
Value
(FK) TemporalString_Id
Desired schema:
Entities
(PK) Id
(FK) Name_Id (references TemporalStrings.Id)
Name (formerly TemporalStrings.CurrentValue)
TemporalStrings
(PK) Id
TemporalStringValues
(no change)

how to annotate a parent-child relationship with Code-First

When using the CTP 5 of Entity Framework code-first library (as announced here) I'm trying to create a class that maps to a very simple hierarchy table.
Here's the SQL that builds the table:
CREATE TABLE [dbo].[People]
(
Id uniqueidentifier not null primary key rowguidcol,
Name nvarchar(50) not null,
Parent uniqueidentifier null
)
ALTER TABLE [dbo].[People]
ADD CONSTRAINT [ParentOfPerson]
FOREIGN KEY (Parent)
REFERENCES People (Id)
Here's the code that I would hope to have automatically mapped back to that table:
class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
class FamilyContext : DbContext
{
public DbSet<Person> People { get; set; }
}
I have the connectionstring setup in the app.config file as so:
<configuration>
<connectionStrings>
<add name="FamilyContext" connectionString="server=(local); database=CodeFirstTrial; trusted_connection=true" providerName="System.Data.SqlClient"/>
</connectionStrings>
</configuration>
And finally I'm trying to use the class to add a parent and a child entity like this:
static void Main(string[] args)
{
using (FamilyContext context = new FamilyContext())
{
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred"
};
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
Parent = fred
};
context.People.Add(fred);
var rowCount = context.SaveChanges();
Console.WriteLine("rows added: {0}", rowCount);
var population = from p in context.People select new { p.Name };
foreach (var person in population)
Console.WriteLine(person);
}
}
There is clearly something missing here. The exception that I get is:
Invalid column name 'PersonId'.
I understand the value of convention over configuration, and my team and I are thrilled at the prospect of ditching the edmx / designer nightmare --- but there doesn't seem to be a clear document on what the convention is. (We just lucked into the notion of plural table names, for singular class names)
Some guidance on how to make this very simple example fall into place would be appreciated.
UPDATE:
Changing the column name in the People table from Parent to PersonId allows the Add of fred to proceed. Howerver you'll notice that pebbles is a added to the Children collection of fred and so I would have expected pebbles to be added to the database as well when Fred was added, but such was not the case. This is very simple model, so I'm more than a bit discouraged that there should be this much guess work involved in getting a couple rows into a database.
You need to drop down to fluent API to achieve your desired schema (Data annotations wouldn't do it). Precisely you have an Independent One-to-Many Self Reference Association that also has a custom name for the foreign key column (People.Parent). Here is how it supposed to get done with EF Code First:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOptional(p => p.Parent)
.WithMany(p => p.Children)
.IsIndependent()
.Map(m => m.MapKey(p => p.Id, "ParentID"));
}
However, this throws an InvalidOperationException with this message Sequence contains more than one matching element. which sounds to be a CTP5 bug as per the link Steven mentioned in his answer.
You can use a workaround until this bug get fixed in the RTM and that is to accept the default name for the FK column which is PersonID. For this you need to change your schema a little bit:
CREATE TABLE [dbo].[People]
(
Id uniqueidentifier not null primary key rowguidcol,
Name nvarchar(50) not null,
PersonId uniqueidentifier null
)
ALTER TABLE [dbo].[People] ADD CONSTRAINT [ParentOfPerson]
FOREIGN KEY (PersonId) REFERENCES People (Id)
GO
ALTER TABLE [dbo].[People] CHECK CONSTRAINT [ParentOfPerson]
GO
And then using this fluent API will match your data model to the DB Schema:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOptional(p => p.Parent)
.WithMany(p => p.Children)
.IsIndependent();
}
Add a new Parent record containing a Child:
using (FamilyContext context = new FamilyContext())
{
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
};
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred",
Children = new List<Person>()
{
pebbles
}
};
context.People.Add(fred);
context.SaveChanges();
}
Add a new Child record containing a Parent:
using (FamilyContext context = new FamilyContext())
{
var fred = new Person
{
Id = Guid.NewGuid(),
Name = "Fred",
};
var pebbles = new Person
{
Id = Guid.NewGuid(),
Name = "Pebbles",
Parent = fred
};
context.People.Add(pebbles);
var rowCount = context.SaveChanges();
}
Both codes has the same effect and that is adding a new parent (Fred) with a child (Pebbles).
In Entity Framework 6 you can do it like this, note public Guid? ParentId { get; set; }. The foreign key MUST be nullable for it to work.
class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid? ParentId { get; set; }
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
https://stackoverflow.com/a/5668835/3850405
It should work using a mapping like below:
class FamilyContext : DbContext
{
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Person>().HasMany(x => x.Children).WithMany().Map(y =>
{
y.MapLeftKey((x => x.Id), "ParentID");
y.MapRightKey((x => x.Id), "ChildID");
});
}
}
However that throws an exception: Sequence contains more than one matching element.
Apperently that is a bug.
See this thread and the answer to #shichao question: http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx#10102970
class Person
{
[key()]
public Guid Id { get; set; }
public String Name { get; set; }
[ForeignKey("Children")]
public int? PersonId {get; set;} //Add ForeignKey
public virtual Person Parent { get; set; }
public virtual ICollection<Person> Children { get; set; }
}
builder.Entity<Menu>().HasMany(m => m.Children)
.WithOne(m => m.Parent)
.HasForeignKey(m => m.PersonId);