Entity to model mapping in LINQ - c#

I have article and author classes.
Fetch articles like so and map entity to model:
public List<Model.Article> GetArticleList() {
using (var db = new ArticlesContext()) {
return db.Articles.Select(t => new Model.Article() {
Author = MapUserEntityToModel(db.Users.FirstOrDefault(u => u.UserID == t.UserID))
Title = t.Title,
Teaser = t.Teaser
// etc
}).ToList();
}
}
This doesn't work because LINQ can't run that function at run-time. What's the simplest and cleanest way to do the mapping?
Here are models:
namespace Model {
public class Article {
public string Title { get; set; }
public string Teaser { get; set; }
public User Author { get; set; }
public DateTime DateAdded { get; set; }
}
public class User {
public string DisplayName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string PasswordHash { get; set; }
}
}
Here are entities:
namespace MyProj {
public class Article {
[Key]
public int ArticleID { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
public int UserID { get; set; }
public DateTime DateAdded { get; set; }
}
public class User {
[Key]
public int UserID { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public string PasswordHash { get; set; }
}
public class ArticleContext : DbContext {
public ArticleContext() : base("name=conn") {
public DbSet<Article> Articles { get; set; }
public DbSet<User> Users { get; set; }
}
}
}

Before continue, map your relationship in a navigation property:
public class Article {
[Key]
public int ArticleID { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
[ForeignKey("UserID")]
public virtual User Author {get; set; } // navigation property
public int UserID { get; set; }
public DateTime DateAdded { get; set; }
}
And then just project your navigation property to his equivalent Model:
public List<Model.Article> GetArticleList() {
using (var db = new ArticlesContext()) {
return db.Articles.Select(t => new Model.Article() {
Author = new Model.User {
DisplayName = t.User.DisplayName,
Email = t.User.Email,
Website = t.User.Website,
PasswordHash = t.User.PasswordHash
},
Title = t.Title,
Teaser = t.Teaser
// etc
}).ToList();
}
}

You don't need to do anything, just return db.Articles directly:
using Model;
public List<Article> GetArticleList() {
using(var db = new ArticlesContext()) {
return db.Articles.ToList();
}
}
Assuming your EF model is set-up correctly with Foreign Keys, your Article type will have a lazily-evaluated Author property which will return a User object when accessed.

Related

Web API C# .net: cannot convert the model in the dto in get method

As I said in the title, I'm trying to convert in the get method a model object to its DTO.
My method is to get users and is the next piece of code:
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDTO>>> GetUsers()
{
var users = _context.Users.ToList();
var userDtos = new List<UserDTO>();
foreach (var user in users)
{
userDtos.Add(new UserDTO
{
IdUser = user.UserProfessionId,
UserName = user.UserName,
UserCompany = user.UserCompany,
UserMail = user.UserMail,
UserProfession = user.UserProfession,
UserProfessionField = user.UserProfessionField
});
}
return userDtos;
}
These are my model and DTO for user:
namespace Sims.Models
{
public partial class User
{
public User()
{
DataUsages = new HashSet<DataUsage>();
}
public long IdUser { get; set; }
public int UserProfessionId { get; set; }
public int UserProfessionFieldId { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public byte[]? UserPicture { get; set; }
public virtual Profession UserProfession { get; set; } = null!;
public virtual ProfessionField UserProfessionField { get; set; } = null!;
public virtual ICollection<DataUsage> DataUsages { get; set; }
}
}
and
namespace sims.DTO
{
public partial class UserDTO
{
public long IdUser { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public virtual ProfessionDTO UserProfession { get; set; } = null!;
public virtual ProfessionFieldDTO UserProfessionField { get; set; } = null!;
}
}
Profession and ProfessionField are also models and have their own DTO. But in the get method, the two following lines contain the same error as it "cannot implicitly convert type '....Models.Profession' to '....DTO.ProfessionDTO'".
Do you have any idea ?
In case, here is an example of the Profession Model and DTO:
namespace Sims.Models
{
public partial class Profession
{
public Profession()
{
ProfessionFields = new HashSet<ProfessionField>();
Users = new HashSet<User>();
}
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
public virtual ICollection<ProfessionField> ProfessionFields { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
and
namespace sims.DTO
{
public class ProfessionDTO
{
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
}
}
Thanks for reading
The UserProfession property is of type ProfessionDTO:
public virtual ProfessionDTO UserProfession { get; set; } = null!;
But you're trying to populate it with an object of type Profession:
UserProfession = user.UserProfession,
Just as the error states, they are different types and can't be substituted for one another. Populate the property with an instance of ProfessionDTO instead:
UserProfession = new UserProfessionDTO
{
IdProfession = user.UserProfession.IdProfession,
ProfessionName = user.UserProfession.ProfessionName
},
If the user.UserProfession field is null then you'd need to check for that. For example:
UserProfession = user.UserProfession == null ?
null as UserProfessionDTO :
new UserProfessionDTO
{
IdProfession = user.UserProfession?.IdProfession,
ProfessionName = user.UserProfession?.ProfessionName
},

Multiplicity constraint violated. The role 'Student_LendedBooks...' of the relationship 'LibarySystem.DataModel' has multiplicity 1 or 0..1

When I try add book to my student with Entity Framework it throw exception "Multiplicity constraint violated. The role 'Student_LendedBooks_Source' of the relationship 'LibarySystem.DataModel.Student_LendedBooks' has multiplicity 1 or 0..1". How can I fix it? I'm not very familiar with Entity Framework. Thank you for any help.
DbContext class:
public class DbContext : System.Data.Entity.DbContext {
public DbSet<Student> Students { get; set; }
public DbSet<Book> Books { get; set; }
}
Student class:
public class Student {
public Student() {
LendedBooks = new HashSet<Book>();
}
[Key]
public string PESEL { get; set; }
public string Name { get; set; }
public string SecondName { get; set; }
public string Surname { get; set; }
public string Class { get; set; }
public virtual ICollection<Book> LendedBooks { get; set; }
}
Book class:
public class Book {
public Book() {
IsLend = false;
}
[Key]
public string CatalogueNumber { get; set; }
public string StudentPesel { get; set; }
public virtual Student Student { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public DateTime? DateOfLend { get; set; }
public DateTime? DateOfReturn { get; set; }
public bool IsLend { get; set; }
}
And Add method:
public static void AddBookToStudent(Student student, Book book) {
using (var context = new DbContext()) {
var findStudent = context.Students.Find(student.PESEL);
var findBook = context.Books.Find(book.CatalogueNumber);
if (findBook != null) {
findBook.DateOfLend = DateTime.Today;
findBook.DateOfReturn = book.DateOfLend + new TimeSpan(7, 0, 0, 0);
findBook.StudentPesel = findStudent?.PESEL;
findBook.Student = findStudent;
findBook.IsLend = true;
}
findStudent?.LendedBooks.Add(book);
context.SaveChanges();
}
}
If you mean to add the Book to just one Student
Then i believe you have a typo there:
findStudent?.LendedBooks.Add(book);
It should become:
findStudent?.LendedBooks.Add(findBook );
You have multiple design issues with this code. But for starters, the 'Lended Book' has to be a separate entity, which connects (FK) to both - Student and Book. It will be an 'association table'.
public class Student {
public Student() {
LendedBooks = new List<LendedBook>();
}
[Key]
public string PESEL { get; set; }
public string Name { get; set; }
public string SecondName { get; set; }
public string Surname { get; set; }
public string Class { get; set; }
public virtual List<LendedBook> LendedBooks { get; set; }
}
public class Book {
public Book() {
}
[Key]
public string CatalogueNumber { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
public class LendedBook
{
public DateTime DateOfLend { get; set; }
public DateTime? DateOfReturn { get; set; }
// TODO: Annotations, ForeignKey
public string StudentPesel { get; set; }
public virtual Student Student { get; set; }
// TODO: Annotations, ForeignKey
public string CatalogueNumber { get; set; }
public virtual Book Book { get; set; }
}
public static void AddBookToStudent(Student student, Book book) {
using (var context = new DbContext()) {
var findStudent = context.Students.Find(student.PESEL);
var findBook = context.Books.Find(book.CatalogueNumber);
if (findBook != null) {
// Something like ...
findStudent?.LendedBooks.Add(new LendedBook() {
DateOfLend = DateTime.Today,
DateOfReturn = book.DateOfLend + new TimeSpan(7, 0, 0, 0),
Book = findBook
});
context.SaveChanges();
}
}
}
You correctly set the findBook.Student property but then you add book, the method parameter, to findStudent.LendedBooks. If that book has another Student set as it's property, you're going to have a bad time.
Move the findStudent?.LendedBooks.Add( findBook ) call inside the findBook != null block and, as written, replace book with findBook

EntityFramework code first keys

I have following tables: User, UserGroups, and UserInGroups. You can see them on picture below. When i call User i want to be able to get Groups that user is in (UserInGroups). I am reading materials about EntityFramework but i am unable to make connections in code to to that, what am i missing? Do i need to connect them onModelCreating?
Currently i am not getting any data from UserInGroup or UserGroups.
DB looks like this
My classes look like this:
public class User : BaseEntity
{
public int RoleId { get; set; }
public Role Role { get; set; }
public UserGroup UserGroup { get; set; }
public UserInGroup UserInGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public UserGroup()
{
Users = new List<User>();
UserInGroups = new List<UserInGroup>();
}
[StringLength(255)]
public string Name { get; set; }
public string KeyName { get; set; }
public List<User> Users { get; set; }
public List<UserInGroup> UserInGroups { get; set; }
}
public class UserInGroup : BaseEntity
{
public UserInGroup()
{
Users = new List<User>();
UserGroups = new List<UserGroup>();
}
public int UserId { get; set; }
public User User { get; set; }
public int UserGroupId { get; set; }
public UserGroup UserGroup { get; set; }
public List<User> Users { get; set; }
public List<UserGroup> UserGroups { get; set; }
}
DbContext:
public DbSet<GlobalSettings> GlobalSettings { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
public DbSet<UserInGroup> UsersInGroups { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<GlobalSettings>().Property(x => x.Key).HasColumnAnnotation("Index", new IndexAnnotation(new[] { new IndexAttribute("Index_VariablenName") { IsClustered = false, IsUnique = true } }));
}
public abstract partial class BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class User : BaseEntity
{
public string Username { get; set; }
public string Title { get; set; }
public FirstName { get; set; }
public string LasName { get; set; }
public Genders Gender { get; set; }
public string Email { get; set; }
public int RoleId { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<UserInGroup> UserInGroups { get; set; }
}
public class UserInGroup : BaseEntity
{
public int UserId { get; set; }
public int UserGroupId { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
public virtual User User { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public string Name { get; set; }
public string KeyName { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
}
public class Role : BaseEntity
{
public string Name { get; set; }
}
public enum Genders
{
Male = 1,
Female = 2
}
You can use two methods to fill navigation properties. First is lazy-loading and second is explicit specifying of required properties.
For lazy-loading you should declare your navigation properties as virtual:
public class User : BaseEntity
{
public int RoleId { get; set; }
public virtual Role Role { get; set; }
public virtual UserGroup UserGroup { get; set; }
public virtual UserInGroup UserInGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public UserGroup()
{
Users = new HashSet<User>(); // HashSet is more effective than List
UserInGroups = new HashSet<UserInGroup>();
}
[StringLength(255)]
public string Name { get; set; }
public string KeyName { get; set; }
public virtual ICollection<User> Users { get; set; } // ICollection is less restrective
public virtual ICollection<UserInGroup> UserInGroups { get; set; }
}
Now, you can load f.e. single user:
var justUser = dbContext.Users.Single(u => u.Id == 100);
When you need its properties they will by transparently loaded:
foreach (var userInGroup in user.UsersInGroups) // here is second loading
{
. . .
}
The second way is the calling of the Include method to explicit specifying required properties:
var userWithGroups = dbContext.Users
.Include(u => u.UserInGroups) // include single navigation property
.Include(ugs => ugs.Groups.Select(ug => ug.Group)) // include collection navigation property
.Single(u => u.Id == 100); // get the user with specified id and filled specified properties

value cannot be null in Entity Sql Statement

My News.cs class has a one to many relationship with Comment.cs as defined below
public class News
{
public int NewsId { get; set; }
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
public ICollection<Comment> Comment { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public int AppUserId { get; set; }
public int? NewsId { get; set; }
[ForeignKey("AppUserId")]
public virtual AppUser AppUser { get; set; }
[ForeignKey("NewsId")]
public virtual News News { get; set; }
}
I have a controller action where i am trying to fetch one News item alongside all its comments so i set up two viewModels like this
public class CommentVM
{
public string CommentText { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
}
public class NewsCommentsVM
{
[Display(Name = "Title")]
public string Title { get; set; }
[Display(Name = "Details")]
public string Details { get; set; }
public DateTime DateCreated { get; set; }
public string Author { get; set; }
public List<CommentVM> Comments { get; set; }
}
In my Controller action i have
public ActionResult Details(int? id)
{
UOW _unit = new UOW();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
News news = _unit.NewsRepository.GetByID(id);
if (news == null)
{
return HttpNotFound();
}
var model = new NewsCommentsVM()
{
Title = news.Title,
Details = news.Details,
DateCreated = news.DateCreated,
Author = news.AppUser.FirstName
Comments = news.Comment.Select(c => new CommentVM()
{
CommentText = c.CommentText,
Author = c.AppUser.Email,
DateCreated = c.DateCreated
}).ToList()
};
return View(result);
}
The problem is that the debugger is showing that Comment is returning Null whereas in the database there are related comments to that particular news item so i'm getting the error
Value cannot be null. Parameter: source
I've been able to use this code in another project without issues.
I think the problem is because you need to change the Comments collection property as virtual. If you want that related entities be lazy loaded, you need to follow this requirements:
public class News
{
//...
public virtual ICollection<Comment> Comment { get; set; }
}
Now,If you have disabled lazy loading, another option could be using the Include extension method in your query when you need to find a particular news:
int id=3;
var specificNews=context.News.Include(n=>n.Comment).FirstOrDefault(n=>n.Id==id);
This way the related entity will be included in the query result

Error with Entity Framework 4 and MVC 3

I have a database with 3 tables:
Subjects
Members
Topics
Then I added the connection string to web.config and created an EF with the following classes:
namespace MySite.Models
{
public class MySiteDBModel : DbContext
{
public DbSet<Topic> Topics { get; set; }
public DbSet<Subject> Subjects { get; set; }
public DbSet<Member> Members { get; set; }
public DbSet<TopicDataModel> TopicDataModel { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Conventions.Remove<PluralizingTableNameConvention>();
}
}
public class Topic
{
[Key]
public int TopicID { get; set; }
public int SubID { get; set; }
public int MemberID { get; set; }
public string TDate { get; set; }
public string Title { get; set; }
public string FileName { get; set; }
public int Displays { get; set; }
public string Description { get; set; }
public virtual Subject Subject { get; set; }
public virtual Member Member { get; set; }
public virtual ICollection<TopicView> TopicView { get; set; }
}
public class Subject
{
[Key]
public int SubID { get; set; }
public string SubName { get; set; }
public virtual ICollection<Topic> Topic { get; set; }
}
public class Member
{
[Key]
public int MemberID { get; set; }
public string FLName { get; set; }
public string Email { get; set; }
public string Pwd { get; set; }
public string About { get; set; }
public string Photo { get; set; }
public virtual ICollection<Topic> Topic { get; set; }
}
public class TopicDataModel
{
[Key]
public int TopicID { get; set; }
public string SubName { get; set; }
public string FLName { get; set; }
public string TDate { get; set; }
public string Title { get; set; }
public int Displays { get; set; }
public string Description { get; set; }
}
}
Now when I am trying to query the database with the this code:
public ActionResult Index()
{
var topics = from t in db.Topics
join s in db.Subjects on t.SubID equals s.SubID
join m in db.Members on t.MemberID equals m.MemberID
select new TopicDataModel()
{
TopicID = t.TopicID,
SubName = s.SubName,
FLName = m.FLName,
TDate = t.TDate,
Title = t.Title,
Displays = t.Displays,
Description = t.Description
};
return View(topics.ToList());
}
I got this Error:
The model backing the 'MySiteDBModel' context has changed since the
database was created. Either manually delete/update the database, or
call Database.SetInitializer with an IDatabaseInitializer instance.
For example, the DropCreateDatabaseIfModelChanges strategy will
automatically delete and recreate the database, and optionally seed it
with new data.
Please help me!!!!!!
You need to set some controls on how EF is handling changes to your data model. Julie Lerman has a good blog post on Turning Off Code First Database Initialization Completely.
Also, here is a good overview - Inside Code First Database Initialization

Categories