I have an entity like this:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string FullText { get; set; }
public string Tags { get; set; }
public virtual Category Category { get; set; }
}
This entity has relations to Category:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<Post> Articles { get; set; }
}
Now, when inserting data into the Post table, I use this code:
var post = new Post
{
Category = _categoryService.FindById(model.CategoryId),
FullText = model.FullText,
Tags = model.Tags,
Title = model.Title,
};
_articsleService.Save(post);
This code works fine, but it has 1 fetching data form database. In entity objects that has more than 3 relations, I think this way not so good. Therefore, another way can be like this:
var post = new Post
{
Category = new Category { Id = model.CategoryId },
FullText = model.FullText,
Tags = model.Tags,
Title = model.Title,
};
_articsleService.Save(post);
But, when the code is run, I get this exception:
Cannot insert explicit value for identity column in table 'Categories' when IDENTITY_INSERT is set to OFF.
Is there any best solution for this problem?
Add to your post model the FK to CategoryId
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string FullText { get; set; }
public string Tags { get; set; }
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
}
Then just add a new post after updating your db model.
var post = new Post
{
CategoryId = model.CategoryId,
FullText = model.FullText,
Tags = model.Tags,
Title = model.Title,
};
_articsleService.Save(post);
That's it, EF will do the rest for you.
Your current approach leads to creating a new Category which does not work bc the same primary key already exists and inserting into an identity column is not enabled by default. This is how change-tracking from EF works. EF creates a proxy from each database row as an entity in your code and tracks this entity during application lifetime.
You can read more about it here.
Related
I have the following action method to add a parent (Submission) and child (SubmissionQuestionSubmission) objects:
public async Task<IActionResult> Create([Bind("Submission,SubmissionQuestionSubmission")] SubmissionCreate sc )
{
if (ModelState.IsValid)
{
var newsubmission = _context.Submission.Add(sc.Submission);
sc.Submission.Created = DateTime.Now;
await _context.SaveChangesAsync();
foreach (var v in sc.SubmissionQuestionSubmission)
{
v.SubmissionId = sc.Submission.Id;//i though this will be null..
_context.SubmissionQuestionSubmission.Add(v);
}
await _context.SaveChangesAsync();
}
}
At first I thought that the code will not work as the sc.Submission.Id parent foreign key will be null, since I have just added the submission object. but my code work well, so how did the ASP.NET Core MVC and Entity Framework Core retrieved the Id of the newly added object? Will it automatically do a round trip to the database?
Here are my model classes:
public partial class Submission
{
public Submission()
{
SubmissionQuestionSubmission = new HashSet<SubmissionQuestionSubmission>();
}
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? Created { get; set; }
public virtual ICollection<SubmissionQuestionSubmission> SubmissionQuestionSubmission { get; set; }
}
public partial class SubmissionQuestionSubmission
{
public int SubmissionQuestionId { get; set; }
public long SubmissionId { get; set; }
public bool? Answer { get; set; }
public virtual Submission Submission { get; set; }
public virtual SubmissionQuestion SubmissionQuestion { get; set; }
}
When SaveChanges() is called it will automatically update the Id field for the new saved entity. As an example, EF might generate queries as follow when saving a new entity:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN
INSERT INTO `Author` (`Name`)
VALUES ('test');
SELECT `Id`
FROM `Author`
WHERE ROW_COUNT() = 1
AND `Id`=LAST_INSERT_ID()
COMMIT
As you see it will run the INSERT INTO query followed by a SELECT query to get the Id of the new inserted row, which will be saved in the Id property.
I am using Entity Framework Core 2.0.1 and I have the following models
public class Article
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Slug { get; set; }
public int Approved { get; set; }
public DateTime ArticleDate { get; set; }
// ... some other fields
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategory
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
//... soem other fields
[ForeignKey("ArticleCategoryParent")]
public int? ArticleCategoryParentID { get; set; }
public virtual ArticleCategory ArticleCategoryParent { get; set; }
public virtual ICollection<ArticleCategory> SubCategories { get; set; }
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategoryRelation
{
[Column(Order = 0)]
public int ArticleId { get; set; }
public Article Article { get; set; }
[Column(Order = 1)]
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory {get; set;}
}
Every article belongs to one or more categories. Categories might have parent category.
I want to get from database last two articles (where Approved = 1) with related category details, for each category that belongs to a parent category which id is given as input.
I have tried but with no success. I can't filter results of an .Include() entity. Is it possible... or I don't know how to do it?
All my data are accessed through entity framework with appContext (the context used to get entities from database). Can I achieve what I want through entity framework core (lambda expression is preferred over Linq if possible), or should I use ADO.NET library (which I know how to execute custom queries).
P.S. I want to get data only to show in the view... no edit is needed.
You don't actually need to include here at all, as far as I can tell. Whenever you use data from a nav property, EF will go get the data from that table, as best it can filter it.
var CategoriesUnderParent = AppContext.ArticleCategories
.Where(c => c.ArticleCategoryParent == {parent});
foreach(var category in CategoriesUnderParent)
{
var ArticlesAllowed = category.ArticleCategoryRelations
.Where(acr => acr.Article.Approved == 1).Select(a => a.Article);
var ArticlesPicked = ArticlesAllowed
.OrderByDescending(ar => ar.ArticleDate)
.Take(2);
// Do something with your data
}
I am trying to retrieve a subset of properties in in Entity Framework Core (1.1.1) for an optional one-to-one relationship, but my query attempts result in all columns being retrieved in the query for both tables (verified with SQL Profiler).
For an example, lets say I have these models bound up to my DbContext:
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public string Birthday { get; set; }
public string FavoriteIceCream { get; set; }
public string OtherRandomInfoForExample { get; set; }
public virtual StudentAddress Address { get; set; }
}
public class StudentAddress
{
public int StudentId { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public int Zipcode { get; set; }
public string State { get; set; }
public string Country { get; set; }
public virtual Student Student { get; set; }
}
When I try projecting this into a new model choosing only the columns I want like the following:
var studentsCityInfos = _dbContext.Student.AsNoTracking().Select(s => new StudentCityInfo
{
Id = s.StudentId,
Name = s.StudentName,
City = s.Address.City
}).ToList();
The result is a query that returns all columns both tables. I have also tried the following with a null check on the navigation property and that leads to the same results:
var studentsCityInfos = _dbContext.Student.AsNoTracking().Select(s => new StudentCityInfo
{
Id = s.StudentId,
Name = s.StudentName,
City = s.Address == null ? null : s.Address.City
}).ToList();
Is this maybe just a projection bug in EF Core 1.1.1 that should be optimized?
Any alternative approaches anyone else can think of?
As a fall back I can break this up into two queries or use FromSql to write my own, but I would like to know the correct approach to this scenario.
For completeness I have no control over the schema and both tables have many more columns and roughly a million rows.
Thank you for taking the time to read and provide answers!
EDIT: as requested, here is an example of the sql profiler results (manually made to reflect the results from my scenario):
SELECT [s].[StudentId], [s].[StudentName], [s].[Birthday], [s].[FavoriteIceCream], [s].[OtherRandomInfoForExample], [s].[BusinessFullName], [s].[BusinessLastName], [s.Address].[StudentId], [s.Address].[Address1], [s.Address].[Address2], [s.Address].[City], [s.Address].[Zipcode], [s.Address].[State], [s.Address].[Country]
FROM [Student] AS [s]
LEFT JOIN [StudentAddress] AS [s.Address] ON [s].[StudentId] = [s.Address].[StudentId]
ORDER BY [s.Address].[StudentId]
In my IdentityModels class I have a property called Reports like below
public DbSet<Report> Reports { get; set; }
In my database there is a table with the same name which this property pulls the data from.
I also have a model called Report that (in addition to some other properties) has these
public class Report
{
//...
[Required]
public string State { get; set; }
[Column("reporter_id")]
public string ReporterId { get; set; }
[Required]
[Column("report_text")]
public string ReportText { get; set; }
//...
}
I also have a view that is a strong view with Report as its model.
Where I ran into a problem is that my model has reporterId which is a foreign key to different table called AspNetUsers that has the user details.
I want to display the user name not the id and because the Reports table only has the userId there is no way for me to display the user name.
What would be a good way of making a user_name field in the AspNetUsers table be part of my model? There obviously has to be a join statement somewhere between the reports table and aspNetUsers table but I'm not sure how best to do this.
My answer may not be helpful to you but I'm will still post it, ViewModel are specifically used for this kind of situation where you need to have two or more tables data displayed in a view.
First , you will need to create a ViewModel lets call it UserReportViewModel. In this view model, you will include an additional property UserName that will pull the username based on ReporterId.
public class UserReportViewModel
{
[Required]
public string State { get; set; }
[Column("reporter_id")]
public string ReporterId { get; set; }
[Required]
[Column("report_text")]
public string ReportText { get; set; }
public string UserName{ get; set; }
}
Then , assuming you are using LINQ to retrieve the report:
Dim ReportList As List(Of UserReportViewModel) = New List(Of UserReportViewModel)
ReportList = dbContext.Reports.Select(Function(x) New UserReportViewModel With {
.State = x.State, _
.ReporterId= x.ReporterId, _
.UserName= dbContext.Users.Find(x.ReporterId).UserName, _
.ReportText = x.ReportText, _
}).ToList()
You have to change your models as shown below.
public class Report
{
[Required]
public string State { get; set; }
[ForeignKey("UserId")]
public virtual AspNetUser { get; set; }
public int UserId { get; set; }
[Required]
[Column("report_text")]
public string ReportText { get; set; }
}
public class AspNetUser
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Report> Reports { get; set;}
}
Your Query should be like this :
var query =
(from u in db.AspNetUsers
from r in u.Reports
select new { UserName = u.Name, ReportText = r.ReportText }).ToList();
In order to learn ASP.NET I decided to write my own blog in it. I'm using Entity Framework 6 and MySQL 5.6.21.
I have a BlogPost entity
public class BlogPost
{
public BlogPost()
{
Comments = new HashSet<Comment>();
BlogPostTags = new HashSet<BlogPostTag>();
}
public int BlogPostId { get; set; }
[Required]
[StringLength(150)]
public string Title { get; set; }
[Required]
[StringLength(16777215)]
public string Content { get; set; }
public DateTime PublishTime { get; set; }
public DateTime UpdateTime { get; set; }
[StringLength(500)]
public string Summary { get; set; }
public string Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual ICollection<BlogPostTag> BlogPostTags { get; set; }
}
that has many-to-many relationship with BlogPostTag entity, which looks like this
public class BlogPostTag
{
public BlogPostTag()
{
BlogPosts = new HashSet<BlogPost>();
}
[Key]
[StringLength(50)]
public string TagName { get; set; }
public ICollection<BlogPost> BlogPosts { get; set; }
public override int GetHashCode()
{
return TagName.GetHashCode();
}
}
When I try to edit a post and I decide to add some tags to a BlogPost entity, EF6 throws an exception (which is just a MySQL exception propagated upwards): "Duplicate entry 'tag1' for key 'PRIMARY'". This happens only if 'tag1' already exists in database (= some blog post has/had this tag).
This is how I'm updating BlogPost entity:
public void EditBlogPost(BlogPost blogPost, string tags)
{
if (!string.IsNullOrEmpty(tags))
{
var splitTags = tags.Split(';');
foreach (var tag in splitTags)
{
blogPost.BlogPostTags.Add(new BlogPostTag() {TagName = tag});
}
}
blogPost.UpdateTime = DateTime.Now;
int bound = blogPost.Content.Length < 300 ? blogPost.Content.Length : 300;
blogPost.Summary = blogPost.Content.Substring(0, bound) + "...";
BlogPosts.Add(blogPost);
SaveChanges();
}
This method is called from ASP.NET MVC controller. tags parameter is received from a POST and is a semicolon delimited string (e.g. tag1;tag2;tag3). BlogPosts is declared as public DbSet<BlogPost> BlogPosts { get; set; }
Is there a way to tell EF to first check if a tags already exists in a database and if so, use it instead of trying to insert a new one?
It looks like you have to progmatically check if there is something in blogposttag that exists already.
Something like
Blogposttag.firstordefault (x => x.tagname == name);
Then if that's null, then add that to the blog post tag. And if it isn't null, then you can skip the blogposttag.add() and just use that object in the blogpost entity.