An Entity with identical table data - c#

Before I elaborate the problem, I'm well aware the database isn't designed conventionally. Sadly, I can't change this particular database due to how it is integrated, so I've got a potential solution but that won't be implemented for several months. In the mean time I need to work around the following:
The problem is I need to build an Entity, this would represent our Accounts. But the problem, our database implements the following structure:
Invoiced Table
Non-Invoiced Table
My Entity, represents the exact same data on those tables, same column names, duplicate under all conditions, except one is invoiced while the other represents non-invoiced customers. But since it isn't one table, with a Flag to indicate invoiced versus non-invoiced, how can my Entity link to both of those tables?
Since both tables represent separate names, I can't use the [Table("...")] or the auto mapping capabilities. I hate asking such a question, but I can't find any documentation on how to handle such an issue.

You could use table-per-concrete class inheritance then define the table names on the derived types:
public abstract class Account
{
// common entity code here
...
}
public class InvoicedAccount : Account {}
public class NonInvoicedAccount: Account {}
public YourContext : DbContext
{
public DbSet<InvoicedAccount> InvoicedAccounts { get; set; }
public DbSet<NonInvoicedAccount> NonInvoicedAccounts { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<InvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "InvoicedAccountTable" );
} );
modelBuilder.Entity<NonInvoicedAccounts>().Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "NonInvoicedAccountTable" );
} );
}
}

Related

Inheriting properties from model - Navigation properties can only participate in a single relationship

