Been having a play about with ef core and been having an issue with the include statement. For this code I get 2 companies which is what i expected.
public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
var c = db.Company;
return c;
}
This returns
[
{
"id":1,
"companyName":"new",
"admins":null,
"employees":null,
"courses":null
},
{
"id":2,
"companyName":"Test Company",
"admins":null,
"employees":null,
"courses":null
}
]
As you can see there are 2 companies and all related properties are null as i havnt used any includes, which is what i expected. Now when I update the method to this:
public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
var c = db.Company
.Include(t => t.Employees)
.Include(t => t.Admins)
.ToList();
return c;
}
this is what it returns:
[
{
"id":1,
"companyName":"new",
"admins":[
{
"id":2,
"forename":"User",
"surname":"1",
"companyId":1
}
]
}
]
It only returns one company and only includes the admins. Why did it not include the 2 companies and their employees?
public class Company
{
public int Id { get; set; }
public string CompanyName { get; set; }
public List<Admin> Admins { get; set; }
public List<Employee> Employees { get; set; }
public List<Course> Courses { get; set; }
public string GetFullName()
{
return CompanyName;
}
}
public class Employee
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company company { get; set; }
public ICollection<EmployeeCourse> Employeecourses { get; set; }
}
public class Admin
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
}
I'm not sure if you've seen the accepted answer to this question, but the problem is to do with how the JSON Serializer deals with circular references. Full details and links to more references can be found at the above link, and I'd suggest digging into those, but in short, adding the following to startup.cs will configure the serializer to ignore circular references:
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Make sure you are using Include from "Microsoft.EntityFrameworkCore" And Not from "System.Data.Entity"
Lazy loading is not yet possible with EF Core. Refer here.
Alternatively you can use eager loading.
Read this article
Below is the extension method i have created to achieve the eager loading.
Extension Method:
public static IQueryable<TEntity> IncludeMultiple<TEntity, TProperty>(
this IQueryable<TEntity> source,
List<Expression<Func<TEntity, TProperty>>> navigationPropertyPath) where TEntity : class
{
foreach (var navExpression in navigationPropertyPath)
{
source= source.Include(navExpression);
}
return source.AsQueryable();
}
Repository Call:
public async Task<TEntity> FindOne(ISpecification<TEntity> spec)
{
return await Task.Run(() => Context.Set<TEntity>().AsQueryable().IncludeMultiple(spec.IncludeExpression()).Where(spec.IsSatisfiedBy).FirstOrDefault());
}
Usage:
List<object> nestedObjects = new List<object> {new Rules()};
ISpecification<Blog> blogSpec = new BlogSpec(blogId, nestedObjects);
var challenge = await this._blogRepository.FindOne(blogSpec);
Dependencies:
public class BlogSpec : SpecificationBase<Blog>
{
readonly int _blogId;
private readonly List<object> _nestedObjects;
public ChallengeSpec(int blogid, List<object> nestedObjects)
{
this._blogId = blogid;
_nestedObjects = nestedObjects;
}
public override Expression<Func<Challenge, bool>> SpecExpression
{
get { return blogSpec => blogSpec.Id == this._blogId; }
}
public override List<Expression<Func<Blog, object>>> IncludeExpression()
{
List<Expression<Func<Blog, object>>> tobeIncluded = new List<Expression<Func<Blog, object>>>();
if (_nestedObjects != null)
foreach (var nestedObject in _nestedObjects)
{
if (nestedObject is Rules)
{
Expression<Func<Blog, object>> expr = blog => blog.Rules;
tobeIncluded.Add(expr);
}
}
return tobeIncluded;
}
}
Will be glad if it helps. Please note this is not a production ready code.
I test your code, this problem exist in my test. in this post LINK Proposed that use data projection. for your problem Something like the following, is work.
[HttpGet]
public dynamic Get()
{
var dbContext = new ApplicationContext();
var result = dbContext.Companies
.Select(e => new { e.CompanyName, e.Id, e.Employees, e.Admins })
.ToList();
return result;
}
I know this is an old issue, but its the top result in google, so im putting my solution i I found here. For a Core 3.1 web project there is a quick fix.
Add nuget package Microsoft.EntityFrameworkCore.Proxies.
Then you simply just need to specify in your options builder when configuring your services. Docs: https://learn.microsoft.com/en-us/ef/core/querying/related-data
public void ConfigureServices(IServiceCollection services) {
services.AddDbContextPool<YourDbContext>(options => {
options.UseLazyLoadingProxies();
options.UseSqlServer(this.Configuration.GetConnectionString("MyCon"));
});
}
Now your lazy loading should work as it did in previous EF versions.
If you not using it for a web project, you can do it right inside of the OnConfiguringMethod inside of your DbContext itself.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseLazyLoadingProxies();
}
My EF stuff is kept in a separate class library so i can re-use it through multiple company applications. So having the ability to not lazy load when not needed for a particular application is useful. So i prefer passing in the build options, for reuse-ability purposes. But both will work.
Related
I have a small database that has been created by EF using a typical model class:
public class Metrics
{
public int Id { get; set; }
public string? MetricValue { get; set; }
public string? MetricHost { get; set; }
public string? MetricTime { get; set; }
}
The database is now populated with data and my Minimal API can return all the entries from:
app.MapGet("/metric", async (DataContext context) => await context.Metrics.ToListAsync());
And also, I can query by Id:
app.MapGet("/metric/{id}", async (DataContext context, int id) =>
await context.Metrics.FindAsync(id) is Metric metric ?
Results.Ok(metric) :
Results.NotFound("Metric not found"));
I've been searching the web for something that would show how to search by another property but have not found anything that works. e.g.,
app.MapGet("/hostnames/{MetricHost}"...
This article on CodeMaze is the closest I've found but none of the examples seem to work:
https://code-maze.com/aspnetcore-query-string-parameters-minimal-apis/
Any help is appreciated. Here's an example that did not work:
app.MapGet("/search", (SearchCriteria criteria) =>
{
return $"Host: {criteria.MetricHost}, Id: {criteria.Id}";
});
With model changes:
public class Metric
{
public int Id { get; set; }
public string? MetricValue { get; set; }
public string? MetricHost { get; set; }
public string? MetricTime { get; set; }
public static ValueTask<Metric?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string hostname = context.Request.Query["MetricHost"];
int.TryParse(context.Request.Query["Id"], out var id);
var result = new Metric
{
MetricHost = hostname,
Id = id
};
return ValueTask.FromResult<Metric?>(result);
}
}
You are binding wrong type, BindAsync should be part of SearchCriteria:
app.MapGet("/search", (SearchCriteria criteria, DataContext context) =>
{
IQueryable<Metric> query = context.Metrics;
if(criteria.MetricHost is not null)
{
query = query.Where(m => m.MetricHost == criteria.MetricHost)
}
// ... rest of filters
return await query.ToListAsync();
});
public class SearchCriteria
{
public string? MetricHost { get; set; }
// ... rest of filters
public static ValueTask<SearchCriteria?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string hostname = context.Request.Query["MetricHost"];
// ... rest of filters
var result = new SearchCriteria
{
MetricHost = hostname,
};
return ValueTask.FromResult<SearchCriteria?>(result);
}
}
Read more:
Filtering in EF Core
Im currently trying to implement CRUD functionality with a dbfactory and generics with microsoft EF, but while listing entries is working, making changes to the db is currently not working.
public class AbstractDataModel
{
[Key]
public Guid gid { get; set; }
}
Model
class SalesOrder : AbstractDataModel
{
public int salesOrderID { get; set; }
public int productID { get; set; }
public int customerID { get; set; }
public Guid createdBy { get; set; }
public string dateCreated { get; set; }
public string orderDate { get; set; }
public string orderStatus { get; set; }
public string dateModified { get; set; }
}
A DBCore with some other functionality besides the ones listed here, which are not relevant for the factory
public class DBCore : DbContext
{
public static string connectionString = "myConnectionStringToDb";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
Data Service which calls factory
class SalesOrderService : DBCore
{
public DbSet<SalesOrder> SalesOrders { get; set; }
public OkObjectResult GetAllSalesOrders()
{
DBFactory factory = new DBFactory();
return new OkObjectResult(JsonConvert.SerializeObject(factory.GetAll(SalesOrders)));
}
public OkObjectResult AddSalesOrder(SalesOrder order)
{
order.gid = Guid.NewGuid();
return DBFactory.AddOne(order);
}
public OkObjectResult UpdateSalesOrder(SalesOrder order)
{
return DBFactory.UpdateOne(order);
}
public OkObjectResult DeleteSalesOrder(SalesOrder order)
{
return DBFactory.DeleteOne(order);
}
}
simple CRUD-Factory,
class DBFactory : DBCore
{
public DbSet<UserModel> Users { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
public List<T> GetAll<T>(DbSet<T> dbset) where T : class
{
using (this)
{
return dbset.ToList();
}
}
public static OkObjectResult AddOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Add(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully added");
}
}
public static OkObjectResult UpdateOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Update(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully updated");
}
}
public static OkObjectResult DeleteOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Attach(data);
factory.Remove(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully removed");
}
}
}
Edit: Following the advices i changed the code so it should SaveChanges for the Factory, which also contains the context as a property. But it still doesnt seem to work for all database operations except listing all entries
Editv2: Thanks for the adivces it seems i have solved that problem, but a new one appeared :D
I can now do database operations like deleting entries, but now i cant list the entries anymore because the following error occurs, although the code there didnt really change:
"Executed 'GetAllOrders' (Failed, Id=5fb95793-572a-4545-ac15-76dffaa7a0cf, Duration=74ms)
[2020-10-23T14:33:43.711] System.Private.CoreLib: Exception while executing function: GetAllOrders. Newtonsoft.Json: Self referencing loop detected for property 'Context' with type 'FicoTestApp.Models.SalesOrder'. Path '[0].ChangeTracker'."
try adding
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to your
startup.cs
it should to the job
Right now I have some models that look like this:
Consumer ResourceModel:
public ConsumerResourceModel()
{
consumerData = new List<ConsumerData>();
Memberships = new List<MemberResourceModel>();
}
public List<ConsumerData> consumerData { get; set; }
public List<MemberResourceModel> Memberships { get; set; }
ConsumerResponseModel:
public class ConsumerResponseModel
{
public List<Consumer> consumers { get; set; }
}
public class Membership
{
public string membershipType { get; set; }
}
public class Consumer
{
public decimal consumerId { get; set; }
public Name name { get; set; }
public List<Membership> memberships { get; set; }
}
My current Mapping looks like this:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(config =>
{
config.CreateMap<ConsumerData, Consumer>()
.ForMember(dest=>dest.memberships, opts=>opts.MapFrom(src=>new List<Membership>()))
.ReverseMap();
config.CreateMap<ConsumerData, Name>()
.ReverseMap();
config.CreateMap<MemberResourceModel, Membership>()
.ReverseMap();
});
}
I have everything being mapped correctly for consumer and membership but I'm basically having to do something like this in my method:
public ConsumerResponseModel MapConsumer(IEnumerable<ConsumerResourceModel> res)
{
var responseModel = new ConsumerResponseModel {consumers = new List<Consumer>()};
var consumerResourceModels = res as IList<ConsumerResourceModel> ?? res.ToList();
foreach (var item in consumerResourceModels)
{
foreach(var consumerData in item.consumerData)
{
var consumer = Mapper.Map<Consumer>(consumerData);
foreach(var membership in item.Memberships)
{
consumer.memberships.Add(Mapper.Map<Membership>(membership);
}
responseModel.consumers.Add(consumer);
}
}
return responseModel;
}
While this code is doing the mapping I need it to do and everything works fine, I'm trying to figure out how I can make one call to Mapper.Map and it just automaps everything in this collection for me instead of having this extra method iterating through and just have it all being done in my AutoMapperConfig class.
I'm having a tough time wrapping my head around how to get that piece to work as everything I have tried like doing .AfterMap in the ConsumerData, Consumer mapping just ends up creating empty memberships. I haven't mapped ConsumerResourceModel yet as I figured once I learned how to map the memberships then I could move a level up to map that all correctly.
I have .net 4.5.2 test app playing about with Azure Mobile Services and I'm attempting to store data using the TableController. I have my data types as follows:
public class Run:EntityData
{
public int RunId { get; set; }
public DateTime? ActivityStarted { get; set; }
public DateTime? ActivityCompleted { get; set; }
public List<Lap> LapInformation { get; set; }
public Run()
{
LapInformation = new List<Lap>();
}
}
public class Lap
{
[Key]
public int LapNumber { get; set; }
public int CaloriesBurnt { get; set; }
public double Distance {get; set;}
//Some other basic fields in here
public DateTime? LapActivityStarted { get; set; }
public DateTime? LapActivityCompleted { get; set; }
public Lap()
{
}
In my Startup class I call:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
And in my MobileServiceContext class:
public class MobileServiceContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString2";
public MobileServiceContext() : base(connectionStringName)
{
}
public DbSet<Run> Runs { get; set; }
public DbSet<Lap> Laps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
}
In my controller then, I have:
[MobileAppController]
public class RunController: TableController<Run>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MobileServiceContext context = new MobileServiceContext();
DomainManager = new EntityDomainManager<Run>(context, Request);
}
public IList<Run> GetAllRuns()
{
var runs = context.Runs.Include("LapInformation").ToList();
return runs;
}
public SingleResult<Run> GetRun(string id)
{
return Lookup(id);
}
public async Task<IHttpActionResult> PostRun(Run run)
{
Run current = await InsertAsync(run);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
public Task DeleteRun(string id)
{
return DeleteAsync(id);
}
}
I can then POST a record in fiddler which responds with a 201 and the Location of the newly created Item. An Example of the data I'm posting is:
{RunId: 1234, LapInformation:[{LapNumber:1,Distance:0.8, LapActivityStarted: "2017-06-19T00:00:00", LapActivityCompleted: "2017-06-19T00:00:00", CaloriesBurnt: 12}]}
However, when I GET that object, I'm only getting the fields from Run, without the list of Detail records (Lap). Is there anything I have to configure in Entity Framework so that when I GET a Run record from the DB, it also gets and deserializes all associated detail records?
Hopefully that makes sense.
EDIT
Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
You can use custom EF query with Include() method instead of Lookup call preferably overload that takes function from System.Data.Entity namespace.
var runs = context.Runs.Include(r => r.LapInformation)
Take a look at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
AFAIK, you could also use the $expand parameter to expand your collections as follows:
GET /tables/Run$expand=LapInformation
Here is my sample, you could refer to it:
You could mark your action with a custom ActionFilterAttribute for automatically adding the $expand property to your query request as follows:
// GET tables/TodoItem
[ExpandProperty("Tags")]
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
For more details, you could refer to adrian hall's book chapter3 relationships.
EDIT Turns out that it is pulling back all the lap information, but when I return it to the client, that information is getting lost.
I defined the following models in my mobile client:
public class TodoItem
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public List<Tag> Tags { get; set; }
}
public class Tag
{
public string Id { get; set; }
public string TagName { get; set; }
}
After execute the following pull operation, I could retrieve the tags as follows:
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
Note: The Tags data is read-only, you could only update the information in the ToDoItem table.
Additionally, as adrian hall mentioned in Data Access and Offline Sync - The Domain Manager:
I prefer handling tables individually and handling relationship management on the mobile client manually. This causes more code on the mobile client but makes the server much simpler by avoiding most of the complexity of relationships.
I have a parent/child relationship between a ProductFamily (the parent) and a list of subordinates (SaleItem). I am running the Ravendb Server locally with the server pulled up as a console app. When I query the Family data I am attempting to include the list of SaleItems in the session to avoid extra trips to the server. However on the console I see the subsequent calls to load the individual saleitem list for each family as I step through the foreach loop. I think I am doing something incorrectly and am puzzled as to what it may be. I am on day 2 of using RavenDB, so any handholding is appreciated.
Classes:
public class Family
{
public string Id { get { return string.Format(#"Families/{0}", this.FamilyID); } }
public int FamilyID { get; set; }
public string FamilyNumber { get; set; }
public string Description { get; set; }
public string[] SaleitemIds { get; set; }
public override string ToString()
{
return string.Format("Number:{0} - {1}", FamilyNumber, Description);
}
[JsonIgnore]
public List<SaleItem> SaleItems { get; set; }
}
public class SaleItem
{
public string Id { get { return string.Format(#"SaleItems/{0}", this.SaleItemID); } }
public int SaleItemID { get; set; }
public string Description { get; set; }
public override string ToString()
{
return string.Format("Number:{0} - {1}", SaleItemID.ToString(), Description);
}
}
And the code:
List<SearchTerm> searchterms = new List<SearchTerm>(){ new SearchTerm(){term="1009110922"}
,new SearchTerm(){term="1009112439"}
,new SearchTerm(){term="1009122680"}
,new SearchTerm(){term="1009124177"}
,new SearchTerm(){term="1009133928"}
,new SearchTerm(){term="1009135435"}
,new SearchTerm(){term="1009148000"}};
using (IDocumentSession session = documentStore.OpenSession())
{
var results = session.Query<Family>().Customize(o => o.Include<SaleItem>(s => s.Id)).Where(x => x.FamilyNumber.In(searchterms.Select(t => t.term).ToList()));
foreach (Family fam in results)
{
Console.WriteLine(fam.ToString());
fam.SaleItems = session.Load<SaleItem>(fam.SaleitemIds).ToList();
foreach (SaleItem si in fam.SaleItems)
{
Console.WriteLine(" " + si.ToString());
}
}
}
As I step through the code I see the calls to Get the list of saleitems on the line:
fam.SaleItems = session.Load<SaleItem>(fam.SaleitemIds).ToList();
I believe I have implemented something incorrectly, but I am new enough with this platform to accept that I could have simply misunderstood what the behavior would be. There are definitely cases where I do not want the Saleitem doc to be embedded in the Family doc, so that is not really an option in this case.
Doug_w,
Look at what you are including:
o.Include<SaleItem>(s => s.Id)
You probably want it to be:
o.Include<SaleItem>(s => s.SaleitemIds )