Zero to One Relationship in EF Code First - c#

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.

Related

How to add a foreign key into a table using one-to-one relationship in ASP.NET Core 6 Web API?

I am new to ASP.NET, how can I make my item table save the categoryID which I get from the category table? As an example I want the item "Apple mobile phone" to have the category ID which refers to category name which is electronics, I hope you got the picture.
Here is model:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WebApplication1.Models
{
public class itemTable
{
[Key]
public int Id { get; set; }
public string company { get; set; }
public string availability { get; set; }
public decimal price { get; set; }
public decimal discount { get; set; }
public decimal tax { get; set; }
public string description { get; set; }
public int categoryid { get; set; }
}
public class categories
{
[Key]
public int categoryID { get; set; }
public string categoryName { get; set; }
}
}
And here is my DbContext:
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
using WebApplication1.Models;
namespace WebApplication1.Context
{
public class itemTableDbContext : DbContext
{
public itemTableDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<itemTable> ItemTables { get; set; }
public DbSet<categories> categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<itemTable>().ToTable("itemtable");
modelBuilder.Entity<categories>().ToTable("categories");
}
}
}
I tried all the possible ways but there is always something wrong, multiple items might be in the same category like phone brand items are all under the "electronics" category
You can configure One-to-One relationship between two entities in such way:
public class ItemTable
{
[Key]
public int Id { get; set; }
public string Company { get; set; }
public string Availability { get; set; }
public decimal Price { get; set; }
public decimal Discount { get; set; }
public decimal Tax { get; set; }
public string Description { get; set; }
public virtual Category Category { get; set; }
}
public class Category
{
[Key]
[ForeignKey("ItemTable")]
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public virtual ItemTable ItemTable { get; set; }
}
Note: Better to use PascalCase in C#

Unable to retrieve metadata for xyz.model.xxx in mvc

I am using entity-framework in mvc. I am receiving this error while generating view.I am using MVC controller with read/write actions and views, using EF. I am Trying to generate a list using scaffold template.
This Entity Framework auto generated class
namespace WebApplication3.Models
{
using System;
using System.Collections.Generic;
public partial class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string City { get; set; }
public Nullable<System.DateTime> DOB { get; set; }
public Nullable<int> DepartmentId { get; set; }
public virtual TblDepartment TblDepartment { get; set; }
}
}
Here is my Controller Code :
public ActionResult EmployeesByDep()
{
var employees = db.Employees.Include("TblDepartment").GroupBy(x => x.TblDepartment.DepName)
.Select(y => new TotalDepartments
{
DepName = y.Key,
Total = y.Count()
}
).ToList().OrderByDescending(y=>y.Total);
return View(employees);
}
Model Code:
public string DepName { get; set; }
public int Total { get; set; }
The problem is because you had not declared a key.
You should create a new class EmployeeMetaData.cs
With:
[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee
{
}
public class Employee
{
[Key]
public int EmployeeId { get; set; }
}
add: using System.ComponentModel.DataAnnotations;

EF adding columns to bridge table and accessing data

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; }
}

ASP.NET MVC How to access Entity Framework generated foreign 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.

Linq to Entities any way to regenerate only the changes?

I am trying to update a CART. When I am doing the search for it, it fails. It used a GUID since the user is not logon. I don't know if I have to re-update the DBEntities maybe ? not sure.
I got this error : The specified type member 'CartId' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Thanks
ShoppingCart.cs
namespace Tp1WebStore3.Models
{
public partial class ShoppingCart
{
Tp1WebStoreDBEntities db = new Tp1WebStoreDBEntities();
string ShoppingCartId { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
// Helper method to simplify shopping cart calls
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public void AddToCart(Produit produit)
{
// Get the matching cart and album instances
var cartItem = db.Paniers.SingleOrDefault(
c => c.CartId == ShoppingCartId &&
c.ProduitId == produit.ProduitId); <== the error happen here
Panier.cs
namespace Tp1WebStore3.Models
{
using System;
using System.Collections.Generic;
public partial class Panier
{
public int PanierId { get; set; }
public string CartId { get; set; }
public int ProduitId { get; set; }
public int Quantite { get; set; }
public decimal Prix { get; set; }
public System.DateTime DateCree { get; set; }
public virtual Produit Produit { get; set; }
}
}
produit.cs
namespace Tp1WebStore3.Models
{
using System;
using System.Collections.Generic;
public partial class Produit
{
public Produit()
{
this.Paniers = new HashSet<Panier>();
}
public int ProduitId { get; set; }
public int CategorieId { get; set; }
public string Description { get; set; }
public int Quantite { get; set; }
public decimal Prix { get; set; }
public virtual Categorie Categorie { get; set; }
public virtual ICollection<Panier> Paniers { get; set; }
}
}
If you changed things in the database you have to update the Data model. Right click inside your .edmx file where your entity diagram is and click Update model from db

Categories