Entity splitting with EF Code First issue - c#

So I am trying to achieve entity splitting in EF 6.1 with Code First, and I am running into an error.
I have the following tables:
CREATE TABLE [dbo].[Organization]
(
[OrganizationId] INT NOT NULL PRIMARY KEY IDENTITY,
[TenantId] INT NOT NULL,
[Name] NVARCHAR(80) NOT NULL
)
CREATE TABLE [dbo].[OrganizationSettings]
(
[OrganizationSettingsId] INT NOT NULL PRIMARY KEY IDENTITY,
[OrganizationId] INT NOT NULL,
[AllowMultipleTimers] BIT NOT NULL,
CONSTRAINT [FK_OrganizationSettings_Organization] FOREIGN KEY (OrganizationId) REFERENCES Organization(OrganizationId)
)
With the following model objects:
public partial class Organization
{
public int OrganizationId { get; set; }
public int TenantId { get; set; }
public string Name { get; set; }
public OrganizationSettings Settings { get; set; }
}
public class OrganizationSettings
{
public int OrganizationSettingsId { get; set; }
public int OrganizationId { get; set; }
public bool AllowMultipleTimers { get; set; }
}
With the following config code:
var org = modelBuilder.Entity<Organization>();
org.Map(u =>
{
u.Properties(m => new { m.TenantId, m.Name });
})
.ToTable("Organization");
org.Map(u =>
{
u.Property(m => m.Settings.AllowMultipleTimers).HasColumnName("AllowMultipleTimers");
u.ToTable("OrganizationSettings");
});
Then just the following query:
context.Organizations.FirstOrDefault();
Which yields the following error:
The property 'Settings.AllowMultipleTimers' on type 'Organization'
cannot be mapped because it has been explicitly excluded from the
model or it is of a type not supported by the DbModelBuilderVersion
being used.
What am I doing wrong here?
Update: I forgot to mention that I created the database by hand, and am using the CF fluent API to map my models, rather than using "real" Code First.

