I am writing a test OData Rest API with an InMemoryDatabase.
I would like to use DTO(s) to hide the SQL model and adjust a few fields (geographic positions and so on).
However, when I use ProjectTo<...> method from AutoMapper, GET request to the API return an empty collection instead of the actual result list.
Do you have any idea about what I am doing wrong ?
Here is the controller :
namespace offers_api.Controllers
{
public class OffersController : ODataController
{
private readonly OfferContext _context;
private IMapper _mapper;
public OffersController(OfferContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[EnableQuery]
public IActionResult Get()
{
IQueryable<Offer> res = _context.Offers.ProjectTo<Offer>(_mapper.ConfigurationProvider); // <-- works without ProjectTo !
return Ok(res);
}
}
}
The automapper declaration :
namespace offers_api.Entities
{
public class Mapping : Profile
{
public Mapping()
{
//CreateMap<CategoryEntity, string>().ConvertUsing(cat => cat.Name ?? string.Empty);
CreateMap<LocationEntity, Location>()
.ForMember(x => x.longitude, opt => opt.MapFrom(o => 0))
.ForMember(x => x.latitude, opt => opt.MapFrom(o => 0))
.ReverseMap();
CreateMap<OfferEntity, Offer>()
.ForMember(x => x.Category, opt => opt.MapFrom(o => o.Category.Name))
.ReverseMap()
.ForMember(x => x.Category, opt => opt.MapFrom(o => new CategoryEntity { Name = o.Category }));
CreateMap<OfferPictureEntity, OfferPicture>().ReverseMap();
CreateMap<UserEntity, User>().ReverseMap();
}
}
}
The EDM model :
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Offer>("Offers");
return builder.GetEdmModel();
}
I found the solution.
In fact, automapper loaded more data than OData's default behaviour.
The relation between an offer and it's author was described by a non nullable foreing key. I didn't insert any author in the DB, but OData tried to load a user and saw it was missing in the USER table, so it discarded the Offer result.
Solution : make the foreign key nullable.
namespace offers_api.Entities
{
public class OfferEntity
{
[Key]
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public long AuthorId { get; set; } // <== Bug here : add long? to resolve it...
public virtual UserEntity Author { get; set; }
}
}
Related
I am unable to generate a migration for a many to many relationship that spans 2 different db contexts.
I have 3 entities:
AccountApp, SubscriptionDetail, SubscribedAccountApp
AccountApp and SubscribedAccountApp are the AccountDbContext while the SubscriptionDetail is in a SubscriptionsDbContext.
I want to set up an indirect many to many relationship as described here
Based on the documentation, I have created an EntityTypeConfig for the SubscribedAccountApp like this
public class SubscribedAccountAppConfig : IEntityTypeConfiguration<SubscribedAccountApp>
{
public void Configure(EntityTypeBuilder<SubscribedAccountApp> builder)
{
builder.ToTable("subscribed_account_apps");
builder.HasKey(m => new { m.SubscriptionDetailId, m.AccountAppId });
builder.HasOne(x => x.SubscriptionDetail)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.SubscriptionDetailId);
builder.HasOne(x => x.AccountApp)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.AccountAppId);
}
}
This config is then applied to the AccountDbContext like so
public class AccountDbContext : DbContext
{
public IQueryable<AccountApp> AccountApps { get; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new AccountAppConfig());
modelBuilder.ApplyConfiguration(new SubscribedAccountAppConfig());
}
}
The data model for SubsriptionsDetails looks like:
public class SubscriptionDetail
{
private readonly List<SubscribedAccountApp> _subscribedAccountApps;
private readonly List<ConsumptionComponent> _consumptionComponents;
public SubscriptionDetail(
Guid id,
IEnumerable<ConsumptionComponent> consumptionComponents = null,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_consumptionComponents = consumptionComponents ?? new List<ConsumptionComponent>();
_subscribedAccountApps = subscribedAccountApps ?? new List<SubscribedAccountApp>();
}
public Guid Id { get; set; }
public IReadOnlyCollection<ConsumptionComponent> ConsumptionComponents => _consumptionComponents;
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
The data model for AccountApp looks like:
public class AccountApp
{
public readonly List<SubscribedAccountApp> _subscribedAccountApps;
public AccountApp(Guid id,
Guid accountId,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_subscribedAccountApps = subscribedAccountApps.ToNavigationProperty();
}
public Guid Id { get; private set; }
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
This leaves me an error when I try to create a migration for the AccountDbContext:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
System.InvalidOperationException: No suitable constructor was found for entity type 'ConsumptionComponent'.
This error describes a ConsumptionComponent constructor for some reason, although this model has already existed and nothing has changed on that model. This model does appear as an IEnumerable in the SubscriptionDetail constructor but I am not sure why it is showing up as an error. All I want this migration to do is add support for the indirect-many-to-many-relationship which has nothing to do with ConsumptionComponent.
For the life of me, I cannot get this to work properly. I have a relatively simple domain model that has a couple of navigation properties that I want to fill out via eager loading.
To keep my domain model pure, I have opted to use shadow properties as foreign keys, so they are not accessible by the client code.
This is the domain model:
public class CourseType : Entity
{
protected CourseType() { }
public CourseType(string name, CoachGroup coachGroup, bool active)
{
Name = name;
CoachGroup = coachGroup;
Active = active;
}
public string Name { get; private set; }
private int? _coachGroupId;
private int? CoachGroupId => _coachGroupId;
public virtual CoachGroup CoachGroup { get; private set; }
public int? AgeLimit { get; private set; }
public bool Active { get; private set; }
public bool ShowInSearchForm { get; private set; }
private List<Course> _accessGivingCourses = new List<Course>();
public virtual IReadOnlyList<Course> AccessGivingCourses => _accessGivingCourses?.ToList();
}
This is how I wire the configuration up:
public class CourseTypeConfiguration : IEntityTypeConfiguration<CourseType>
{
public void Configure(EntityTypeBuilder<CourseType> builder)
{
builder.ToTable("CourseTypes", "Courses");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).HasColumnName("CourseTypeId");
builder.Property(p => p.Name).HasColumnName("CourseTypeName");
builder.Property(p => p.Active).HasColumnName("IsActive");
builder.Property(p => p.ShowInSearchForm).HasColumnName("ShowInSearchForm");
builder.Property(p => p.AgeLimit).HasColumnName("AgeLimit");
builder.Property<int?>("CoachGroupId").HasField("_coachGroupId");
builder.HasOne(p => p.CoachGroup).WithOne().HasForeignKey<CourseType>("CoachGroupId").OnDelete(DeleteBehavior.Restrict);
builder.HasMany(p => p.AccessGivingCourses).WithMany("AccessGivingCourses")
.UsingEntity<Dictionary<string, object>>("CourseTypesAccessGivingCourses",
j => j.HasOne<Course>().WithMany().HasForeignKey("CourseId"),
j => j.HasOne<CourseType>().WithMany().HasForeignKey("CourseTypeId"),
j => j.ToTable("CourseTypesAccessGivingCourses")
);
builder.HasIndex("CoachGroupId").IsUnique(false);
}
}
This is how I extract the data via my repository class:
public override async Task<IEnumerable<CourseType>> GetAll()
{
try
{
return await Context.CourseTypes.Include(i => i.CoachGroup).Include(i => i.AccessGivingCourses).ToListAsync();
}
catch (Exception e)
{
Logger.LogCritical(e, $"Could not retrieve list of course type entities");
throw;
}
}
It ALMOST works, except for the fact that when I add or update entities, the CoachGroup link randomly gets lost for some updates. For others, it works just fine. It's like Entity Framework Core OR the database randomly loses track of it. Which is odd, because when I look in the database table, the foreign keys in the table are all there like they're supposed to!?
Does anyone have any idea what the hell I am doing wrong? Or if this is the correct approach to this problem at all? All I want to do is to load related data, but it's getting to the point where it's becoming rocket science to merely link a couple of optional relationships together...
I am using EF Core 3.1.7. The DbContext has the UseLazyLoadingProxies set. Fluent API mappings are being used to map entities to the database. I have an entity with a navigation property that uses a backing field. Loads and saves to the database seem to work fine except for an issue when accessing the backing field before I access the navigation property.
It seems that referenced entities don't lazy load when accessing the backing field. Is this a deficiency of the Castle.Proxy class or an incorrect configuration?
Compare the Student class implementation of IsRegisteredForACourse to the IsRegisteredForACourse2 for the behavior in question.
Database tables and relationships.
Student Entity
using System.Collections.Generic;
namespace EFCoreMappingTests
{
public class Student
{
public int Id { get; }
public string Name { get; }
private readonly List<Course> _courses;
public virtual IReadOnlyList<Course> Courses => _courses.AsReadOnly();
protected Student()
{
_courses = new List<Course>();
}
public Student(string name) : this()
{
Name = name;
}
public bool IsRegisteredForACourse()
{
return _courses.Count > 0;
}
public bool IsRegisteredForACourse2()
{
//Note the use of the property compare to the previous method using the backing field.
return Courses.Count > 0;
}
public void AddCourse(Course course)
{
_courses.Add(course);
}
}
}
Course Entity
namespace EFCoreMappingTests
{
public class Course
{
public int Id { get; }
public string Name { get; }
public virtual Student Student { get; }
protected Course()
{
}
public Course(string name) : this()
{
Name = name;
}
}
}
DbContext
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreMappingTests
{
public sealed class Context : DbContext
{
private readonly string _connectionString;
private readonly bool _useConsoleLogger;
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public Context(string connectionString, bool useConsoleLogger)
{
_connectionString = connectionString;
_useConsoleLogger = useConsoleLogger;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information)
.AddConsole();
});
optionsBuilder
.UseSqlServer(_connectionString)
.UseLazyLoadingProxies();
if (_useConsoleLogger)
{
optionsBuilder
.UseLoggerFactory(loggerFactory)
.EnableSensitiveDataLogging();
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>(x =>
{
x.ToTable("Student").HasKey(k => k.Id);
x.Property(p => p.Id).HasColumnName("Id");
x.Property(p => p.Name).HasColumnName("Name");
x.HasMany(p => p.Courses)
.WithOne(p => p.Student)
.OnDelete(DeleteBehavior.Cascade)
.Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);
});
modelBuilder.Entity<Course>(x =>
{
x.ToTable("Course").HasKey(k => k.Id);
x.Property(p => p.Id).HasColumnName("Id");
x.Property(p => p.Name).HasColumnName("Name");
x.HasOne(p => p.Student).WithMany(p => p.Courses);
});
}
}
}
Test program which demos the issue.
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Linq;
namespace EFCoreMappingTests
{
class Program
{
static void Main(string[] args)
{
string connectionString = GetConnectionString();
using var context = new Context(connectionString, true);
var student2 = context.Students.FirstOrDefault(q => q.Id == 5);
Console.WriteLine(student2.IsRegisteredForACourse());
Console.WriteLine(student2.IsRegisteredForACourse2()); // The method uses the property which forces the lazy loading of the entities
Console.WriteLine(student2.IsRegisteredForACourse());
}
private static string GetConnectionString()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
return configuration["ConnectionString"];
}
}
}
Console Output
False
True
True
When you declare a mapped property in an EF entity as virtual, EF generates a proxy which is capable of intercepting requests and assessing whether the data needs to be loaded. If you attempt to use a backing field before that virtual property is accessed, EF has no "signal" to lazy load the property.
As a general rule with entities you should always use the properties and avoid using/accessing backing fields. Auto-initialization can help:
public virtual IReadOnlyList<Course> Courses => new List<Course>().AsReadOnly();
I am using value objects as identities and would like to know how to best deal with nulls in EF core.
For example if I have an employee with an optional title (mr, mrs, etc):
public class Employee
: Entity,
IAggregateRoot
{
public EmployeeTitleId TitleId { get; private set; }
public EmployeeFirstName FirstName { get; private set; }
public EmployeeSurname Surname { get; private set; }
...
}
I could check for nulls everywhere in my code e.g.
if (employee.TitleId == null) ...
or I could use a default e.g.
if (employee.TitleId.Equals(EmployeeTitleId.None)) ...
with the EmployeeTitleId implemented as follows:
public class EmployeeTitleId
: Value<EmployeeTitleId>
{
public static readonly EmployeeTitleId None = new EmployeeTitleId();
protected EmployeeTitleId() { }
public EmployeeTitleId(Guid value)
{
if (value == default)
throw new ArgumentNullException(nameof(value), "Employee title id cannot be empty");
Value = value;
}
public Guid Value { get; internal set; }
public static implicit operator Guid(EmployeeTitleId self) => self.Value;
public static implicit operator EmployeeTitleId(string value)
=> new EmployeeTitleId(Guid.Parse(value));
public override string ToString() => Value.ToString();
}
I would prefer the second approach as it seems cleaner but I don't know if this is overkill. It would also require a bit more setup on the entity framework side e.g.:
builder.OwnsOne(_ => _.TitleId).Property(_ => _.Value)
.HasColumnName("TitleId").HasConversion(v => v == default ? (Guid?) null : v, v => v ?? EmployeeTitleId.None);
Does this seem like a viable approach or should I just stick to checking for null? If so, is there a convention I could use in the entity type configurations so that I don't have to manually set each HasConversion?
There is another way to filter the results globally. You can configure this in the OnModelCreating like:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// code omitted for brevity
modelBuilder.Entity<Employee>().HasQueryFilter(p => p.TitleId == null);
}
I have the following classes
public class Slot : Entity
{
public SnackPile SnackPile { get; set; }
public SnackMachine SnackMachine { get; set; }
public int Position { get; }
protected Slot()
{
}
public Slot(SnackMachine snackMachine, int position)
{
SnackMachine = snackMachine;
Position = position;
SnackPile = SnackPile.Empty;
}
}
public class Snack : AggregateRoot
{
public string Name { get; set; }
public Snack()
{
}
private Snack(long id, string name)
{
Id = id;
Name = name;
}
}
public class SnackPile : ValueObject
{
public Snack Snack { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
protected SnackPile()
{
}
public SnackPile(Snack snack, int quantity, decimal price)
{
Snack = snack;
Quantity = quantity;
Price = price;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Snack;
yield return Quantity;
yield return Price;
}
}
I'm trying to build my relationships using Entity Framework Core but my SnackPiles and Snacks are all null when trying to load them in my UI. However if I only set up my SnackMachines, all my of SnackPiles load fine but have null Snacks.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<SnackMachine>(entity =>
{
entity.OwnsOne<Money>(e => e.MoneyInside, MoneyInside =>
{
MoneyInside.Property(mi => mi.OneCentCount).HasColumnName("OneCentCount");
MoneyInside.Property(mi => mi.TenCentCount).HasColumnName("TenCentCount");
MoneyInside.Property(mi => mi.QuarterCentCount).HasColumnName("QuarterCentCount");
MoneyInside.Property(mi => mi.OneDollarCount).HasColumnName("OneDollarCount");
MoneyInside.Property(mi => mi.FiveDollarCount).HasColumnName("FiveDollarCount");
MoneyInside.Property(mi => mi.TenDollarCount).HasColumnName("TenDollarCount");
}).Ignore(o => o.MoneyInTransaction);
});
builder.Entity<Slot>(entity =>
{
entity.Property(e => e.Position);
entity.OwnsOne<SnackPile>(e => e.SnackPile, SnackPile =>
{
SnackPile.Property(sp => sp.Quantity).HasColumnName("Quantity");
SnackPile.Property(sp => sp.Price).HasColumnName("Price").HasColumnType("Decimal");
});
});
}
}
I have two questions. Doing this, I get a shadow property called SnackPile_SnackId which I would like named SnackId but nothing I do accomplishes this without creating both properties and the SnackPile_SnackId is set up as the FK.
The next question, is.. is this relationship attainable in Entity Framework Core 3? It appears I have an Entity that has a value object containing the Id of another Entity which I would like to reference.
The result I would like to get can be done with NHibernate
public class SlotMap : ClassMap<Slot>
{
public SlotMap()
{
Id(x => x.Id);
Map(x => x.Position);
Component(x => x.SnackPile, y =>
{
y.Map(x => x.Quantity);
y.Map(x => x.Price);
y.References(x => x.Snack).Not.LazyLoad();
});
References(x => x.SnackMachine);
}
}
Further reference is that I'm following the DDDInPractice course on PluralSite which uses NHibernate (It's an amazing course and highly recommend). Using EF is a learning exercise to see the nuances. The course owner referred me to his blog post on the subject but there have been changes to EF since then. I have an ok understanding for a lot of these concepts but I'm stuck here.
Number 6 in the list:
https://enterprisecraftsmanship.com/posts/ef-core-vs-nhibernate-ddd-perspective/
The problem is that I wasn't using lazy loading.