I'm trying to share common properties with multiple entities by using multiple levels of inheritance, but I'm running into an error.
Cannot create a relationship between 'User.SupersCreated' and 'Super.CreatedBy' because a relationship already exists between 'User.BasicsCreated' and 'Basic.CreatedBy'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'Super.CreatedBy' first in 'OnModelCreating'.
The structure of my models is as follows.
public class EntityBase
{
public Guid Id { get; set; }
public Guid CreatedById { get; set; }
public User CreatedBy { get; set; }
}
public class Basic: EntityBase
{
public string BasicProperty { get; set; }
}
public class Super : Basic
{
public string SuperProperty { get; set; }
}
public class User : IdentityUser<Guid>
{
public ICollection<Basic> BasicsCreated { get; set; }
public ICollection<Super> SupersCreated { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasMany(x => x.BasicsCreated)
.WithOne(x => x.CreatedBy);
modelBuilder.Entity<User>()
.HasMany(x => x.SupersCreated)
.WithOne(x => x.CreatedBy);
}
The problem seems to be a result of Super Inheriting from Basic, or at least, the problem goes away when I remove this level of inheritance and make Super inherit from EntityBase (however than I'll lose the properties that exist in Basic).
Can anyone please help me understand why I'm getting this error and what should be done to fix it?
Edit
After considering this some more, I think I'm trying to abuse inheritance to do what it's not intended to do.
The database structure I was hoping to end up with, is:
Even though my Basic and Super tables share the same properties, with Super having it's own additional properties, there's no relationship between Basic data and Super data.
From having a look at Microsoft's tutorial on implementing inheritance, there's two options:
Table per type
Table per hierarchy
Neither of these are what I'm trying to achieve.
Perhaps I should be using interfaces to define the common properties that exist between unrelated entities. It seems like I need to back and re-evaluate my design anyway.
If some of the base classes of the entity is identified as entity (as with your Super and Basic), by default EF Core will try to use one of the database inheritance strategies.
If you don't want that (want to treat is just like non entity base class), then you have to configure that explicitly at the very beginning of the OnModelCreating, e.g. for your sample
modelBuilder.Entity<Super>().HasBaseType((Type)null);
or more generally using a loop similar to this
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
entityType.BaseType = null;
and then define explicitly the entity hierarchy if and where needed.

How to add the same column to all entities in EF Core?

Imagine that I want to add an IsDeleted colum or some auditing columns to all of my entities. I could create a base class from which all of my entities will inherit and this will solve my problem, however I cannot specify the order in which the column will be created so I will end up with all the auditing fields before the fields of my entity, which I do not want. I want them to be at the end of the table.
In the standard version of entity framework we can do this by using annotations that specify the order of the columns. However, such a thing does not exist for EF core at the moment.
I could do it with the fluent api on the OnModelCreating() method, the problem is that I only know how to do it individually for each of my entities, which means I would have to write the same code for every entity I have.
Is there any way I can do it generically for all of my entities? Some sort of for loop that iterates through all the entities registered in the DbSets on my dbcontext?
Your question title is about adding the same properties to multiple entities. However, you actually know how to achieve this (use a base type) and your actual question is how to ensure that these properties come last in the generated tables' columns.
Although column order shouldn't really matter nowadays, I'll show an alternative that you may like better than a base type and also positions the common properties at the end of the table. It makes use of shadow properties:
Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model.
Most of the times, auditing properties don't need much visibility in the application, so I think shadow properties is exactly what you need. Here's an example:
I have two classes:
public class Planet
{
public Planet()
{
Moons = new HashSet<Moon>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Moon> Moons { get; set; }
}
public class Moon
{
public int ID { get; set; }
public int PlanetID { get; set; }
public string Name { get; set; }
public Planet Planet { get; set; }
}
As you see: they don't have auditing properties, they're nicely mean and lean POCOs. (By the way, for convenience I lump IsDeleted together with "audit properties", although it isn't one and it may require another approach).
And maybe that's the main message here: the class model isn't bothered with auditing concerns (single responsibility), it's all EF's business.
The audit properties are added as shadow properties. Since we want to do that for each entity we define a base IEntityTypeConfiguration:
public abstract class BaseEntityTypeConfiguration<T> : IEntityTypeConfiguration<T>
where T : class
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property<bool>("IsDeleted")
.IsRequired()
.HasDefaultValue(false);
builder.Property<DateTime>("InsertDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
builder.Property<DateTime>("UpdateDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
}
}
The concrete configurations are derived from this base class:
public class PlanetConfig : BaseEntityTypeConfiguration<Planet>
{
public override void Configure(EntityTypeBuilder<Planet> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
// Follows the default convention but added to make a difference :)
builder.HasMany(p => p.Moons)
.WithOne(m => m.Planet)
.IsRequired()
.HasForeignKey(m => m.PlanetID);
base.Configure(builder);
}
}
public class MoonConfig : BaseEntityTypeConfiguration<Moon>
{
public override void Configure(EntityTypeBuilder<Moon> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
base.Configure(builder);
}
}
These should be added to the context's model in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PlanetConfig());
modelBuilder.ApplyConfiguration(new MoonConfig());
}
This will generate database tables having columns InsertDateTime, IsDeleted and UpdateDateTime at the end (independent of when base.Configure(builder) is called, BTW), albeit in that order (alphabetical). I guess that's close enough.
To make the picture complete, here's how to set the values fully automatically in a SaveChanges override:
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries()
.Where(e => e.Properties.Any(p => p.Metadata.Name == "UpdateDateTime")
&& e.State != Microsoft.EntityFrameworkCore.EntityState.Added))
{
entry.Property("UpdateDateTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
}
Small detail: I make sure that when an entity is inserted the database defaults set both fields (see above: ValueGeneratedOnAdd(), and hence the exclusion of added entities) so there won't be confusing differences caused by client clocks being slightly off. I assume that updating will always be well later.
And to set IsDeleted you could add this method to the context:
public void MarkForDelete<T>(T entity)
where T : class
{
var entry = this.Entry(entity);
// TODO: check entry.State
if(entry.Properties.Any(p => p.Metadata.Name == "IsDeleted"))
{
entry.Property("IsDeleted").CurrentValue = true;
}
else
{
entry.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
}
}
...or turn to one of the proposed mechanisms out there to convert EntityState.Deleted to IsDeleted = true.
You can always generate an initial migration for the model and manually rearrange the column order in the Migration.
Here is the open issue tracking support for explicit column ordering in EF Core: https://github.com/aspnet/EntityFrameworkCore/issues/10059
Also see this question and answer on using Shadow Properties and Query Filters for soft deletes. EF Core: Soft delete with shadow properties and query filters

Entity Framework 6 DBContext with only a subset of all tables

We have a huge database with 770 tables and want to do some performance testing with EF 6.1x.
We want to query only 5 of those 770 tables. Is it possible to create a "light" DBContext with only 5-6 entities/DBSets instead of using the full 770-tables-context?
When we use the full context, a simple query with 4 joins takes 45 seconds. Thats' 44 seconds too long.
We are using code-first (reverse engineered).
The problem:
When we create such a "light" version of the full context (i.e. 5 tables only), EF complains that all the other entities that are somehow related to these 5 tables have missing keys. We only map the keys, properties, relationships for those 5 tables, but not the rest.
Since the query written in LINQ only queries 5 tables, EF should simply ignore the other 765 tables, but it won't.
Why not? LazyLoading=true/false doesn't seem to have any bearing on this.
Note: Obviously one could create a view in the DB that does what we do in code with a LINQ query. The question is can it be done with a "light" DbContext as above.
There's the "light" version of the context:
public class ItemLookupContext : DbContext
{
static ItemLookupContext()
{
Database.SetInitializer<ItemLookupContext>( null );
}
public ItemLookupContext()
: base( "Name=ItemLookupContext" )
{
//Configuration.LazyLoadingEnabled = true;
}
public DbSet<Identity> Identities { get; set; }
public DbSet<Item> Items { get; set; }
public DbSet<Price> Prices { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Brand> Brands { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Configurations.Add( new IdentityMap() );
modelBuilder.Configurations.Add( new ItemMap() );
modelBuilder.Configurations.Add( new PriceMap() );
modelBuilder.Configurations.Add( new DepartmentMap() );
modelBuilder.Configurations.Add( new BrandMap() );
//ignore certain entitities to speed up loading?
//does not work
modelBuilder.Ignore<...>();
modelBuilder.Ignore<...>();
modelBuilder.Ignore<...>();
modelBuilder.Ignore<...>();
modelBuilder.Ignore<...>();
}
}
what you trying to something like "Bounded Context" which is one of DDD patterns
So, you can check this article by Julie Lerman, Shrink EF Models with DDD Bounded Contexts
Simply just create your DBContext for your tables. To prevent Entity Framework moaning about the not mapped tables, you have switch off the db initialization in your application. Put this in your global.asax/Startup.cs
Database.SetInitializer<YourDbContext>(null);
It tells EF to stop comparing your actual DB structure against your DbContext.
It also means that if someone changes your EF mapped tables, you have no chance of getting notified about that.
When you have a many-to-one relation between class A and class B:
public class A
{
public B b {get; set;}
}
public class B
{
public ICollection<A> As {get; set;}
}
and define following DbContext, EF automatically includes DbSet<B> to the DbContext:
public class MyContext : DbContext
{
...
public DbSet<A> As { get; set; }
}
So, if you want your light DbContext does not includes the related DbSets, simply use Ignore method:
public class MyContext : DbContext
{
...
public DbSet<A> As { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Ignore<B>();
}
}
It looks like you used a tool like Entity Framework Power Tools to generate the entity classes and mappings. This would have generated a class for each table in the database, a huge context, mappings for all these classes and all possible associations. This is way too much.
First remove all classes and mappings that you don't need. Then remove all associations to removed classes in the few classes you have left, not the primitive foreign key fields. Also remove all DbSets from the context except the few you need.
This slimmed-down class model will be consistent in itself. It won't have associations to all entities in the database, but it will be possible to filter by foreign key values that refer to entities outside the context.
If you generated/created the code in any other way this is still the crux: only use navigation properties to other classes in the class model. For other references use primitive foreign key properties.

Table Per Class Inheritance with FluentNHibernate - Identity generation

I have a set of payment objects that come from different sources that I'd like to use an TPC inheritance in NHibernate for. The base for all these object is the payment processor (so all have a consistent format), however there is no representation of the base class in the database. I think I have my mapping worked out, except for the fact that I get an exception thrown when trying to insert - "Cannot use identity column key generation with <union-subclass> mapping for: <EntityName>"
Classes:
public class BasePayment
{
public virtual int Id { get; set; }
public virtual PaymentSource Source { get; set; }
public virtual DateTime Date { get; set; }
public virtual decimal Amount { get; set; }
}
public class SubPayment : BasePayment
{
}
Mappings:
public class BasePaymentMap : ClassMap<BasePayment>
{
public BasePaymentMap()
{
UseUnionSubclassForInheritanceMapping();
DiscriminateSubClassesOnColumn("Source");
Id(m => m.Id);
Map(m => m.Source);
Map(m => m.Amount);
Map(m => m.Date);
}
}
public class SubPaymentMap : SubclassMap<SubPayment>
{
public SubPaymentMap()
{
DiscriminatorValue(PaymentSource.SourceX);
Abstract();
Table("SourceXPayments");
}
}
And that's as far as I've got. On a SaveOrUpdate, I'm getting the error above, and a google search is not proving helpful thus far - I have found this question: Cannot use identity column key generation with <union-subclass> ( TABLE_PER_CLASS ) which appears to cover the issue in Java/Hibernate, but I'm having a problem with converting the #GeneratedValue(strategy = GenerationType.TABLE) into a Fluent syntax, as Fluent NHibernate's GeneratedBy() method doesn't seem to have a table, or a lot of documentation (that I have found) as to what it does under the covers.
Doing some more reading around it seems that this might not be possible, so if anyone can confirm this or offer a work around for the situation, it'd be greatly appreciated.
If I've not given enough detail, please let me know what would be more use.
Thanks in advance
UseUnionSubclassForInheritanceMapping();
DiscriminateSubClassesOnColumn("Source");
is conflicting and FNH will ignore one.
UseUnionSubclassForInheritanceMapping means each class has it's own table with all columns and the table is used to discriminate the class, no column needed
DiscriminateSubClassesOnColumn means each class of the inheritance hierarchy live in the same table and a column is used to discriminate the classes.
the error message means that when using UseUnionSubclassForInheritanceMapping identity generation is not allowed since the id's would be unique for one table only but NHibernate threats all subclasses as one set where the id must be unique.
e.g. session.Get<BaseClass>(5); won't work relyably anymore since there could be more than one subclass with the same id.
#GeneratedValue(strategy = GenerationType.TABLE) just tells "use another strategy than identity". GeneratedBy.HiLow(); would be a good alternative to identity.

Mapping references to companion objects with fluent-nhibernate

I've got the following basic domain model for my MVC website accounts:
public class Account
{
public Account()
{
Details = new AccountDetails( this );
Logon = new LogonDetails(this);
}
public virtual int Id { get; private set; }
public virtual AccountDetails Details { get; set; }
public virtual LogonDetails Logon { get; set; }
...
}
public class AccountDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime Created { get; set; }
...
}
public class LogonDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime? LastLogon { get; set; }
...
}
Both AccountDetails and LogonDetails use a mapping like this:
public class AccountDetailsOverride : IAutoMappingOverride<AccountDetails>
{
public void Override( AutoMap<AccountDetails> mapping )
{
mapping
.UseCompositeId()
.WithKeyReference( x => x.Account, "AccountId" );
mapping.IgnoreProperty( x => x.Account );
}
}
I've split the account details and logon details into separate models since I rarely need that information, whereas I need the userid and name for many site operations and authorization. I want the Details and Logon properties to be lazy-loaded only when needed. With my current mapping attempts I can get one of two behaviors:
# 1 Create table and load successfully, cannot save
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.WithColumns( x => x.Account.Id )
.Cascade.All();
mapping
.References( x => x.Logon )
.WithColumns( x => x.Account.Id )
.Cascade.All();
}
}
The tables are generated as expected. Existing data loads correctly into the model, but I can't save. Instead I get an index out of range exception. Presumably because Account.Details and Account.Logon are both trying to use the same db field for their reference (The Account.Id itself).
#2 Table includes extra fields, does not save properly
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.Cascade.All();
mapping
.References( x => x.Logon )
.Cascade.All();
}
}
I get a table with a separate field for Details_id and Logon_id but they are null since the value of Details.Account.Id is null when the Account is persisted. So, attempting to Session.Get the account results in Details and Logon being null. If I save Account twice, the table is updated correctly and I can load it.
Help...
There must be a way of mapping this hierarchy and I'm missing something simple. Is there a way to help nhibernate pick the proper field (to solve #1) or to have it update dependent fields automatically after save (to solve#2)?
Thanks for any insight you folks can provide.
If I'm understanding your model and desired behavior, what you have is actually a one-to-one relationship between Account and AccountDetails and between Account and LogonDetails. References creates a many-to-one relationship, so that could be your problem; try HasOne instead.
That said, for this and other reasons, I avoid one-to-ones unless absolutely necessary. There may be more than what you're showing, but is it worth the headache and ugly model to avoid loading two DateTime fields?
Finally, and this is somewhat speculation since I have not tested this functionality, NHibernate 2.1's (which FNH has switched to as supported version) mapping XML schema defines a lazy attribute for property elements. The 1.0 release of FNH (should be in the next week or two) will support setting this attribute. As I said, I have not tested it but it would seem that this would allow you to lazy load individual properties which is exactly what you want.

Categories