I want to use table-per-type mapping pattern for 2 tables - Blog and Item. And Item is a relation with Blog. My entities:
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Item> Items { get; set; } = new();
}
public class Item
{
public int ItemId { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
public string Name { get; set; }
}
I enable TPT for both tables:
modelBuilder.Entity<Blog>().UseTptMappingStrategy();
modelBuilder.Entity<Item>().UseTptMappingStrategy();
And this are derived entitites:
public class RssBlog : Blog
{
public string RssUrl { get; set; }
public List<RssItem> RssItems { get; set; } = new();
}
public class RssItem : Item
{
public string RssName { get; set; }
}
This is sample code with expectation that EF will create 4 records (one per each table) in the DB. But this code failed with error insert or update on table "Items" violates foreign key constraint "FK_Items_Blogs_BlogId".
var rssBlog = new RssBlog()
{
RssUrl = "rssUrl",
Url = "url",
RssItems = new List<RssItem>()
{
new()
{
Name = "name",
RssName = "rssname"
}
}
};
db.RssBlogs.Add(rssBlog);
await db.SaveChangesAsync();
So what is a proper way to configuring inheritance with relations?
Related
I'm trying to Deserialize data into an object, and then add that object to a sqlite database with Entity Framework.
The first part is working fine.
But if try to add the object to the database I get this error:
System.InvalidOperationException: 'The instance of entity type 'Client' cannot be
tracked because another instance with the same key value for {'id'} 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.'
Is it not possible to add the Rootobject with all the subclasses in one go?
Here is my code, I did not include all the sub classes.
All classes has an Id field:
HttpClient client = new HttpClient();
//Add headers etc. ...
var result = await client.GetAsync(url);
string? content = await result.Content.ReadAsStringAsync();
var TimeEntries = JsonConvert.DeserializeObject<Rootobject>(content);
using (var database = new DataContext())
{
database.Add(TimeEntries);
database.SaveChanges();
}
public class DataContext : DbContext
{
public DbSet<Rootobject>? RootObjects { get; set; }
public DbSet<Link>? Links { get; set; }
public DbSet<Time_Entry>? Time_Entries { get; set; }
public DbSet<User>? Users { get; set; }
public DbSet<Client>? Clients { get; set; }
public DbSet<Project>? Projects { get; set; }
public DbSet<Task>? Tasks { get; set; }
public DbSet<User_Assignment>? User_Assignments { get; set; }
public DbSet<Task_Assignment>? Task_Assignments { get; set; }
public DbSet<External_Reference>? External_References { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder dbContextOptionsBuilder)
{
dbContextOptionsBuilder.UseSqlite("Data Source=data.db");
}
}
public class Rootobject
{
public int Id { get; set; }
public ICollection<Time_Entry>? Time_entries { get; set; }
public int Per_page { get; set; }
public int Total_pages { get; set; }
public int Total_entries { get; set; }
public int ?Next_page { get; set; }
public int ?Previous_page { get; set; }
public int Page { get; set; }
public Link? Links { get; set; }
}
I have below classes spacetype and mastersection which is having below structure
public class SpaceType
{
public Guid Id { get; set; }
[ForeignKey("MasterSection")]
public string MasterSectionName { get; set; }
public virtual MasterSection MasterSection { get; set; }
[ForeignKey("LibrarySpaceTypeCategory")]
public Guid? LibrarySpaceTypeCategoryId { get; set; }
public virtual LibrarySpaceTypeCategory LibrarySpaceTypeCategory { get; set; }
[Column(TypeName = "jsonb")]
public MechanicalData MechanicalData { get; set; }
}
public class MasterSection
{
[Key, GraphQLNonNullType]
public string Name { get; set; }
public List<SectionEmployee> SectionOwners { get; set; } = new List<SectionEmployee>();
[ForeignKey("ParentMasterSection"), GraphQLIgnore]
public string ParentMasterSectionName { get; set; }
public virtual MasterSection ParentMasterSection { get; set; }
}
and i am trying to update mastersection object for all spacetypes like as below
var spaceTypes = ctx.SpaceTypes.ToList();
foreach(var spacetype in spaceTypes)
{
spacetype.MasterSection = masterSectionMappedLibrary["Space Type"];
}
ctx.SaveChanges();
and below is the data that I am getting from masterSectionMappedLibrary["Space Type"]
and getting an error here ctx.SaveChanges(); like Duplicate key violates unique constraint on PK_Mastersections.
Could any one please let me know where i am doing wrong and i am using EF core with postgreSQL.
Many thanks in advance
Update:
Try this
var spaceTypes = ctx.SpaceTypes.ToList();
foreach(var spacetype in spaceTypes)
{
spacetype.MasterSectionName = masterSectionMappedLibrary["Space Type"].Name;
}
ctx.UpdateRange(spaceTypes);
ctx.SaveChanges();
After you save the updated value to db, try to retrieve it again
var spaceTypesUpdated = ctx.SpaceTypes
.Include("MasterSection.ParentMasterSection")
.Include("SectionOwners")
.ToList();
// serialize spaceTypesUpdated to json
I have two EF models -
public class What
{
[Key]
public int primary_key_What { get; set; }
public int another_column_What { get; set; }
public virtual ICollection<Why> Whys { get; set; }
}
And
public class Why
{
[Key]
public int primary_key_Why { get; set; }
public int some_column_Why { get; set; }
public virtual What What { get; set; }
}
The problem is, I have to use another_column_What and some_column_Why for navigating between the two. As you can see none of them are keys or declared unique in the database, also their names are different.
I've tried all the ways I could imagine of and found on search, but none of them works. How and in which model mapping do I use to say, navigate between What and Why using another_column_What and some_column_Why columns.
So whenever a query is generated by EF, it will compare another_column_What with some_column_Why?
Very unfortunately, changing the database architecture (or even column names) is not an option here.
Can anyone please help me with it?
This should work:
Db schema:
What model:
[Table("what")]
public class what
{
[Key]
[Column("primary_key_what")]
public int primary_key_what { get; set; }
[Column("another_column_what")]
public int another_column_what { get; set; }
public virtual ICollection<why> whys { get; set; }
}
Why model:
[Table("why")]
public class why
{
[Key]
[Column("primary_key_why")]
public int primary_key_why { get; set; }
[ForeignKey("what")]
[Column("some_column_why")]
public int some_column_why { get; set; }
public virtual what what { get; set; }
}
Context:
public class Context : DbContext
{
public virtual DbSet<what> what { get; set; }
public virtual DbSet<why> why { get; set; }
public Context() : base("name=SqlConnection")
{
}
}
Main:
static void Main(string[] args)
{
using (var context = new Context())
{
var results = from w in context.what
select w;
foreach (var what in results)
{
Console.WriteLine("what.primary_key_what = {0}", what.primary_key_what);
Console.WriteLine("what.another_column_what = {0}", what.another_column_what);
Console.WriteLine("what has {0} whys", what.whys.Count);
foreach (var why in what.whys)
{
Console.WriteLine("Why.primary_key_why = {0}", why.primary_key_why);
Console.WriteLine("Why.some_column_why = {0}", why.some_column_why);
}
}
}
}
What data:
Why data:
Output:
I have a many-to-many relationship between two tables. EF created (model first) created the bridge table for me with two columns that has links the two keys together. All good and fine. But I want to add a few columns to the bridge table for holding additional data that's relevant to a particular relationship.
If I do an alter table add <column name> on the bridge table, then how can I access the data using EF? Is it possible? I guess I can always do a straight SQL query..but that's so 90's.
Here is an example how can you do that:
The default brdige table name is
dbo.Table1NameTable2Name but you can customize it.
you can also use automapper to create a generic soultion.
Example:
public class Account
{
public int AccountId { get; set; }
public virtual List<Operation> Operations { get; set; }
}
public class Operation
{
public Int32 OperationId { get; set; }
public virtual List<Account> Accounts { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Operation> Operations { get; set; }
public DbSet<Account> Accounts { get; set; }
public MyDbContext()
: base("name=cs")
{
}
}
public class OperationAccounts
{
public int AccountId { get; set; }
public int OperationId { get; set; }
public string ExtraInfo { get; set; }
}
public static ICollection<OperationAccounts> GetOperationAccounts(string connectionString = #"Data Source=.\;Initial Catalog=TestDb;Integrated Security=true")
{
ICollection<OperationAccounts> dict = new List<OperationAccounts>();
var sqlBuilder = new SqlConnectionStringBuilder(connectionString);
using (var con = new SqlConnection(connectionString))
{
con.Open();
var cmd = con.CreateCommand();
cmd.CommandText = "SELECT * FROM OperationAccounts";
using (var rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection))
{
while (rdr.Read())
{
var accountId = rdr.GetInt32(0);
var opertationId = rdr.GetInt32(1);
var extraColumn = rdr.IsDBNull(2)? string.Empty : rdr.GetString(2);
dict.Add(new OperationAccounts() { AccountId = accountId, OperationId = opertationId, ExtraInfo = extraColumn });
}
}
}
return dict;
}
public static void SetOperationAccounts(ICollection<OperationAccounts> operationAccounts, string connectionString = "name=cs")
{
// Your homework same as GetOperationAccounts
}
static void Main(string[] args)
{
Database.SetInitializer<MyDbContext>(new CreateDatabaseIfNotExists<MyDbContext>());
using (var dbContext = new MyDbContext())
{
dbContext.Database.ExecuteSqlCommand(#"ALTER TABLE OperationAccounts ADD ExtraInfo VARCHAR(20) NULL; ");
var account = new Account();
var operation = new Operation();
account.Operations = new List<Operation> { operation };
operation.Accounts = new List<Account> { account };
dbContext.Accounts.Add(account);
dbContext.SaveChanges();
var oas = GetOperationAccounts();
foreach (var oa in oas)
{
oa.ToString();
}
}
}
Setup your bridge tables manually:
public class User
{
// User stuff
// Bridge table
public virtual ICollection<UserFile> Files { get; set; }
}
public class File
{
// Other File stuff ....
// Bridge table
public virtual ICollection<UserFile> Users { get; set; }
}
// Bridge table
public class UserFile
{
public User User { get; set; }
public File File { get; set; }
public DateTime CreatedDate { get; set; }
// Other metadata here.
}
You may need to setup the relationships in your context override OnModelCreating()
modelBuilder.Entity<UserFile>()
.HasRequired(i => i.User)
.WithMany(i => i.Files)
.WillCascadeOnDelete(true);
You can use the default bridge table but there is an easier way. Here is a sample of the bridge table with extra fields.
In this example, we have two entity table and one bridge table
public class Student
{
public int id { get; set; }
public string FullName { get; set; }
public IList<CourseStudent> CourseStudents { get; set; }
}
public class Course
{
public int id { get; set; }
public string CourseName { get; set; }
public IList<CourseStudent> CourseStudents { get; set; }
}
//bridge Table
public class CourseStudent
{
public Student Student { get; set; }
[Key, Column(Order = 0)]
public int Studentid { get; set; }
public Course Course { get; set; }
[Key, Column(Order = 1)]
public int Courseid { get; set; }
//You can add foreign keys like this
//public Yourclass Yourclass{ get; set; }
//[key, Column(Order = )]
//public Yourclasstype(int,string or etc.) Yourclassid{ get; set; }
//Other data fields
public DateTime RegisterDate { get; set; }
}
Now Add this to your DBcontext
public class Yourdbcontextname: DbContext
{
public BridgeDB() : base("name=EFbridge")
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<CourseStudent> CourseStudents { get; set; }
}
Song has a many to many relationship with it's self. I store these ids in a class called SimilarVersion with both id columns.
public class Song
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
public virtual ICollection<SimilarVersion> SimilarVersions { get; set; } = new List<SimilarVersion>();
}
public class SimilarVersion
{
public int Id { get; set; }
public int? Song_Id1 { get; set; }
}
View Models:
public class SongDto
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
public ICollection<SimilarSongDto> SimilarSongDtos { get; set; } = new List<SimilarSongDto>();
}
public class SimilarSongDto
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
}
The SimilarVersion table in my database now has Id,Song_Id,Song_Id1, as EF has generated Song_Id. How do I get to use that EF generated column in my code though?
_similarVersionService.GetSimiliarVersion().Song_Id will give me an error because there is no property in the class called that. I could manually add it to the class like I have done with Song_Id1 and remove the collection from the other class but I think I must be doing something wrong. Also please tell me if there is a better way of mapping this.
I tried adding public int Song_Id { get; set; } and it just made another column in my table called Song_Id2.
public ActionResult Song(int id)
{
//Map the domainModel to songViewModel
var songDto = Mapper.Map<Song, SongDto>(_songService.GetSong(id));
//Get all of the songs where the id == the Song_Id column in similar version table
var songs = _songService.GetSongs().ToList()
.Where(x => x.SimilarVersions.Any(z => z.Song_Id == songDto.Id))
.ToList(); //z.Song_Id no definition found
//Map the Song domain to SimilarSong ViewModel and assign it to the songDto to be passed to the view
songDto.SimilarSongDtos = Mapper.Map<ICollection<Song>, ICollection<SimilarSongDto>>(songs);
return View(songDto);
}
Edit. Trying to add to a row based on Admir answer:
var songToUpload = new Song
{
AudioName = uploadSongDtos[i].AudioName.Trim(),
ArtistName = uploadSongDtos[i].ArtistName,
};
foreach (var compareAgainstString in _songService.GetSongs().ToDictionary(x => x.Id, x => x.AudioName))
{
var score = SearchContext.Levenshtein.iLD(songToUpload.AudioName, compareAgainstString.Value);
//Don't add the current song
if (score < 50 && songToUpload.Id != compareAgainstString.Key)
songToUpload.SimilarVersionsWhereSimilar.Add(new SimilarVersion { SimilarId = compareAgainstString.Key });
}
Both OriginalId and SimilarId are assigned to whatever the id of songToUpload.Id is given the relationship we defined in models, which is correct for OriginalId but it is also overriding my custom set SimilarId above. How can I stop this?
You can take this approach:
public class Song
{
public int Id { get; set; }
public string ArtistName { get; set; }
public virtual IList<Similarity> SimilaritiesWhereOriginal { get; set; }
public virtual IList<Similarity> SimilaritiesWhereSimilar { get; set; }
}
public class Similarity
{
public int Id { get; set; }
public int OriginalId { get; set; }
public virtual Song Original { get; set; }
public int SimilarId { get; set; }
public virtual Song Similar { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<Song> Songs { get; set; }
public DbSet<Similarity> Similarities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Song>().HasMany(x => x.SimilaritiesWhereOriginal).WithRequired(x => x.Original).WillCascadeOnDelete(false);
modelBuilder.Entity<Song>().HasMany(x => x.SimilaritiesWhereSimilar).WithRequired(x => x.Similar).WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
}
Similarity class shows relationship between "original" song and "similar" song. This class replaces EF auto-generated table with your own many-2-many relationship table that you can access from the code.
It is likely the ID is actually generated by your Store. Call Context.SaveChanges() to create it, then read the ID.