EF Core 7 Inheritance - mapping abstract fields - c#

I have a following hierarchy I'd like to map:
public abstract class Investment
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Stock : Investment
{
public virtual ICollection<Dividend> Dividends { get; set; }
}
public class Bond : Investment
{
public virtual ICollection<Coupon> Coupons { get; set; }
}
public abstract class InvestmentYield
{
public int Id { get; set; }
public decimal Amount { get; set; }
// This is the tricky part!
public abstract Investment Investment { get; }
public abstract int InvestmentId { get; }
}
public partial class Dividend : InvestmentYield
{
public virtual Stock Stock { get; set; }
public int StockId { get; set; }
public override Investment Investment => Stock;
public override int InvestmentId => StockId;
}
public class Coupon : InvestmentYield
{
public virtual Bond Bond { get; set; }
public int BondId { get; set; }
public override Investment Investment => Bond;
public override int InvestmentId => BondId;
}
and I'd like to make a query to get all InvestmentYields including their investments.
I tried following code:
_context.Yields.Include(yield => yield.Investment)
but it ended up with error
"The expression 'yield.Investment' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
so I followed the advice and added those castings.
I got something like this:
_context.Yields
.Include(yield => (yield as Dividend).Stock)
.Include(yield => (yield as Coupon).Bond)
which is really ugly, but works. Even WHERE condition can be used as following
.Where(yield => (yield as Dividend).Stock.Id == investmentId
|| (yield as Interest).SavingsAccount.Id == investmentId
|| (yield as Coupon).Bond.Id == investmentId);
But problem is when it comes to using it in GroupBy, Select and other expressions. Another problem would be of course scalability - adding new entities would be pain and break things.
Ideally, I'd like to use TPT or TPC mapping and avoid TPH if possible. And I would like to have tables Stock, Bond and Dividend with foreign key to Stock, Coupon with foreign key to Bond.
Is this possible in EF.Core 7? I use MySQL as a database, if it makes any difference.

Related

How can I map a list of IDs to a list of Entities using AutoMapper.Collection

I'm using Entity Framework Core in a C# WebAPI project. I have the following entities (simplified for the purposes of this example):
public class Division
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int DivisionID { get; set; }
public virtual IEnumerable<CompetitorLevel> CompetitorLevels { get; set; }
}
public class CompetitorLevel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int CompetitorLevelID { get; set; }
[Required]
public string Name { get; set; }
public virtual IEnumerable<Division> Divisions { get; set; }
}
public class DivisionDTO
{
public int? id { get; set; }
public List<string> competitorLevels { get; set; }
}
The DTO is used to send and receive data to/from my frontend app.
I'm successfully using AutoMapper to map the straightforward properties, and to map from a Division to a DivisionDTO, no problem there. My problem is that I can't get conversion from the list of IDs on the DTO to a list of entities working.
I tried AutoMapper.Collection using every permutation of .EqualityComparison(...) or .MapFrom(...) that I could think of; using lists, selecting IDs, etc.. but I can't get it to work.
My solution right now is to build and map the entities manually in my service class, which is probably fine, but I feel like there may be a more elegant way of doing this in AutoMapper. Here is the solution in my service:
public void Update(DivisionDTO request)
{
if (request.id == null)
throw new ValidationException("The ID attribute is required.");
Division division = _db.Divisions
.Include(d => d.CompetitorLevels)
.FirstOrDefault(d => d.DivisionID == request.id.Value);
if (division == null)
throw new KeyNotFoundException("A division with that ID was not found.");
_mapper.Map(request, division);
UpdateCompetitorLevels(request, division);
_db.SaveChanges();
}
public void UpdateCompetitorLevels(DivisionDTO request, Division division)
{
// Remove competitor levels from division if not present in DTO
foreach (CompetitorLevel competitorLevel in division.CompetitorLevels.ToList())
{
if (!request.competitorLevels.Select(cl => int.Parse(cl)).Contains(competitorLevel.CompetitorLevelID))
{
(division.CompetitorLevels as List<CompetitorLevel>).Remove(competitorLevel);
}
}
// Add competitor level to division if present in DTO
foreach (int competitorLevelID in request.competitorLevels.Select(cl => int.Parse(cl)))
{
if (!division.CompetitorLevels.Any(cl => cl.CompetitorLevelID == competitorLevelID))
{
(division.CompetitorLevels as List<CompetitorLevel>).Add(_db.CompetitorLevels.Find(competitorLevelID));
}
}
}
I tried to create the Map bewteen string and CompetitorLevel as below:
public class DivisionProfile:Profile
{
public DivisionProfile()
{
CreateMap<DivisionDTO, Division>().ForMember(x=>x.CompetitorLevels,y=>y.MapFrom(x=>x.competitorLevels)).ForMember(x=>x.DivisionID,y=>y.MapFrom(x=>x.id));
CreateMap<string, CompetitorLevel>().ForMember(x => x.CompetitorLevelID, y => y.MapFrom(x => int.Parse(x)));
}
}
The result:

