im working on my web app. Im using .NET and EntityFrameworkCore. I have created migration and database. Simply I just want to add item to the database but I encounter an error when sending a request to this particular endpoint. I attach some of my code below. I also attach link to my github repo https://github.com/szymi-dev/TestRepo
[HttpPost("add-product")]
public async Task<ActionResult<Product>> AddProduct(ProductDto productDto)
{
var product = new Product
{
Name = productDto.Name,
Price = productDto.Price,
Descripiton = productDto.Descripiton,
PictureUrl = productDto.PictureUrl
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
return product;
}
Here is productDTO class
public class ProductDto
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Descripiton { get; set; }
public string PictureUrl { get; set; }
}
I have also added some Entity Configurations
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.Property(p => p.Id).IsRequired();
builder.Property(p => p.Name).IsRequired().HasMaxLength(50);
builder.Property(p => p.PictureUrl).IsRequired();
builder.Property(p => p.Descripiton).HasMaxLength(180).IsRequired();
builder.Property(p => p.Price).HasColumnType("decimal(18, 2)");
builder.HasOne(p => p.ProductBrand).WithMany().HasForeignKey(k => k.ProductBrandId);
builder.HasOne(p => p.ProductType).WithMany().HasForeignKey(k => k.ProductTypeId);
builder.HasOne(p => p.User).WithMany(p => p.Products).HasForeignKey(k => k.UserId);
}
}
Finally im getting " SQLite Error 19: 'FOREIGN KEY constraint failed'" error in Postman and
Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (6ms) [Parameters=[#p0='?' (Size = 14), #p1='?' (Size = 6), #p2='?' (Size = 5), #p3='?', #p4='?', #p5='?', #p6='?'], CommandType='Text', CommandTimeout='30']
INSERT INTO "Products" ("Descripiton", "Name", "PictureUrl", "Price", "ProductBrandId", "ProductTypeId", "UserId")
VALUES (#p0, #p1, #p2, #p3, #p4, #p5, #p6);
SELECT "Id"
FROM "Products"
WHERE changes() = 1 AND "rowid" = last_insert_rowid(); ~ in VScode
There are a couple of issues I see with your example and your schema. Firstly, as mentioned your Product has a ProductType and ProductBrand reference that you will need to set. Typically in your UI you would have something like a drop-down list populated with Types and Brands to select from, so your Product DTO would need a ProductTypeId and ProductBrandId to represent these selections.
Entities can have a FK exposed (Which your entities do appear to have based on the HasForeignKey property, so you can set these when creating your product. Otherwise it is generally better to set navigation property references as this validates that the provided IDs are actually a valid product type and brand:
var productType = _context.ProductTypes.Single(x => x.ProductTypeId == productDto.ProductTypeId);
var productBrand = _context.ProductBrands.Single(x => x.ProductBrandId == productDto.ProductBrandId);
var product = new Product
{
Name = productDto.Name,
Price = productDto.Price,
Descripiton = productDto.Descripiton,
PictureUrl = productDto.PictureUrl,
ProductType = productType,
ProductBrand = productBrand
};
I don't generally recommend having FK properties exposed when you have navigation properties as this forms 2 sources of truth for the relationship. This is generally only an issue when updating, not creating though.
Lastly you have a User reference, which I think may be a bit of a problem depending on what this relationship is actually intended for. A common case for this would be something like tracking a CreatedBy type relationship, though this would be a many-to-one scenario where the user would not bother maintaining a relationship back to the products (and other entities) that they created. Your User has a Products collection defined, so I would take a close look at what this relationship should be. It seems odd that a product would be associated to a single user in a relationship under a user. Where I wanted to associate users to products like this I would typically expect to see a many-to-many relationship where a User has products associated to them, but those products could be associated to many users. (Rather than distinct products assigned exclusively to one user)
As it stands your product has a required User reference, so if this needs to be associated to the current user then I would fetch the current user ID from your Authentication state or session state and assign that to the Product on creation as well. Again either setting the FK or loading a User entity and setting the User navigation property
You should fill ProductBrandId, ProductTypeId and UserId fields in Product object with existing (or freshly added) values to avoid Foreign key constraint violations.
Related
public class Book
{
public int Id { get; set; }
public string Name{ get; set; }
public string ISBN { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name{ get; set; }
public ICollection<Book> Books { get; set; }
}
I a using Entity Framework core 6 with .NET 6.
I am trying to update the Categories of a specific Book.
For example, If one book has categories like .NET, C# then I want to update categories into .NET, EF Core, SqlServer, I think you get it now.
Do I need to add a Join entity for only the Update operation? As you can see I have not created any Join entity like BookCategories though I managed to Insert categories while creating Book for the first time.
But when trying to update the book with new categories I am getting two issues.
The old category is not deleted.
And getting Duplicate Error Key while trying to update with existing category, in this case, .NET.
Please kindly show the proper way of updating related entities in Entity Framework Core 6 in .NET6.
Many-to-Many relationships need a bit of configuration depending on what you want out of the relationship. If you just want the linking table to manage the link and nothing else:
[BookCategories]
BookId (PK, FK)
CategoryId (PK, FK)
Then you can set up the relationship to either use an entity definition or a shadow entity. In both cases this is typically preferable since your Book can have a collection of Categories, and the Category can have a collection of books. With Code-First and Migrations I believe EF can and will set up this linking table automatically. Otherwise you can use OnModelCreating or an EntityTypeConfiguration to configure what Table and Columns to use for the relationship.
This can be done either with an Entity declared for BookCategory, or without one:
With entity:
modelBuilder.Entity<Book>()
.HasMany(x => x.Categories)
.WithMany(x => Books);
.UsingEntity<BookCategory>(
l => l.HasOne<Book>().WithMany().HasForeignKey(x => x.BookId),
r => r.HasOne<Category>().WithMany().HasForeignKey(x => x.CategoryId),
j =>
{
j.HasKey("BookId", "CategoryId");
j.ToTable("BookCategories");
});
Without entity: (See Scaffolding many-to-many relationships - https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/whatsnew)
modelBuilder.Entity<Book>()
.HasMany(x => x.Categories)
.WithMany(x => Books);
.UsingEntity<Dictionary<string, object>>(
"BookCategories",
l => l.HasOne<Book>().WithMany().HasForeignKey("BookId"),
r => r.HasOne<Category>().WithMany().HasForeignKey("CategoryId"),
j =>
{
j.HasKey("BookId", "CategoryId");
j.ToTable("BookCategories");
});
Alternatively, if the joining table needs to contain additional relevant details, for example if you are using a soft-delete system and want to mark deleted relationships as inactive rather than deleting those rows, then you have to adopt an indirect relationship using a BookCategory entity where Book has a collection of BookCategories, as does Category. (See Join entity type configuration - https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key)
Once you have your relationships set up, it is important to treat these relationships as associations, not copies of data. This means you should ensure that your collections are initialized on construction, and never reset. You can add items to the collection or remove items from the collection, but you should never have code that resets the collection. (I.e. no code that does stuff like book.Categories = new List<Category>() or book.Categories = myUpdatedCategories etc.) While EF is tracking entities, it is relying on proxies to help with change tracking to know when data needs to be added, removed, or updated. This also means if you want to "change" a book's category, this is a remove and add, not an update.
For instance to change a book's category from "Java" to ".Net", you don't want to do something like:
var book = context.Books.Include(x => x.Categories).Single(x => x.BookId == bookId);
var category = book.Categories.SingleOrDefault(x => x.CategoryName == "Java");
if (category != null)
category.CategoryName = ".Net"; // or category.CategoryId = dotNetCategoryId;
This would attempt to modify the Category record to change it's Name (likely not intended) or attempt to change it's PK. (illegal)
Instead, you want to change the association:
var dotNetCategory = context.Categories.Single(x => x.CategoryId == dotNetCategoryId);
var book = context.Books.Include(x => x.Categories).Single(x => x.BookId == bookId);
var category = book.Categories.SingleOrDefault(x => x.CategoryName == "Java");
if (category != null)
{
book.Categories.Remove(category);
book.Categories.Add(dotNetCategory);
}
Behind the scenes, EF will delete the BookCategory linking the book to Java category, and insert a BookCategory with the new .Net association. If you have a joining entity then you will just need to remove, add, or update the BookCategory entity specifically based on the relationship changes you want to make.
Currently, I have a service that grabs user information from the User table. The users can be created by admins or an employee and all of these employees have their own Id. So with that in mind, there is a column called CreatedBy which holds the id of this admin, or the user, that of which's name I have to return. So far I've pulled the user model but now I need to create the part where I pull the user's name with the user.Id in the CreatedBy
This is what I have pulling from my database tables Users and Company and the query parameters are just a name or company name
public async Task<List<ApplicationUser>> SearchUsers(UserSearchDto userSearchDto)
{
userSearchDto.FirstName ??= string.Empty;
userSearchDto.LastName ??= string.Empty;
userSearchDto.CompanyName ??= string.Empty;
return await _locationDbContext.Users
.Include(nameof(Company))
.Where(user => user.FirstName.Contains(userSearchDto.FirstName)
&& user.LastName.Contains(userSearchDto.LastName)
&& user.Company.Company_Name.Contains(userSearchDto.CompanyName))
.ToListAsync();
}
So within this list that I am returning I'm trying to do another query to grab more user information based on the CreatedBy id's returned in the first service to bring back the name of those users with the id's in CreatedBy.
var userDtos = _mapper.Map<List<ApplicationUser>, List<UserDetailsDto>>(users);
foreach (UserDetailsDto user in userDtos)
{
user.CreatedByName = await _userService
.SearchUsers(userDtos.Where(user.Id == user.CreatedBy))
}
I feel like this is a possible solution so far but I'm not sure where or how I would pull that because this solution is giving me an error at the point where I use the ".Where" statement. Maybe if I could create another service that would return the user by Id instead and use the createdby Id to pull the name but nothing like that exists yet. The model I'd like to return is also a bit different from the model representing the Users table as ApplicationUser has the CreatedBy which is an Id but the returned model, userDetailsDto will have a name string property as well that I will try and assign here in automapper. If I can think of how I can assign the name by the Id.
CreateMap<ApplicationUser, UserDetailsDto>()
.ForMember(dest => dest.CompanyName,
opts => opts.MapFrom(src => src.Company.Company_Name));
Ideally this is something that you should be able to resolve using navigation properties. If your User table uses CreatedBy to represent the CreatedBy User ID then you could adjust your mapping to facilitate a CreatedBy navigation property:
public class User
{
public class UserId { get; set; }
// ...
public virtual User CreatedBy { get; set; }
}
Then in the mapping use a shadow property for the FK association: (in OnModelCreating or using an EntityTypeConfiguration)
EF Core
.HasOne(x => x.CreatedBy)
.WithMany()
.HasForeignHey("CreatedBy") // Property on Users table
.Required();
EF 6
.HasRequired(x => x.CreatedBy)
.WithMany()
.Map(x => x.MapKey("CreatedBy")) // Property on Users table
Alternatively if you want the CreatedBy FK accessible in the User table, map it as something like CreatedByUserId:
public class User
{
public int UserId { get; set; }
// ...
[ForeignKey("CreatedBy"), Column("CreatedBy")]
public int CreatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
}
Now when you go to search for your users, you can project your CreatedBy user ID and Name in one go.
When it comes to optional search parameters you should keep the conditionals (if/else/ null checks etc ) outside of the Linq wherever possible. This helps compose more efficient queries rather than embedding conditional logic into the SQL.
public async Task<List<ApplicationUserViewModel>> SearchUsers(UserSearchDto userSearchDto)
{
var query = _locationDbContext.Users.AsQueryable();
if (!string.IsNullOrEmpty(userSearchDto.FirstName))
query = query.Where(x => x.FirstName.Contains(userSearchDto.FirstName));
if (!string.IsNullOrEmpty(userSearchDto.LastName))
query = query.Where(x => x.LastName.Contains(userSearchDto.LastName));
if (!string.IsNullOrEmpty(userSearchDto.CompanyName))
query = query.Where(x => x.Company.Name.Contains(userSearchDto.CompanyName));
return await query.ProjectTo<ApplicationUserViewModel>(_config)
.ToListAsync();
}
Where _config reflects an automapper MapperConfiguration containing the details on how to map a User to your desired view model. If you're not leveraging Automapper you can accomplish this using Select to project the values. Other considerations there would be to consider using StartsWith instead of Contains, perhaps offering an option to perform a more expensive Contains search... Also adding things like minimum search length checks (I.e. 3+ characters) and pagination or result row limits (I.e. Take(50)) to avoid outrageous search requests from hammering your system. (I.e. searching for users with "e" in the first name)
That view model might have UserId, UserName, CompanyName, then things like CreatedByUserId, CreatedByName. To resolve the CreatedBy details you just reference u.CreatedBy.UserId and u.CreatedBy.Name either in the Automapper config or within your Select(u => new ApplicationUserViewModel { ... }) statement.
I'm using Entity Framework Core and have two tables in my database :
Table 1 (Contract)
Columns : ContractNumber, ContractCode, ProductType
Table 2 (ContractRole)
Columns: ContractNumber, ContractCode, ProductType, RoleType, RoleName
So, my database doesn't have a foreign key, instead I use two columns (contractnumber, contractcode) to reference tables.
My goal is to create my entities, so that I can fetch contracts and then for each Contract I can extract a relevant list of ContractRoles. That means using navigation properties.
My code will be something like:
[Table("XXXXX")]
public class Contract
{
public Contract()
{
ContractRoles = new HashSet<ContractRole>();
}
public ICollection<ContractRole> ContractRoles { get; set; }
}
If I had a direct contractId foreign key then I could do:
modelBuilder.Entity<ContractRoles>()
.HasOne(x => x.Contract)
.WithMany(x => x.ContractRoles)
.HasForeignKey(x => x.ContractId);
But I don't! Therefore, I need the reference to be made to two fields: contract number and contract code. Is it possible?
I did make it work by fetching the flat data with a query and then building my proper objects (Contract object with a list of ContractRoles) later:
var result = (from s in _dbContextReadOnly.Contracts
join sa in _dbContextReadOnly.ContractRoles
on new { s.ContractNumber, s.ContractCode } equals new { sa.ContractNumber, sa.ContractCode }
select new FlatContractWithContractRoles
{
ContractNumber = s.ContractNumber,
ContractCode = s.ContractCode,
RoleType = sa.RoleType,
RoleName = sa.RoleName
}).Distinct().ToList();
Please don't advise me to modify the database at the source, it is not a possibility. I just want to know if I can fetch a Contract with a list of ContractRoles using the navigation properties directly.
Thanks :) !
I got it! I could just do :
.HasForeignKey(x => new { x.ContractNumber, x.ContractCode });
I have a database generated by application which I can't modify (I can add tables, views and such but I can't modify existing tables, add columns to them). I work on a web application which uses BreezeJS to allow the client-side part of the web app query for the data via OData protocol.
Measurement table has following structure:
MeasurementId INT
DeviceId INT FOREIGN KEY REFERENCES Devices (DeviceId)
Name VARCHAR,
PRIMARY KEY (MeasurementId)
What I need is to add nullable ParentId self referencing foreign key and because I can't modify existing tables, I've created new one, Measurement_Parent:
MeasurementId INT FOREIGN KEY REFERENCES Measurements (MeasurementId),
ParentId INT FOREIGN KEY REFERENCES Measurements (MeasurementId),
PRIMARY KEY (MeasurementId)
I have following entity:
public partial class Measurement
{
public Measurement()
{
this.Children = new List<Measurement>();
}
public Int32 MeasurementId { get; set; }
public virtual Measurement Parent { get; set; }
public Int32 DeviceId { get; set; }
public virtual Device Device { get; set; }
public String Name { get; set; }
public virtual ICollection<Measurement> Children { get; set; }
}
Now the tricky part. I've tried many different approaches to get this working but without success. Current EntityTypeConfiguration for my entity looks like this:
// Primary Key
this.HasKey(m => m.MeasurementId);
// Table & Column Mappings
this.Property(t => t.MeasurementId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// Table & Column Mappings
this.ToTable("Measurement");
this.Property(m => m.MeasurementId);
this.Property(m => m.DeviceId);
this.Property(m => m.Name);
// Relationships
// Each measurement references device performing the measurement.
this.HasRequired(d => d.Device)
.WithMany(m => m.Measurements)
.HasForeignKey(d => d.DeviceId);
// Each measurement can have optional parent.
this.HasOptional(measurement => measurement.Parent)
.WithMany() // .WithMany(measurement => measurement.Children) ??
.Map(m =>
{
m.MapKey("ParentId");
m.ToTable("Measurement_Parent");
});
Unfortunately this gives me weird error while loading my app:
Metadata query failed for: api/EDW/Metadata; The specified table 'Measurement_Parent' was not found in the model. Ensure that the table name has been correctly specified.
I have no idea why is this happening because the table is there. I tried mapping these two tables onto one entity (table splitting), but because the ParentId can be NULL and EF generated INNER JOIN instead of LEFT OUTER JOIN for this mapping, it didn't work because some rows in Measurement table were ommited as they didn't have any corresponding rows in Measurement_Parent.
Basically what I need is to have optional Parent property with reference to parent measurement and list of Children measurements.
What you need is entity splitting - splitting a single entity among two or more tables. This implicitly involves shared primary key - in this case, the shared key in the relationship table will be the child entities' ID. You do this by calling multiple Map methods, each with a call to EntityMappingConfiguration.Properties to define which properties should be included in that mapping fragment and a call to ToTable to set the table name.
modelBuilder.Entity<Measurement>()
.HasKey( ke => ke.MeasurementId )
.Map( emc =>
{
emc.Properties( pe => new { pe.MeasurementId, pe.Name, pe.DeviceId } );
emc.ToTable( "Measurement" );
} )
.Map( emc =>
{
emc.Properties( pe => new { pe.MeasurementId, pe.ParentId } );
// maybe name this MeasurementExtension? This table could
// be used for any properties you wish to add to the Measurement entity
emc.ToTable( "Measurement_Parent" );
// for this example's clarity I've named the PK Child_MeasurementId
// but in real-world use I would name it MeasurementId
emc.Property( pe => pe.MeasurementId ).HasColumnName( "Child_MeasurementId" );
emc.Property( pe => pe.ParentId ).HasColumnName( "Parent_MeasurementId" );
} );
modelBuilder.Entity<Measurement>()
.HasOptional( npe => npe.Parent )
.WithMany( npe => npe.Children )
.HasForeignKey( fke => fke.ParentId );
Here's the result in the DB (note I did not set up a FK/nav prop for Device but you know how to do that):
Ideally, the Parent_MeasurementId field would be not null and the record would be deleted instead of setting that column to null if their is no parent, but that doesn't seem possible with entity splitting. In any case, this does exactly what you're looking for - extending an entity without modifying the initial underlying table.
I am developing a web application with ASP.net MVC and I have a database which I'm connecting with an ADO.NET Entity Framework.
In this database I have a table Group with GroupId as a primary key, another table UserInfo with UserId as its primary key and another table GroupUser which is not considered as an Entity but rather as an Association Set since it is used as a mean to have a many to many relationship between Group and User.
GroupUser contains GroupId and UserId as a composite key and both are foreign keys to the respective tables.
These are the Group and User classes generated (regarding this relationship)
// Group
public Group()
{
this.UserInfo1 = new HashSet<UserInfo>();
}
public virtual UserInfo UserInfo { get; set; }
public virtual ICollection<UserInfo> UserInfo1 { get; set; }
// UserInfo
public UserInfo()
{
this.Group = new HashSet<Group>();
this.Group1 = new HashSet<Group>();
}
public virtual ICollection<Group> Group { get; set; }
public virtual ICollection<Group> Group1 { get; set; }
To add a record to this GroupUser table I am doing this
int ownerId = Convert.ToInt32(WebSecurity.CurrentUserId);
group.UserInfo1.Add(conn.UserInfo.Find(ownerId));
However I am stuck on how to find a record in this table. How can I check if a particular user belongs to this group by having groupId and userId provided here?
Group group = conn.Group.Find(id);
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
Thanks for any help :)
With the starting point you have provided in order to test if the user is in that group you can use:
Group group = conn.Group.Find(id);
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
bool isUserInGroup = group.UserInfo1.Any(u => u.UserId == userId);
It will work because when you access group.UserInfo1 (with the Any extension method in this case) Entity Framework will run a second query (the first one was Find) to load all related UserInfo entities of the given group into the group.UserInfo1 collection. This query is based in lazy loading which is enabled by default if the navigation collection is declared as virtual (which it is in your example). After loading the collection the Any call is a check in memory (no database query here anymore) if the group.UserInfo1 collection contains at least one entity that fulfills the supplied condition, i.e. contains a user with that userId.
However, this is not the best solution because - as said - it will cause two queries (Find and lazy loading of the collection). Actually you can test if the user is in the group by a single database query alone and you don't even need to load any entities for that test, just directly return the bool result from the database:
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
bool isUserInGroup = conn.Group
.Any(g => g.GroupId == id && g.UserInfo1.Any(u => u.UserId == userId));
The result will be false if the group with the id does not exist or if it doesn't have a related user with userId.