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; }
}
Related
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?
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 2 entities like below;
public class Order
{
public int Id { get; set; }
public Description { get; set; }
public virtual Purchase Purchase{ get; set;}
}
public class Purchase
{
public int Id { get; set; }
public Description { get; set;}
public int? OrderId { get; set;}
public virtual Order Order { get; set;}
}
Here I create order first.
Based on order i can create purchase.
Purchase can be happened without order also. So it is Nullable foreign key.
For one order, it should have only one purchase. So OrderId is unique also.
How can i specify this relationship in Code First
You can specify a unique attribute like this. You just can't make a unique index the target of a foreign key in EF6.
public class Order
{
public int Id { get; set; }
public string Description { get; set; }
public virtual Purchase Purchase { get; set; }
}
public class Purchase
{
public int Id { get; set; }
public string Description { get; set;}
[Index(IsUnique = true)]
public int? OrderId { get; set; }
public virtual Order Order { get; set; }
}
But EF won't allow a 1-1 relationship to a non-key column, but something like this has the desired relational model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp5
{
public class Order
{
public int Id { get; set; }
public string Description { get; set; }
internal ICollection<Purchase> Purchases { get; } = new HashSet<Purchase>();
public Purchase Purchase { get { return Purchases.FirstOrDefault(); } }
}
public class Purchase
{
public int Id { get; set; }
public string Description { get; set; }
[Index(IsUnique = true)]
public int? OrderId { get; set; }
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
}
public class Db : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<Purchase> Purchases { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasMany(o => o.Purchases).WithOptional(p => p.Order);
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
int OrderId;
using (var db = new Db())
{
var o = db.Orders.Create();
o.Description = "New Order";
var p = db.Purchases.Create();
p.Order = o;
p.Description = "New Purchase";
db.Orders.Add(o);
db.Purchases.Add(p);
db.SaveChanges();
OrderId = o.Id;
}
using (var db = new Db())
{
var p = db.Purchases.Create();
p.OrderId = OrderId;
p.Description = "Another Purchase";
db.Purchases.Add(p);
db.SaveChanges(); //fails
}
}
}
}
David
In the comments to your question, you indicated:
I need to make it (OrderId) unique also
You cannot do that that because EF does not support unique columns except keys.
You may use an index which will allow you to make the index unique but you cannot have a unique key.
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.
So I have a model:
[Table("Site")]
public class Store : MPropertyAsStringSettable {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name { get; set; }
public int CompanyID { get; set; }
public bool IsActive { get; set; }
public string Address { get; set; }
public string City { get; set; }
[Display(Name = "Province")]
public int ProvinceID { get; set; }
public string Postal { get; set; }
public string Phone { get; set; }
public int StoreNumber { get; set; }
public bool visible { get; set; }
public DateTime lastShift { get; set; }
}
The field lastShift is from a different table called "Shifts", how do I get it from that table?
EDIT: The lookup will have to be something like this:
select top 1 shiftDate as lastShift from [Shifts] where SiteID = Store.ID
This is how I load my data:
public class MyDbContext: DbContext {
public MyDbContext()
: base("name=DefaultConnection") {
}
public DbSet<UserAccount> UserAccounts { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<ProvinceModel> Provinces { get; set; }
}
And this is how I use it:
MyDbContext database = new MyDbContext();
var myStores = from database.Stores select ID;
EDIT according to your last edit, this is not the case
It will need to be a "navigation property" which means that you'll need to have an explicit (Foreing Key) relationship between Site and Ship
Then you'll have something like this
Store.Shift.LastShift
But if it is a one to many relationship (and LastShift field is not part of Shift table) then
you'll need to do it manually, and use a view model or a custom property that it is not mapped directly to the table and do the assignment somewhere in your code
If you're using a Repository, then you'll need to add a method there to get the last shift
Then (or if you are using ORM directly) you use the code that #Cristi posted, just remember to add the sorting
public ActionResult MyAction(){
var store = db.Stores.Where(x => x.ID == objId).Select(x => new StoreModel(){
Name = x.Name,
ID = x.ID,
lastShift = db.Shifts.FirstOrDefault(y => y.SiteID == x.ID).OrderByDescending(shift => shift.Date);
}).FirstOrDefault();
return View(store);
}
Here is how I solved the problem.
TL,DR: I shared my dbcontext in my controller so I have access to it in my models!
I manually load the lastShiftTime in my Store Model with the following code:
public class Store : MPropertyAsStringSettable {
.......
public DateTime lastShiftTime {
get{
MyDbContext curContext = MyWebProject.Controllers.MyBaseController.database;
if (curContext != null) {
return (from shift in curContext.Shifts where shift.SiteID == ID select shift.ShiftDate).First();
} else {
return new DateTime(0);
}
}
}
}
Here is my Shift Model I created for this, very standard:
[Table("Shifts")]
public class Shift : MPropertyAsStringSettable {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int SiteID { get; set; }
public string ShiftID_In_POS { get; set; }
public DateTime ShiftDate { get; set; }
}
And here is how I am sharing the context in controller:
public class MyBaseController : Controller {
.........
private static MyDbContext _database;
public static MyDbContext database {
get {
if (_database == null) {
_database = new MyDbContext();
}
return _database;
}
}
}