Automatic mapping of nested objects in Generic Repo with EF - returns null

I am back trying to get into the .NET again, I have been of development for the past 10 years, though I started with .NET 1.0 and now it's a bit different. I was used to the N-tier model, with ADO.NET or what i liked even better was the recordSet in ASP. I am trying to get my head around this Repository pattern, Genneric pattern and the Entity Framework.
Please have patience with my knowledge as I have only been back for the past month. I am building a product / order service, just for fun, trying to de-couple it both to use as microservice and MVC or WinForms if I would like. I just want to learn the proper way of doing this.
The problem is that I do not get nested objects and I do understand this, maybe I need to properly join them together, but then again, why would I then use the EF if I don't get this for free? I understand you get all the modelling from the framework etc, but you get my point.
I have two models
Orders Model with the foreign key pointed out - which in my world would map automatically
public class Orders
{
[Key]
public int OrderId { get; set; }
public int ProductId { get; set; }
[ForeignKey("ProductId")]
public IEnumerable<Product> Product { get; set; }
public DateTime Datetime { get; set; }
}
Product Model
public class Product
{
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public decimal Price { get; set; }
}
I have a Generic repository interface
public interface IGenericRepository <T> : IDisposable where T : class
{
IEnumerable<T> GetAllRecords();
IEnumerable<T> FindRecord(Expression<Func<T,bool>> predicate);
T GetRecordById(int objId);
void AddRecord(T obj);
void DeleteRecord(T obj);
void UpdateRecord(T obj);
}
I Implement this interface through
public class GenericRepository<T> : IGenericRepository<T>, IDisposable where T : class
{
private readonly DBContext _context;
private readonly DbSet<T> _DbSet;
public GenericRepository(DBContext context)
{
this._context = context;
this._DbSet = this._context.Set<T>();
}
public IEnumerable<T> GetAllRecords()
{
return _DbSet.ToList();
}
public IEnumerable<T> FindRecord(Expression<Func<T, bool>> predicate)
{
throw new NotImplementedException();
}
public T GetRecordById(int objId)
{
return _DbSet.Find(objId);
}
public void AddRecord(T obj)
{
_DbSet.Add(obj);
}
public void DeleteRecord(T obj)
{
_DbSet.Remove(obj);
}
public void UpdateRecord(T obj)
{
_DbSet.Attach(obj);
_context.Entry(obj).State = EntityState.Modified;
}
public void Save()
{
throw new NotImplementedException();
}
void IDisposable.Dispose()
{
throw new NotImplementedException();
}
}
And last i have the UnitOfWork with an interface that I implement through
public class UnitOfWork : IUnitOfWork
{
private DBContext _context;
// public ProductRepository productRepository { get; private set; }
//public OrderRepository OrderReposity { get; private set; }
public IGenericRepository<Product> productRepository { get; set; }
public IGenericRepository<Orders> OrderRepository { get; set; }
public UnitOfWork(DBContext context)
{
this._context = context;
this.productRepository = new GenericRepository<Product>(this._context);
this.OrderRepository = new GenericRepository<Orders>(this._context);
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
In the WebAPI controller I call the unitOfWork through
[Route("api/[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
public readonly UnitOfWork UoW;
public OrdersController(IUnitOfWork _uow)
{
this.UoW = _uow as UnitOfWork;
}
And pointing to the API method GET / Orders
// GET: api/<OrdersController>
[HttpGet]
public IEnumerable<Orders> Get()
{
return UoW.OrderRepository.GetAllRecords();
}
It works like a charm, I think its a good way to implement this. I can easily create another application to use the back-end, I can mock and test this pretty well. Everything is in 4 different projects in the solution.
But the problem is the returns a null on the products objects.
Feel free to give me all feedback you can, how I should build this if my solution is not preferred or if I am doing it too "de-coupled".
Thanks for a great forum and a great inspiration for learning
Best regards
Martin
I'm assuming you are using .NET Core here, so if you are not, please leave a comment and I'll re-write the solution to fit your needs. =)
First of all I believe that you have a miss modeled your data classes since um have a 1-n between products x orders, the way it was build means that a product can only be related by one order which is not true, so we need a table to represent the n-n relation between these two entities and a little bit changes in the original 2 ones to support the mapping between them.
Product.cs
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public decimal Price { get; set; }
public List<OrderProduct> OrderProducts { get; set; }
}
Order.cs
public class Order
{
public int OrderId { get; set; }
public DateTime Datetime { get; set; }
public List<OrderProduct> OrderProducts { get; set; }
}
OrderProduct.cs (the one that will ties things up)
public class OrderProduct
{
public int OrderProductId { get; set; }
public int ProductId { get; set; }
public int OrderId { get; set; }
public Product Product { get; set; }
public Order Order { get; set; }
}
With the entities now set up we can run for the part were entity will load the related entities automatically for you =)
To Entity Framework knows what is related to what we need more than convention naming (name thing equally), entity expects for explicit configuration.
You were configuring this relations with DataAnnotations which is not wrong, but personally I prefer to work with Fluent API since DataAnnotations create a hard bound between your entity models and Entity Framework making it harder to change your architecture for a DDD model in the future for example.
So let's configure our entities. To achieve this we will create a configuration file for each entity.
ProductConfig.cs
public class ProductConfig : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
// Tells entity that ProductId is your primary key
builder.HasKey(b => b.ProductId);
}
}
OrderConfig.cs
public class OrderConfig : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
// Tells entity that OrderId is your primary key
builder.HasKey(b => b.OrderId);
}
}
OrderProductConfig.cs
public class OrderProductConfig : IEntityTypeConfiguration<OrderProduct>
{
public void Configure(EntityTypeBuilder<OrderProduct> builder)
{
// Tells entity that OrderProductId is your primary key
builder.HasKey(b => b.OrderProductId);
// Configure the navigation property Order telling that an Order may be related to a bunch of OrderProducts
// setting up the OrderId as your foreign key constraint and telling Entity that is a required field (not null)
builder.HasOne(b => b.Order)
.WithMany(b => b.OrderProducts)
.HasForeignKey(b => b.OrderId)
.IsRequired();
// Configure the navigation property Product telling that a Product may be related to a bunch of OrderProducts
// setting up the ProductId as your foreign key constraint and telling Entity that is a required field (not null)
builder.HasOne(b => b.Product)
.WithMany(b => b.OrderProducts)
.HasForeignKey(b => b.ProductId)
.IsRequired();
}
}
Now we need a place to apply this configurations, so we need to create a context for your application, is a good practice to create one also =)
SalesSoftwareContext.cs
// This class inherits DbContext class, and will be your new "DbContext"
public class SalesSoftwareContext : DbContext
{
// During runtime this method will be called so Entity will load the configurations and knows how to handle things
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new OrderConfig());
builder.ApplyConfiguration(new ProductConfig());
builder.ApplyConfiguration(new OrderProductConfig());
}
}
Inside GenericRepository.cs and UnitOfWork.cs replace the references for DBContext by SalesSoftwareContext and run your code!
Now when you query the Order you will have a List of OrderProduct that you will be able to interact and get the product info, for an example:
public class OrderViewModel
{
public int OrderId {get;set;}
List<OrderItemViewModel> OrderItems {get;set;}
}
public class OrderItemViewModel
{
public int ProductId {get;set;}
public string Name {get;set;}
public decimal Price {get;set;}
}
[HttpGet]
public IEnumerable<OrderViewModel> Get()
{
List<OrderViewModel> returnValue = new List<OrderViewModel>();
List<Order> orders = UoW.OrderRepository.GetAllRecords();
foreach(var order in orders)
{
OrderViewModel orderToReturn = new OrderViewModel();
orderToReturn.OrderId = order.OrderId;
orderToReturn.OrderItems = order.OrderProducts
.Select(x => new OrderItemViewModel
{
ProductId = x.Product.ProductId,
Name = x.Product.ProductName,
Price = x.Product.Price
}).ToList());
returnValue.Add(orderToReturn);
}
return returnValue;
}
I hope this helps =)
Since I don't have your 'full-code' I ended up typing in the dark, sorry for any typo or error that came along with the posted solution
It's nice to have another developer coming back to .NET world, the .NET evolved a lot in the last years, specially when .NET Core came around, but I believe that all the changes were for better.
If you have specific questions or need any guidance, leave a comment with your email so we can get in touch.
Happy Holidays!

