I'm trying to create some In-Memory dbContext mocks using Moq and using EntityFramework.Testing.Moq extension methods:
https://github.com/scott-xu/EntityFramework.Testing
I'm hitting a brick wall when I'm trying to unit test my eagerly loaded queries using mocked context. The issue is, nothing is selected at all. I know something should be selected, because when I point the same query using LINQPad to my source database, I get the 2 results back that I expect.
I never know how much code etc I need to post, so hopefully the below will help.
Data Diagram
I have two main tables, dbo.Applicant that holds basically user details and dbo.Application, which holds job applications, there are lookup tables that dbo.Application hooks into:
Example Models
This is a code first approach based off the above diagram. I have removed properties to keep the code as clean and to the point for the question:
[Table("Application")]
public partial class Application
{
[Min(1)]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string ApplicationId { get; set; }
[Min(1)]
public int ApplicantId { get; set; }
public virtual Applicant Applicant { get; set; }
}
[Table("Applicant")]
public partial class Applicant
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Applicant()
{
Applications = new HashSet<Application>();
RowStatus = "L";
}
public int Id { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string AddressLine1 { get; set; }
[Required]
public string Email { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Application> Applications { get; set; }
}
Context
Again a cut down context, but just in case I'm ask for it in the comments:
public partial class WorkExperienceContext : DbContext
{
public WorkExperienceContext()
: base("name=WorkExperienceContext")
{
this.Configuration.LazyLoadingEnabled = false;
}
public WorkExperienceContext(string userTestConnectionString)
: base(userTestConnectionString)
{
Trace.Write("Using test context " + Add to dictionary);
}
public virtual DbSet<Applicant> Applicants { get; set; }
public virtual DbSet<Application> Applications { get; set; }
}
Unit Test method
The following is (again) a cut down version of the Unit test method where I expect two Application records to be pulled through based off the email.
As you can see from the below, there are:
2 Application records for Lucifer (ApplicantId = 666)
There is one Applicant record with an email of misunderstood#fireandbrimestone.co.uk
My issue is test is null, when querying the database itself with the same code, I get the correct results. I'm perplexed what I need to do. Remember I'm using the EntityFramework.Testing.Moq.SetupData() method which handles the mocking of the DbSet methods and transforming the List<> seed data to an IQueryable collection.
public void InMemory_Find_Application_By_Email_EFMoq()
{
var applications = new List<Application>
{
new Application { Id = 1, ApplicantId = 666, ApplicationId = "1-a", DivisionId = 2, PublishingAreaId = 4, SourceId = 2, SkillsLearnt = "How to pick up pen", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 2, ApplicantId = 666, ApplicationId = "1-a", DivisionId = 3, PublishingAreaId = 4, SourceId = 2, SkillsLearnt = "How to pick up pen", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 3, ApplicantId = 5, ApplicationId = "1-b", DivisionId = 3, PublishingAreaId = 1, SourceId = 2, SkillsLearnt = "Reading a book well" , RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 4, ApplicantId = 5, ApplicationId = "1-b", DivisionId = 2, PublishingAreaId = 1, SourceId = 2, SkillsLearnt = "Reading a book well ", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2 ,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 5, ApplicantId = 5, ApplicationId = "1-b", DivisionId = 7, PublishingAreaId = 1, SourceId = 2, SkillsLearnt = "Reading a book well" , RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2 ,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay } ,
new Application { Id = 6, ApplicantId = 24, ApplicationId = "1-c", DivisionId = 10, PublishingAreaId = 3, SourceId = 1, SkillsLearnt = "I can now re-iterate stuff", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay },
new Application { Id = 7, ApplicantId = 21, ApplicationId = "1-d", DivisionId = 2, PublishingAreaId = 2, SourceId = 1, SkillsLearnt = "I made some bread the other day", RowStatus="L", ApplicantStartDateId = 1,
ApplicantDisabilityId = 2 ,CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay }
};
var applicants = new List<Applicant>
{
new Applicant {Id = 5, FullName = "Pen Is", AddressLine1="Somewhere Over the Rainbow", County="West Sussex", Email="pen_is#hotmail.co.uk", RowStatus = "L",
GenderId = 2, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="PEN 1ST", Telephone="N/A"},
new Applicant {Id = 24, FullName = "Gareth Bradley", AddressLine1="an address", County="West Sussex", Email="gareth.bradley#hachette.co.uk", RowStatus = "L",
GenderId = 2, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="bn13 3qb",Telephone="N/A"} ,
new Applicant {Id = 21, FullName = "Lizzy Windsor", AddressLine1="A Palace (Take your pick)", County="Berkshire", Email="HerRoyalMaj#TheCrownJewels.co.uk", RowStatus = "L",
GenderId = 2, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="BE1 1HM",Telephone="N/A"} ,
new Applicant {Id = 666, FullName = "Lucifer MorningStar", AddressLine1="Hotsy Street", County="Down South", Email="misunderstood#fireandbrimes1one.co.uk" , RowStatus = "L",
GenderId = 3, EducationId = 1, CreatedDate = DateTime.Now, CreatedTime = DateTime.Now.TimeOfDay, PostCode="HE11 6SS",Telephone="666-666"}
};
foreach (Applicant applicant in applicants)
{
applicant.Applications = applications.Where(a => a.ApplicantId == applicant.Id).ToArray();
}
foreach (Application application in applications)
{
application.Applicant = applicants.SingleOrDefault(a => a.Id == application.ApplicantId);
}
var mockSet = new Mock<DbSet<Application>>()
.SetupData(applications);
var mockSetUsers = new Mock<DbSet<Applicant>>()
.SetupData(applicants);
var mockContext = new Mock<WorkExperienceContext>();
mockContext.Setup(c => c.Applications).Returns(mockSet.Object);
mockContext.Setup(c => c.Applicants).Returns(mockSetUsers.Object);
var mockService = new WorkExperienceFormService(mockContext.Object);
var test = mockContext.Object
.Applications
.Include(a => a.Applicant)
.Where(e => e.Applicant.Email == "misunderstood#fireandbrimestone.co.uk")
.ToList();
Assert.AreEqual(2, test.Count());
}
The following is proof of my LINQPad is pulling the data without a mocked context:
Maybe it's a typo when you posted the code, but the e-mail for 'Lucifer MorningStar' is
misunderstood#fireandbrimes1one.co.uk instead of
misunderstood#fireandbrimestone.co.uk
Related
I have a list of Logs for the history of each Task. I need a query to retrieve the last Status for every Task, based on the log date.
class Log
{
public int Id { get; set; }
public int TaskId { get; set; }
public string LogDate { get; set; }
public string Status { get; set; }
static void Main()
{
Log[] logs = {
new Log() { Id = 1, TaskId = 1, LogDate = "2022-12-21", Status = "ToDo" },
new Log() { Id = 2, TaskId = 2, LogDate = "2022-12-22", Status = "ToDo" },
new Log() { Id = 3, TaskId = 1, LogDate = "2022-12-23", Status = "InProgress" },
new Log() { Id = 4, TaskId = 1, LogDate = "2022-12-24", Status = "Done" },
new Log() { Id = 5, TaskId = 3, LogDate = "2022-12-25", Status = "ToDo" },
new Log() { Id = 6, TaskId = 2, LogDate = "2022-12-26", Status = "InProgress" }
};
var lastLogs = logs.GroupBy(/* ... need a query to get below results */);
/*
Id = 4, TaskId = 1, LogDate = 2022-12-24, Status = Done
Id = 5, TaskId = 3, LogDate = 2022-12-25, Status = ToDo
Id = 6, TaskId = 2, LogDate = 2022-12-26, Status = InProgress
*/
foreach (var item in lastLogs)
{
Console.WriteLine($"Id = {item.Id}, TaskId = {item.TaskId}, LogDate = {item.LogDate}, Status = {item.Status}");
}
}
}
I'm new to Entity Framework Core, and have had trouble seeding a mssql database. I have a Subject model and a Assignment model with a one to many relationship (Subject has a ICollection).
I tried adding:
storeContext.Entry<Subject>(subject1).State = EntityState.Detached;
for all the objects created for the database, but it still gave the same error.
I get this error when trying to run the asp.net core application:
Exception thrown: 'System.InvalidOperationException' in Microsoft.EntityFrameworkCore.dll
An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code
The instance of entity type 'Assignment' cannot be tracked because another instance with the same key value for {'AssignmentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
The DatabaseInitializer class:
using FoxAcademicManager.Backend.Models;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
namespace FoxAcademicManager.Backend.Data
{
public class DatabaseInitializer
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AcademicManagerContext>();
//do your stuff....
context.Database.EnsureCreated();
if (context.Subjects.Any()) return;
if (context.Events.Any()) return;
var subject1 = new Subject
{
SubjectId = 1,
Title = "Physics",
Description = "The AB Physics course",
GradeEarned = null,
GradeOverall = null,
};
var subject2 = new Subject
{
SubjectId = 2,
Title = "Chemistry",
Description = "A AB Chem course",
GradeEarned = null,
GradeOverall = null,
};
var assignment1 = new Assignment
{
Date = new DateTime(2020, 3, 1, 9, 0, 0),
Name = "Assignment 1",
Description = "Forces and Newton's Laws of Motion",
Score = 80,
Weight = 0.2,
AssignmentId = 1,
SubjectId = 1
};
var assignment2 = new Assignment
{
Date = new DateTime(2020, 3, 1, 9, 0, 0),
Name = "Assignment 2",
Description = "Gravity",
Score = 90,
Weight = 0.2,
AssignmentId = 2,
SubjectId = 1
};
var assignment3 = new Assignment
{
Date = new DateTime(2020, 3, 1, 9, 0, 0),
Name = "Assignment 3",
Description = "Frequency",
Score = 70,
Weight = 0.2,
AssignmentId = 3,
SubjectId = 1
};
var exam1 = new Assignment
{
Date = new DateTime(2020, 5, 1, 10, 0, 0),
Name = "Exam 1",
Description = "The first exam",
Score = 85,
Weight = 0.6,
AssignmentId = 1,
SubjectId = 2
};
var exam2 = new Assignment
{
Date = new DateTime(2020, 6, 1, 11, 0, 0),
Name = "Exam 2",
Description = "The second exam",
Score = 75,
Weight = 0.6,
AssignmentId = 2,
SubjectId = 2
};
var assignmentz1 = new Assignment
{
Date = new DateTime(2020, 5, 11, 12, 0, 0),
Name = "Assignment 1",
Description = "The first Chem grade",
Score = 85,
Weight = 0.2,
AssignmentId = 3,
SubjectId = 2
};
context.Subjects.AddRange(new Subject[] { subject1, subject2 });
subject1.Assignments = new List<Assignment> { assignment1, assignment2, assignment3 };
subject2.Assignments = new List<Assignment> { exam1, exam2, assignmentz1 };
context.UpdateRange(subject1, subject2);
context.SaveChanges();
}
}
}
}
This is the Startup.cs file:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;
using FoxAcademicManager.Backend.Data;
using Microsoft.EntityFrameworkCore;
namespace FoxAcademicManager.Backend
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<AcademicManagerContext>
(options => options.UseSqlServer(connection));
services.AddSwaggerGen(options =>
options.SwaggerDoc("v1", new Info { Title = "Academic Manager", Version = "v1"})
);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwagger();
if(env.IsDevelopment() || env.IsStaging())
{
app.UseSwaggerUI(options =>
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Academic Manager v1")
);
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
DatabaseInitializer.Initialize(app.ApplicationServices);
}
}
}
For initializing database with EF Context, you could not set the key for entity directly, it is generated automatically.
Try to make a test with code below:
Model Subject
public class Subject
{
public int SubjectId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string GradeEarned { get; set; }
public string GradeOverall { get; set; }
public List<Assignment> Assignments { get; set; }
}
Model Assignment
public class Assignment
{
public DateTime Date { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Score { get; set; }
public double Weight { get; set; }
public int AssignmentId { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
}
Seed database
public static void Initialize(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
//do your stuff....
context.Database.EnsureCreated();
var subject1 = new Subject
{
Title = "Physics",
Description = "The AB Physics course",
GradeEarned = null,
GradeOverall = null,
};
var subject2 = new Subject
{
Title = "Chemistry",
Description = "A AB Chem course",
GradeEarned = null,
GradeOverall = null,
};
var assignment1 = new Assignment
{
Date = new DateTime(2020, 3, 1, 9, 0, 0),
Name = "Assignment 1",
Description = "Forces and Newton's Laws of Motion",
Score = 80,
Weight = 0.2,
Subject = subject1
};
var assignment2 = new Assignment
{
Date = new DateTime(2020, 3, 1, 9, 0, 0),
Name = "Assignment 2",
Description = "Gravity",
Score = 90,
Weight = 0.2,
Subject = subject1
};
var assignment3 = new Assignment
{
Date = new DateTime(2020, 3, 1, 9, 0, 0),
Name = "Assignment 3",
Description = "Frequency",
Score = 70,
Weight = 0.2,
Subject = subject1
};
var exam1 = new Assignment
{
Date = new DateTime(2020, 5, 1, 10, 0, 0),
Name = "Exam 1",
Description = "The first exam",
Score = 85,
Weight = 0.6,
Subject = subject2
};
var exam2 = new Assignment
{
Date = new DateTime(2020, 6, 1, 11, 0, 0),
Name = "Exam 2",
Description = "The second exam",
Score = 75,
Weight = 0.6,
Subject = subject2
};
var assignmentz1 = new Assignment
{
Date = new DateTime(2020, 5, 11, 12, 0, 0),
Name = "Assignment 1",
Description = "The first Chem grade",
Score = 85,
Weight = 0.2,
Subject = subject2
};
context.Subjects.AddRange(new Subject[] { subject1, subject2 });
context.Assignments.AddRange(new Assignment[] { assignment1, assignment2, assignment3, exam1, exam2 });
context.SaveChanges();
}
}
If you insist on inserting the value for id, you could try modelBuilder.Entity<Post>().HasData and you could refer Data Seeding
My example contains 2 tables Book and Comment.
Book:
Id Name UserId DateTime
B1 Book1 User1 16/11/2016 11:15:00
B2 Book2 User1 16/11/2016 12:15:00
B3 Book3 User2 16/11/2016 10:15:00
Comment:
Id BookId UserId DateTime
C1 B3 User1 16/11/2016 11:17:00
C2 B1 User1 16/11/2016 11:16:00
List of Book via specific user id:
string userid = "User1";
IEnumerable<Book> books = _context.Books.Where(x => x.UserId == userid);
List of Comment:
IEnumerable<Book> comments = _context.Comments.Where(x => x.UserId == userid);
In activity history (web page), I want to show all books and comments what I have collected via userid, one by one. But, how can I sort 2 objects again via .OrderByDescending(x => x.DateTime)?
My goal:
User1 just postes a new book to the store:
B2 Book2 User1 16/11/2016 12:15:00
Before that, he posted a comment to his own thread:
C1 B1 User1 16/11/2016 11:17:00
Earlier, he posted a comment in another thread (UserId == "User2"):
C2 B3 User1 16/11/2016 11:16:00
Older, he posted a new book to the store:
B1 Book1 User1 16/11/2016 11:15:00
I can classify them via DateTime column (on paper):
16/11/2016 12:15:00
16/11/2016 11:17:00
16/11/2016 11:16:00
16/11/2016 11:15:00
How can I sort it?
UPDATE:
I just found a solution but LinQ. I want a solution using LinQ with same result (for shorter):
namespace MP
{
public class Book
{
public string Id { get; set; }
public string Name { get; set; }
public string UserId { get; set; }
public DateTime DateTime { get; set; }
}
public class Comment
{
public string Id { get; set; }
public string BookId { get; set; }
public string UserId { get; set; }
public string Content { get; set; }
public DateTime DateTime { get; set; }
}
public class Result
{
public object Object { get; set; }
public DateTime DateTime { get; set; }
}
class Program
{
static void Main()
{
try
{
string userid = "User1";
var books = new List<Book>();
books.Add(new Book { Id = "B1", Name = "Book1", UserId = "User1", DateTime = new DateTime(2016, 11, 16, 11, 15, 00) });
books.Add(new Book { Id = "B2", Name = "Book2", UserId = "User1", DateTime = new DateTime(2016, 11, 16, 12, 15, 00) });
books.Add(new Book { Id = "B3", Name = "Book3", UserId = "User2", DateTime = new DateTime(2016, 11, 16, 10, 15, 00) });
var comments = new List<Comment>();
comments.Add(new Comment { Id = "c1", BookId = "B3", UserId = "User1", Content = "cmt1", DateTime = new DateTime(2016, 11, 16, 11, 17, 00) });
comments.Add(new Comment { Id = "c2", BookId = "B1", UserId = "User1", Content = "cmt2", DateTime = new DateTime(2016, 11, 16, 11, 16, 00) });
var result = new List<Result>();
books.ForEach(x =>
{
if (x.UserId == userid)
{
result.Add(new Result { Object = x, DateTime = x.DateTime });
}
});
comments.ForEach(x =>
{
if (x.UserId == userid)
{
result.Add(new Result { Object = x, DateTime = x.DateTime });
}
});
result = result.OrderByDescending(x => x.DateTime).ToList();
foreach (var item in result)
{
Type type = item.Object.GetType();
if (type == typeof(MP.Book))
{
var book = (Book)item.Object;
Console.WriteLine($"Book: Id: {book.Id} - Name: {book.Name} - DateTime: {book.DateTime}");
}
if (type == typeof(MP.Comment))
{
var cmt = (Comment)item.Object;
Console.WriteLine($"Comment: Id: {cmt.Id} - Content: {cmt.Content} - DateTime: {cmt.DateTime}");
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
Result:
Here's solution that should work for you. Basically I took your idea of objectifying the data and creating a linq query that creates a list of objects from each list and sorts them according to the DateTime. By overriding the ToString() method printing the contents of the list becomes very simple:
public class Book
{
public const string className = "Book";
public string Id { get; set; }
public string Name { get; set; }
public string UserId { get; set; }
public DateTime DateTime { get; set; }
public override string ToString()
{
return $"Book: Id: {Id.PadRight(7)} - Name: {Name.PadRight(14)} - DateTime: {DateTime}";
}
}
public class Comment
{
public string Id { get; set; }
public string BookId { get; set; }
public string UserId { get; set; }
public string Content { get; set; }
public DateTime DateTime { get; set; }
public override string ToString()
{
return $"Comment: Id: {Id.PadRight(7)} - Content: {Content.PadRight(11)} - DateTime: {DateTime}";
}
}
class Program
{
static void Main()
{
try
{
string userid = "User1";
var books = new List<Book>();
books.Add(new Book { Id = "B1", Name = "Book1", UserId = "User1", DateTime = new DateTime(2016, 11, 16, 11, 15, 00) });
books.Add(new Book { Id = "B2", Name = "Book2", UserId = "User1", DateTime = new DateTime(2016, 11, 16, 12, 15, 00) });
books.Add(new Book { Id = "B3", Name = "Book3", UserId = "User2", DateTime = new DateTime(2016, 11, 16, 10, 15, 00) });
var comments = new List<Comment>();
comments.Add(new Comment { Id = "c1", BookId = "B3", UserId = "User1", Content = "cmt1", DateTime = new DateTime(2016, 11, 16, 11, 17, 00) });
comments.Add(new Comment { Id = "c2", BookId = "B1", UserId = "User1", Content = "cmt2", DateTime = new DateTime(2016, 11, 16, 11, 16, 00) });
var test = (from b in books
where b.UserId == userid
select (object)b).Concat
(from c in comments
where c.UserId == userid
select (object)c).OrderBy(x => x.GetType() == typeof(Book)?((Book)x).DateTime:((Comment)x).DateTime);
foreach(var o in test)
{
Console.WriteLine(o);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Did some more thinking, using generic objects always makes me uneasy, and came up with a better solution. by using a base class with the common properties and the book and comment classes deriving from that, the list you make can contain either book or comment and it can be filtered and sorted by any of the base class properties:
public class Item
{
public string Id { get; set; }
public string UserId { get; set; }
public DateTime DateTime { get; set; }
}
public class Book:Item
{
public string Name { get; set; }
public override string ToString()
{
return $"Book: Id: {Id.PadRight(7)} - Name: {Name.PadRight(14)} - DateTime: {DateTime}";
}
}
public class Comment:Item
{
public string BookId { get; set; }
public string Content { get; set; }
public override string ToString()
{
return $"Comment: Id: {Id.PadRight(7)} - Content: {Content.PadRight(11)} - DateTime: {DateTime}";
}
}
Creating the filtered and sorted list becomes much simpler:
string userid = "User1";
var items = new List<Item>();
items.Add(new Book { Id = "B1", Name = "Book1", UserId = "User1", DateTime = new DateTime(2016, 11, 16, 11, 15, 00) });
items.Add(new Book { Id = "B2", Name = "Book2", UserId = "User1", DateTime = new DateTime(2016, 11, 16, 12, 15, 00) });
items.Add(new Book { Id = "B3", Name = "Book3", UserId = "User2", DateTime = new DateTime(2016, 11, 16, 10, 15, 00) });
items.Add(new Comment { Id = "c1", BookId = "B3", UserId = "User1", Content = "cmt1", DateTime = new DateTime(2016, 11, 16, 11, 17, 00) });
items.Add(new Comment { Id = "c2", BookId = "B1", UserId = "User1", Content = "cmt2", DateTime = new DateTime(2016, 11, 16, 11, 16, 00) });
var test = (from b in items
where b.UserId == userid
orderby b.DateTime
select b);
foreach (var o in test)
{
Console.WriteLine(o);
}
Please try this one:
var vm = from b in Book
join c in Comment on b.Id equals c.BookId into d
where b.UserId == userid
orderby b.DateTime
select new {
OrderedDate = from item in d
orderby item.DateTime
select item
}
or another approach by UNION it
var vm = (from a in book
where a.UserId == userid
select new { a.DateTime })
.Union
(from b in Comment
where (x=> x.Book.Any(y=>y.UserId == userid))
select new { b.DateTime })
.Select(c => new { c.DateTime }).OrderBy(d => d.DateTime);
If I understand you correctly, you would like to display the records of two tables with different columns in a single grid.
Which columns are to be displayed?
Lets say they are ID, TYPE (either book entry or comment entry) and DT (DateTime).
var result = (from b in _context.Books
where b.UserId = userid
select new {b.Id Id, "BOOK" Type, b.DateTime DT})
.Union(
(from c in _context.Comments
where c.UserId = userid
select new {c.Id Id, "COMMENT" Type, c.DateTime DT})
).OrderByDescending(x => x.DT);
this is how i did,.
public class BookAndComment
{
public DateTime DateTime { get; set; }
public Book Book { get; set; }
public Comment Comment { get; set; }
}
and query
var orderedBooksAndComments = = books.Where(book => book.UserId == userId).Select(book =>
new BookAndComment
{
DateTime = book.DateTime,
Book = book
}).Union(comments.Where(comment => comment.UserId == userId).Select(comment =>
new BookAndComment
{
DateTime = comment.DateTime,
Comment = comment
}
)).OrderByDescending(bookAndComment => bookAndComment.DateTime).ToList();
than
foreach (var item in orderedBooksAndComments)
{
if(item.Book!=null)
{
// Do something
}else
{
var comment = item.Comment;
// comment should not be null here.
// Do something.
}
}
When I try to fill seed in my database I got an error:
The entity found was of type PetsOwner_FF460419E950312585B2229EA7AA6AEAA692190083B7EA5830FB08DEEE79E35D when an entity of type Caretaker was requested."
I went through all tips which I found, I examined the relation between classes and didn't find any solution.
context.CareTakers.AddOrUpdate(c => c.Id,
new Caretaker { Id = 1, Name = "Jan", Surname = "Nowak", Pesel = "29032400352", BankAccountNumber = "05 8202 1016 5582 6188 2074 1719", PhoneNumber = "666-449-292", Accomodations = new List<Accommodation> { context.Accomodations.Find(1) } },
new Caretaker { Id = 2, Name = "Anna", Surname = "Kowalski", Pesel = "29010209388", BankAccountNumber = "76 1610 1335 0163 2600 0322 4138", PhoneNumber = "789-335-709", Accomodations = new List<Accommodation> { context.Accomodations.Find(2) } },
new Caretaker { Id = 3, Name = "Sylwia", Surname = "Boski", Pesel = "42022605090", BankAccountNumber = "21 8499 0008 3163 0216 0506 9685", PhoneNumber = "538 -887-645", Accomodations = new List<Accommodation> { context.Accomodations.Find(3) } },
new Caretaker { Id = 4, Name = "Zbigniew", Surname = "Ryba", Pesel = "10262717572", BankAccountNumber = "61 9614 0008 9550 1997 2431 0213", PhoneNumber = "795-200-495", Accomodations = new List<Accommodation> { context.Accomodations.Find(4) } }
);
context.Accomodations.AddOrUpdate(a => a.Id,
new Accommodation { Id = 1, Beginning = new DateTime(2016, 1, 2), End = new DateTime(2016, 1, 3), PricePerDay = 10.5, Pet = context.Pets.Find(1), Caretaker = context.CareTakers.Find(1) },
new Accommodation { Id = 2, Beginning = new DateTime(2015, 2, 3), End = new DateTime(2016, 2, 4), PricePerDay = 100.0, Pet = context.Pets.Find(2), Caretaker = context.CareTakers.Find(2) },
new Accommodation { Id = 3, Beginning = new DateTime(2014, 9, 10), End = new DateTime(2016, 9, 11), PricePerDay = 12.3, Pet = context.Pets.Find(3), Caretaker = context.CareTakers.Find(3) },
new Accommodation { Id = 4, Beginning = new DateTime(2013, 11, 12), End = new DateTime(2016, 12, 13), PricePerDay = 41.4, Pet = context.Pets.Find(4), Caretaker = context.CareTakers.Find(4) }
);
My classes look like:
public class Accommodation {
public int Id { get; set; }//{BAG}
public virtual Caretaker Caretaker { get; set; }
public virtual Pet Pet { get; set; }
public double PricePerDay { get; set; }
public DateTime Beginning { get; set; }
public DateTime End { get; set; }
public double TotalPrice {
get {
return (End - Beginning).Days * PricePerDay;
}
}
}
[Table("Caretakers")]
public class Caretaker : Person {
public static int Percent => 20;//C# 6
public DateTime ObtainedLicense { get; set; }
public DateTime Hired { get; set; }
public double Salary => 0;
public virtual ICollection<Surgery> Surgeries { get; set; }
public virtual Vet Superior { get; set; }//rekurencyjna
//pochodny
public int AnimalsCurrentlyCareTaken => 0;//Lambda do wyliczenia ilosci opiekowaniymi sie zwierzetyami
public virtual ICollection<Accommodation> Accomodations { get; set; }
}
I'd like to make a method to generate a hierarchy by ParentId
This is my Db class :
public class Kart
{
public long Id { get; set; }
public long? ParentId { get; set; }
public string Name { get; set; }
}
and my Model Class
public class KartModel
{
public long Id { get; set; }
public long? ParentId { get; set; }
public string Hierarchy { get; set; }
public string Name { get; set; }
}
I don't store Hierarchy column in db.
I would like to set Hierarchy property by ParentId like
1
1.1
1.1.1
1.1.2
1.1.3
1.2
1.3
1 //(if ParentId == null start 1)
1.1
Thanks.
Edit: ParentId references itself (Kart)
Recursive methods are the simplest way to set up hierarchical structures. The code below is a console application that generates the hierarchy so that you asked.
class Program
{
public class Kart
{
public long Id { get; set; }
public long? ParentId { get; set; }
public string Name { get; set; }
}
public class KartVM
{
public long Id { get; set; }
public long? ParentId { get; set; }
public string Hierarchy { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
List<Kart> list = new List<Kart>
{
new Kart {Id = 1, ParentId = null, Name = "Main Content1"},
new Kart {Id = 2, ParentId = 1, Name = "Main Content1"},
new Kart {Id = 3, ParentId = 1, Name = "Main Content1"},
new Kart {Id = 4, ParentId = 1, Name = "Main Content1"},
new Kart {Id = 5, ParentId = 3, Name = "Main Content1"},
new Kart {Id = 6, ParentId = 3, Name = "Main Content1"},
new Kart {Id = 7, ParentId = 4, Name = "Main Content1"},
new Kart {Id = 8, ParentId = 4, Name = "Main Content1"},
new Kart {Id = 9, ParentId = 8, Name = "Main Content1"},
new Kart {Id = 10, ParentId = 8, Name = "Main Content1"},
new Kart {Id = 11, ParentId = 8, Name = "Main Content1"},
new Kart {Id = 12, ParentId = 11, Name = "Main Content1"},
new Kart {Id = 13, ParentId = 11, Name = "Main Content1"},
new Kart {Id = 14, ParentId = 13, Name = "Main Content1"},
new Kart {Id = 15, ParentId = null, Name = "Main Content1"},
new Kart {Id = 16, ParentId = 15, Name = "Main Content1"},
new Kart {Id = 17, ParentId = 16, Name = "Main Content1"},
new Kart {Id = 18, ParentId = 17, Name = "Main Content1"},
new Kart {Id = 19, ParentId = 18, Name = "Main Content1"},
};
List<KartVM> theResult = new List<KartVM>();
GetHierachicalList(list, theResult, null, "");
foreach(KartVM t in theResult)
{
Console.WriteLine(t.Hierarchy);
}
Console.ReadLine();
}
static void GetHierachicalList(List<Kart> kart, List<KartVM> kartVM, Kart currentNode, string curH)
{
List<Kart> tmp = new List<Kart>();
if (currentNode == null)
tmp = kart.Where(c => c.ParentId == null).ToList();
else
tmp = kart.Where(c => c.ParentId == currentNode.Id).ToList();
int count = 1;
foreach(Kart k in tmp)
{
KartVM tmpVM = new KartVM { Id = k.Id, Name = k.Name, ParentId = k.ParentId };
tmpVM.Hierarchy += curH + "." + count.ToString();
if (tmpVM.Hierarchy.StartsWith("."))
tmpVM.Hierarchy = tmpVM.Hierarchy.Remove(0, 1);
kartVM.Add(tmpVM);
count++;
GetHierachicalList(kart, kartVM, k, tmpVM.Hierarchy);
}
}
}
In this case , the method is recursive GetHierachicalList
To run it make the call as follows:
GetHierachicalList(list, theResult, null, "")
Hope this helps.