While I was pretty sure I had this mapping working before, I went ahead and went a little different route.
First I got rid of the surrogate key on `OrganizationSettings (probably not strictly necessary), and then mapped it as an entity with a 1:1 relationship.
My OrganizationSettings is now:
public class OrganizationSettings
{
public int OrganizationId { get; set; }
public bool AllowMultipleTimers { get; set; }
}
OrganizationId is both a primary key and a foreign key.
And the config is:
var org = modelBuilder.Entity<Organization>()
.Map(u =>
{
u.Properties(m => new { m.TenantId, m.Name });
})
.HasRequired(m => m.Settings)
.WithRequiredPrincipal();
modelBuilder.Entity<OrganizationSettings>()
.HasKey(m => m.OrganizationId);
And this seems to work just fine. Since I'm not exposing a DbSet for OrganizationSettings it keeps the conceptual modeling of OrganizationSettings as a value object intact.

Were you trying to set up OrganizationSettings as a complex type while using entity splitting as well? Something like this, perhaps:
public partial class Organization
{
public int OrganizationId { get; set; }
public int TenantId { get; set; }
public string Name { get; set; }
public OrganizationSettings Settings { get; set; }
}
public class OrganizationSettings
{
public bool AllowMultipleTimers { get; set; }
}
// if you don't have a key defined on OrganizationSettings, this might not be needed
modelBuilder.ComplexType<OrganizationSettings>();
modelBuilder.Entity<Organization>()
.Map(u =>
{
u.Properties(m => new { m.OrganizationId, m.TenantId, m.Name });
u.ToTable("Organization");
})
.Map(u =>
{
u.Properties(m => new { m.OrganizationId, m.Settings.AllowMultipleTimers });
u.ToTable("OrganizationSettings");
// If you wanted to set the key column name
u.Property(m => m.OrganizationId).HasColumnName("OrganizationSettingsId");
});

Related

How to join two table in C# web API

I am new at C# entity framework. I am trying to build an API, but stuck in retrieving data from relational table.
I have a pei_crops table in MS SQL database, where c_id is the primary key. I have another table called pei_pests, where p_id is the primary key. Another table is pei_cropspests where I have built relation for which pest attack which crop. Multiple pests can attack one crop and one pest can attack multiple crops. In this pei_cropspests table I have put p_id as primary and foreign key and c_id as primary and foreign key as well.
pei_crops table:
c_id
c_name
c_description
1
Corn
NULL
pei_pests table:
p_id
p_name
p_URL
1
pest1
NULL
2
pest2
NULL
pei_cropspests table:
p_id
c_id
1
1
2
1
Now In my API I want to show something like that
[
{
"cId":1,
"pests":[
{
"pId":1,
"pName": pest1,
"pURL": null
},
{
"pId":2,
"pName": pest2,
"pURL": null
}
]
}
]
My get request looks like this so far in C# web API project:
[Route("Getspecific/{cropId}")]
[HttpGet]
public async Task<IActionResult> GetSpecific(int cropId)
{
var cropDetails = await _db.PeiCrops.Where(c=>c.CId == cropId).Include(i=>i.PeiCropspests).ToListAsync();
return Ok(cropDetails);
}
This code returns me only the pID and URL of the pest that effects cID number 1. But I also want the pest name and URL along with their id.
Could someone please show me how to do it. Maybe there is some way to join two table and show the data? I just do not know how to do it in C#. Any help appreciated. Thank you.
Entities class:
PeiCrop:
using System;
using System.Collections.Generic;
#nullable disable
namespace PEI_API.EF
{
public partial class PeiCrop
{
public PeiCrop()
{
PeiCropimages = new HashSet<PeiCropimage>();
PeiCropsdiseases = new HashSet<PeiCropsdisease>();
PeiCropspests = new HashSet<PeiCropspest>();
}
public int CId { get; set; }
public string CName { get; set; }
public string CPhotoUrl { get; set; }
public string CDescription { get; set; }
public virtual ICollection<PeiCropimage> PeiCropimages { get; set; }
public virtual ICollection<PeiCropsdisease> PeiCropsdiseases { get; set; }
public virtual ICollection<PeiCropspest> PeiCropspests { get; set; }
}
}
PeiPest:
using System;
using System.Collections.Generic;
#nullable disable
namespace PEI_API.EF
{
public partial class PeiPest
{
public PeiPest()
{
PeiCropspests = new HashSet<PeiCropspest>();
PeiPestimages = new HashSet<PeiPestimage>();
}
public int PId { get; set; }
public string PName { get; set; }
public string PPhotoUrl { get; set; }
public string PDescription { get; set; }
public virtual ICollection<PeiCropspest> PeiCropspests { get; set; }
public virtual ICollection<PeiPestimage> PeiPestimages { get; set; }
}
}
PeiCropspest:
using System.Collections.Generic;
#nullable disable
namespace PEI_API.EF
{
public partial class PeiCropspest
{
public int PId { get; set; }
public int CId { get; set; }
public virtual PeiCrop CIdNavigation { get; set; }
public virtual PeiPest PIdNavigation { get; set; }
}
}
You're pretty close, but you're also not entirely using EF like you could, I mean you do not actually have to make the relationship table yourself but could refer directly to a list of the entity pei_pests from the entity pei_crop and let EF create the other.
//Example just getting one property from each,
//but you can new a composite return type up if you wish, using select
var cropDetails = await _db.PeiCrops
.Where(c=>c.CId == cropId)
.Include(i=>i.PeiCropspests)
.ThenInclucde(t => t.Pests)
.Select(s => new { CropId = s.p_id, PestName = s.PeiCropsPests.Pest.p_name })
.ToListAsync();
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=net-5.0
First, you need to configure the relationships :
class MyContext : DbContext
{
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PeiCropspest>()
.HasKey(cp => new { cp.PId, cp.CId });
//Configure one PeiPest to many PeiCropspest
modelBuilder.Entity<PeiCropspest>()
// Specify PeiCropspest's navigation property to one PeiPest
.HasOne(cp => cp.PIdNavigation)
// Specify PeiPest's navigaton property to many PeiCropspest
.WithMany(p => p.PeiCropspests)
// Specify PeiCropspest's navigation property
// to use this PeiCropspest's property as foreign key
.HasForeignKey(cp => cp.PId);
//Configure one PeiCrop to many PeiCropspest
modelBuilder.Entity<PeiCropspest>()
// Specify PeiCropspest's navigation shadow property to one PeiCrop
.HasOne<PeiCrop>()
// Specify PeiCrop's navigaton property to many PeiCropspest
.WithMany(c => c.PeiCropspests)
// Specify PeiCropspest's navigation shadow property
// to use this PeiCropspest's property as foreign key
.HasForeignKey(cp => cp.CId);
}
public DbSet<PeiCrop> PeiCrops { get; set; }
}
Then you can do a projection in the LINQ query :
public async Task<IActionResult> GetSpecific(int cropId)
{
var cropDetails = await _db.PeiCrops
.Where(c=>c.CId == cropId)
.Select(c => new {
cId = c.CId,
pests = c.PeiCropspests.Select(p => new {
pId = p.PIdNavigation.PId,
pName = p.PIdNavigation.PName,
pUrl = p.PIdNavigation.PPhotoUrl
})
})
.ToListAsync();
return Ok(cropDetails);
}
Do you know? From EF Core 5, it's possible to do many to many relationship without intermediary entity. This can simplify your entity model. cf. the documentation

Entity Framework Core (Postgres) Multiple Includes creates ghost property

I'm having an issue with a series of Include/ThenInclude in a query.
Here is my EntityFrameworkCore Query :
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions.Where(compo => compo.Date == compositionDate))
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(token)
?? throw new NotFoundException(nameof(Fund), fundId);
I recieve a 'CompositionDate does not exists' error.
As you can see the CompositionDate property is at the Compositions Level.
When I check the SQL generated I get this in a subquery Select statement :
SELECT f1."CompositionFundId", f1."CompositionDate", f1."AssetId", f1."Amount", a."Id", a."CountryCode", a."Currency", a."FundCompositionDate", a."FundCompositionFundId", a."Isin", a."Name", a."SecurityType", a."Ticker", a."Coupon", a."GicsSector", a."InvestmentCase", a."IpoDate", a."Theme"
FROM "FundCompositionItem" AS f1
INNER JOIN "Asset" AS a ON f1."AssetId" = a."Id"
Those 2 properties a."FundCompositionDate", a."FundCompositionFundId" doesn't exists at the 'Asset' level.
They exists in the parent (at the 'Where' level on the first Include).
I'm using Postgres provider for EFcore. Could this be the issue?
Should I be using the select anonymous type .Select(x => new { Fund = x, Compo = x.Compo.Where(...), etc... }?
I would like to preserve the navigation properties if possible. (accessing assets from compositionItems)
Any help would be much appreciated.
Edit:
Models as requested by Atiyar:
public class Portfolio : AuditableEntity
{
public Guid Id { get; set; }
public Guid Name{ get; set; }
}
public class Fund : Portfolio
{
// Irrelevant properties
public IList<FundComposition> Compositions { get; } = new List<FundComposition>();
}
public class FundComposition
{
public Fund Fund { get; set; }
// Primary key / Foreign key
public Guid FundId { get; set; }
// Primary Key
public DateTime Date { get; set; }
public List<FundCompositionItem> CompositionItems { get; set; } = new();
}
public class FundCompositionItem
{
public FundComposition Composition { get; set; }
// Primary Key
public Guid CompositionFundId { get; set; }
// Primary Key
public DateTime CompositionDate { get; set; }
public Asset Asset { get; set; }
// Primary Key
public Guid AssetId { get; set; }
public double Amount { get; set; }
}
public class Asset : BaseEntity
{
// Primary Key
public Guid Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
// more properties
}
In my experience, I've applied the Include() and ThenInclude() first and then applied the any conditional clauses afterwards. I'm also not sure if using Where inside of an include method does what you expect it to.
You can also apply your conditional in the first parameter of .FirstOrDefaultAsync().
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions)
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(x =>
x.Id == fundId && x.Compositions.Any(compo => compo.Date == compositionDate),
token
)

ef core: mapping value object using OwnsMany requires primary key to be defined

I have a class
public class Document
{
public string TranId { get; set; }
public Record Record { get; set; }
public List<Error> Errors { get; set; }
}
public class Record
{
public string TranId { get; set; }
public List<DataResult> DataResults { get; set; }
}
public class DataResult
{
public string DataSourceName { get; set; }
public List<DataField> DataFields { get; set; }
public List<CustomField> CustomFields { get; set; }
}
I want to map Record and DataResult classes as Value objects so I tried to map as
public void Configure(EntityTypeBuilder<Document> builder)
{
builder.ToTable("Document");
builder.HasKey(x => x.TranId);
builder.OwnsOne(a => a.Record, a =>
{
a.ToTable("Doc_Record");
a.Property(p => p.TranId).HasMaxLength(100)
.HasColumnName("TranID")
.HasDefaultValue("");
a.OwnsMany(x => x.DataResults, x =>
{
x.ToTable("Doc_Rec_DataResults");
x.Property(p => p.DataSourceName).HasMaxLength(150)
.HasColumnName("DataSourceName")
.HasDefaultValue("");
});
}
}
When I try to add this migration it errors with a message:
The entity type 'DataResult' requires a primary key to be defined.
And why it requires primary key cause I'm trying to map as a value
object?
Someone suggested using this link and I try to add
a.OwnsMany(x => x.DataResults, x =>
{
x.WithOwner().HasForeignKey("RecordId");
x.ToTable("Doc_Rec_DataResults");
x.Property(p => p.DataSourceName).HasMaxLength(150)
.HasColumnName("DataSourceName")
.HasDefaultValue("");
});
but this approach is not working cause WithOwner is available from .net core 3 where I'm using .net core 2 (and do I really need to add RecordId property into Record class (it's a value object).
It would be great if someone can provide an example of how to map collection of value objects with OwnsMany in EF Core 2.
In this ef core 2.2 example, we have a Company that owns a collection of Addresses, here's the implementation. note that i omitted some useful code to stick to the point, refer to the full example for further explanation. Also note that this feature OwnsMany() is not available in pre ef core 2.2
public class CompanyAddress
{
public string City { get; }
public string AddressLine1 { get; }
}
public class Company
{
private List<CompanyAddress> addresses = new List<CompanyAddress>();
public Guid Id { get; }
public string Name { get; }
public IEnumerable<CompanyAddress> Addresses { get => this.addresses; }
public void AssignAddress(CompanyAddress address)
{
var exists = this.addresses.Contains(address);
if (!exists)
{
this.addresses.Add(address);
}
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>().OwnsMany<CompanyAddress>("Addresses", a =>
{
a.HasForeignKey("CompanyId");
a.Property(ca => ca.City);
a.Property(ca => ca.AddressLine1);
a.HasKey("CompanyId", "City", "AddressLine1");
});
}
here's a repo of full solution for the article's owner

Nhibernate ComposedId on intermediate table

I want to have a intermediate table with only two foreign keys (as a ComposedId).
But NHibernate is automatically creating a "id" property.
I have the following classes
public class Lace
{
public virtual int Id { get; set; }
public virtual string Hostname { get; set; }
public virtual IList<LaceHasCard> LaceHasCards { get; set; }
}
public class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<LaceHasCard> LaceHasCards { get; set; }
}
and this manually created intermediate table
public class LaceHasCard
{
public virtual Card Card { get; set; }
public virtual Lace Lace { get; set; }
}
Mappings
public LaceMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Native));
Property(x => x.Hostname);
Bag(x => x.LaceHasCards, col =>
{
col.Key(k => k.Column("LaceId"));
col.Inverse(true);
}, r => r.OneToMany());
}
public CardMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Native));
Property(x => x.Name);
Bag(x => x.LaceHasCards, col =>
{
col.Key(k => k.Column("CardId"));
col.Inverse(true);
}, r => r.OneToMany());
}
intermediate table mapping
public LaceHasCardMapping()
{
//ComposedId(map =>
//{
// map.Property(x => x.Card.Id, a =>
// {
// a.Column("CardId");
// });
// map.Property(x => x.Lace.Id, a =>
// {
// a.Column("LaceId");
// });
//});
ManyToOne(x => x.Card, map =>
{
map.Column("CardId");
});
ManyToOne(x => x.Lace, map =>
{
map.Column("LaceId");
});
}
If I create the schema with the ComposedId commented out, NHibernate will create a "id" property in the table.
CREATE TABLE [dbo].[LaceHasCard] (
[id] INT NOT NULL,
[CardId] INT NULL,
[LaceId] INT NULL,
PRIMARY KEY CLUSTERED ([id] ASC),
CONSTRAINT [FKDC6D54711CD160AE] FOREIGN KEY ([CardId]) REFERENCES [dbo].[Card] ([Id]),
CONSTRAINT [FKDC6D547151F8AF85] FOREIGN KEY ([LaceId]) REFERENCES [dbo].[Lace] ([Id])
);
If I try to create the schema with the ComposedId, I get the following error message:
Unable to instantiate mapping class (see InnerException):
EmpLaceMgmt.Models.Mappings.LaceHasCardMapping
What would be the right way to tell NHibernate to create a composed Id?
Let me give you suggestion, just my point of view - do not use composite id. Use standard primary key in DB and its C# / entity representation as Id { get; set; }
Chapter 24. Best Practices
...
Declare identifier properties on persistent classes.
NHibernate makes identifier properties optional. There are all sorts of reasons why you should use them. We recommend that identifiers be 'synthetic' (generated, with no business meaning) and of a non-primitive type. For maximum flexibility, use Int64 or String.
See also more about synthetic, surrogate keys at wiki.
From my experience, we should not be worry about having pairing object like this:
public class LaceHasCard
{
public virtual int Id { get; set; } // the key
public virtual Card Card { get; set; }
public virtual Lace Lace { get; set; }
}
Because later it would become so easy to access it:
session.Get<LaceHasCard>(id)
And also to use it in Subqueries (for filtering Card with Laces and vice versa)
One column in DB, autogenerated, should not have any extra bad impact. But handling such table is a bit (a lot) easier...
So, summary, my suggestion would be, make all entities first level citizens, with full rights (including synthetic/surrogate key)

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