NHibernate, QueryOver returning list with list property

I have classes like:
public class A : BaseTableFields
{
public virtual string Name { get; set; }
public virtual IList<B> propB { get; set; }
}
public class B : BaseTableFields
{
public virtual A propA { get; set; }
public virtual IList<C> propC { get; set; }
}
public class C : BaseTableFields
{
public virtual B propB { get; set; }
public virtual IList<D> propD { get; set; }
}
So each of my class has one to many relation to class below of it.
How to write the most effecient query, that I receive List of type A (List listOfA) containing records in listOfA.propB and also the listOfA.propB having all of the referencing records in listOfA.propB.propC and so on.
Please help.
Let's assume for the start that:
var list = Session.QueryOver<A>().Where(x=>x.Name == "test").List().ToList();
returns me list with 3 elements of type A, but its property propB is empty.
I would suggest using the 'Fetch' or 'FetchMany' functions in the NHibernate LINQ provider. Examples are in this article for what it actually does and it shows the SQL it will generate.
Working off your example that would result like this:
var list = Session.QueryOver<A>()
.Where(x => x.Name == "test")
.FetchMany(x => x.propB)
.ThenFetchMany(x => x.propC)
.ToList();
If this is still causing you issues, then there may be a problem with your mapping files between the one to many relationships of the entities.

NHibernate: (Fluent) Mapping / Querying based upon a Getter that accesses an already mapped collection property

I don't know how to phrase this properly. I'm working with a pre-existing domain, where certain entities can contain children that are versioned. The children are responsible for their own version number, but ultimately, this version number only makes sense in context of the attached parent entity.
public class Blog
{
public virtual IList<VersionedItem> VersionedItems { get; set; }
public virtual CurrentVersionedItem {
get {
return VersionedItems.OrderByDescending(x => x.Version).FirstOrDefault();
}
}
}
public class VersionedItem
{
public virtual Blog { get;set; }
public virtual int Version { get; set; }
public virtual string Content { get; set; }
public virtual int SomeNumber { get; set; }
}
And what I'd like to achieve:
var blogs = Session.Query<Blog>(x=> x.CurrentVersionedItem.SomeNumber == 5)
While the IQueryable provider of NHibernate is forgiving, I will not eat everything. Is there a way to define a (fluent) mapping that resolves the "CurrentVersionedItem" property properly?
I'm also aware of the fact CurrentVersionedItem could potentially return null in this scenario (if it worked in the first place).
Why won't you do like this:
var item = session.Query<VersionedItem>().FirstOrDefault(q => q.SomeNumber == 5);
Blog blog;
if (item != null)
blog = item.Blog;

How to map a related table with no primary key with fluent-NHibernate

Looks a common situation to me: I have two tables:
documents:
dID (pk, int), dName(varchar)
and document_options:
dID (int), oType(int), oValue(varchar)
I would like to have a class Document with a property Options (a List of DocumentOption class)
Since document_options has no PK I cannot use HasMany, and rows from this table don't seem like 'real' entities anyway...
I see a way to generate an auto-number key for document options and map with HasMany, or maybe create a composite ID, but I'd like to know if there is a better option that I don't know about.
In this case, DocumentOptions is a value object, since it has no identity of its own and has no meaning outside of the document it belongs to. So, you would use Component to map the collection properties to the value object.
public class Document : Entity // don't worry about Entity; it's a base type I created that contains the Id property
{
public virtual string Name { get; set; }
public virtual IList<DocumentOptions> Options { get; protected set; }
public Document()
{
Options = new List<DocumentOptions>();
}
}
public class DocumentOptions
{
public virtual int Type { get; set; }
public virtual string Value { get; set; }
}
And the mapping:
public DocumentMap()
{
Table("documents");
Id(c => c.Id)
.Column("dId")
.GeneratedBy.HiLo("10");
Map(c => c.Name)
.Column("dName");
HasMany(c => c.Options)
.Component(c =>
{
c.Map(c2 => c2.Value).Column("oValue");
c.Map(c2 => c2.Type).Column("oType");
})
.Table("document_options")
.KeyColumn("dId")
.Cascade.AllDeleteOrphan();
}
If I understand correctly I had to map options as a list of components:
HasMany(x => x.DocumentOptions)
.Table("document_options")
.KeyColumn("dID")
.Component(c => {
c.Map(x => x.Option, "oID");
c.Map(x => x.Value, "oValue");
})
.Fetch.Subselect(); //This type of join isn't strictly needed, is used for SQL optimization
classes FYI:
public class Options {
public virtual int Option { get; set; }
public virtual int Value { get; set; }
}
public class Document {
public virtual int ID { get; set; }
public virtual String Name { get; set; }
public virtual IList<DocumentOption> DocumentOptions { get; set; }
}

Categories

Resources