Entity Framework: Split table into multiple tables - c#

I have the following table [PaymentComponent] created using following EF code first approach (TPH inheritance). It works fine. I need to change the database design – need to store GiftCouponPayments in GiftCouponPayment table and ClubCardPayments in ClubCardPayment table. What change need to be done in C# code to get the required database structure?
CODE
public abstract class PaymentComponent
{
public int PaymentComponentID { get; set; }
public int MyValue { get; set; }
public string MyType { get; set; }
public abstract int GetEffectiveValue();
}
public partial class GiftCouponPayment : PaymentComponent
{
public override int GetEffectiveValue()
{
if (MyValue < 2000)
{
return 0;
}
return MyValue;
}
}
public partial class ClubCardPayment : PaymentComponent
{
public override int GetEffectiveValue()
{
return MyValue;
}
}
public partial class Payment
{
public int PaymentID { get; set; }
public List<PaymentComponent> PaymentComponents { get; set; }
public DateTime PayedTime { get; set; }
}
//System.Data.Entity.DbContext is from EntityFramework.dll
public class NerdDinners : System.Data.Entity.DbContext
{
public NerdDinners(string connString): base(connString)
{
}
protected override void OnModelCreating(DbModelBuilder modelbuilder)
{
modelbuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<GiftCouponPayment> GiftCouponPayments { get; set; }
public DbSet<ClubCardPayment> ClubCardPayments { get; set; }
public DbSet<Payment> Payments { get; set; }
}
CLIENT
static void Main(string[] args)
{
string connectionstring = "Data Source=.;Initial Catalog=NerdDinners;Integrated Security=True;Connect Timeout=30";
using (var db = new NerdDinners(connectionstring))
{
GiftCouponPayment giftCouponPayment = new GiftCouponPayment();
giftCouponPayment.MyValue=250;
giftCouponPayment.MyType = "GiftCouponPayment";
ClubCardPayment clubCardPayment = new ClubCardPayment();
clubCardPayment.MyValue = 5000;
clubCardPayment.MyType = "ClubCardPayment";
List<PaymentComponent> comps = new List<PaymentComponent>();
comps.Add(giftCouponPayment);
comps.Add(clubCardPayment);
var payment = new Payment { PaymentComponents = comps, PayedTime=DateTime.Now };
db.Payments.Add(payment);
int recordsAffected = db.SaveChanges();
}
}
REFERENCE:
How do I get Entity Framework 4.3 Code First to map a subclass using Table Per Type (TPT)?
http://weblogs.asp.net/manavi/archive/2011/04/24/associations-in-ef-4-1-code-first-part-4-table-splitting.aspx
http://www.robbagby.com/entity-framework/entity-framework-modeling-entity-splitting/
Entity Framework Mapping Scenarios - http://msdn.microsoft.com/en-us/library/cc716779.aspx
http://blogs.microsoft.co.il/blogs/gilf/archive/2009/03/06/entity-splitting-in-entity-framework.aspx

In your Context class in OnModelCreating:
modelBuilder.Entity<GiftCouponPayment>()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("GiftCouponPayment");
});
modelBuilder.Entity<ClubCardPayment>()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("ClubCardPayment");
});

Related

Using interfaces in DbContext and DbSets for similar databases

I have 3 sqlite databases, and each has only one table called "Logs".
These "Logs" tables have different columns:
Database1.db
Logs(Id, VarA, VarB)
Database2.db
Logs(Id, VarC, VarD)
Database3.db
Logs(Id, VarE, VarF)
I've modeled this with EntityFramework 6 as follows:
public class Database1Log
{
public int Id { get; set; }
public float? VarA { get; set; }
public float? VarB { get; set; }
}
public class Database2Log
{
public int Id { get; set; }
public float? VarC { get; set; }
public float? VarD { get; set; }
}
public class Database3Log
{
public int Id { get; set; }
public float? VarE { get; set; }
public float? VarF { get; set; }
}
I also have 3 different DbContext:
public class Database1DbContext : DbContext
{
public DbSet<Database1Log> Logs { get; set; }
public Database1DbContext ()
{
Database.SetInitializer<Database1DbContext >(null);
}
}
public class Database2DbContext : DbContext
{
public DbSet<Database2Log> Logs { get; set; }
public Database2DbContext ()
{
Database.SetInitializer<Database2DbContext >(null);
}
}
public class Database3DbContext : DbContext
{
public DbSet<Database3Log> Logs { get; set; }
public Database3DbContext ()
{
Database.SetInitializer<Database3DbContext >(null);
}
}
Question:
Is it possible to refactor the following code using interfaces and/or inheritance?
class Program
{
static void Main(string[] args)
{
if (args[0].Equals("Database1"))
{
var dbContext = new Database1DbContext();
var logs = dbContext.Logs();
Console.WriteLine(JsonConvert.SerializeObject(logs));
}
else if (args[0].Equals("Database2"))
{
var dbContext = new Database2DbContext();
var logs = dbContext.Logs();
Console.WriteLine(JsonConvert.SerializeObject(logs));
}
else if (args[0].Equals("Database3"))
{
var dbContext = new Database3DbContext();
var logs = dbContext.Logs();
Console.WriteLine(JsonConvert.SerializeObject(logs));
}
}
}
This is what I've tried so far:
public interface ILog
{
int Id { get; set; }
}
public interface IDbContext
{
DbSet<ILog> Logs { get; set; }
}
public class Database1DbContext : DbContext, IDbContext
{
public DbSet<ILog> Logs { get; set; }
public Database1DbContext ()
{
Database.SetInitializer<Database1DbContext >(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Database1Log>()
.HasKey(u => u.Id)
.ToTable("Logs");
}
}
public class Database2DbContext : DbContext, IDbContext
{
public DbSet<ILog> Logs { get; set; }
public Database2DbContext ()
{
Database.SetInitializer<Database2DbContext >(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Database2Log>()
.HasKey(u => u.Id)
.ToTable("Logs");
}
}
public class Database3DbContext : DbContext, IDbContext
{
public DbSet<ILog> Logs { get; set; }
public Database3DbContext ()
{
Database.SetInitializer<Database2DbContext >(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Database3Log>()
.HasKey(u => u.Id)
.ToTable("Logs");
}
}
class Program
{
static void Main(string[] args)
{
IDbContext dbContext = null;
List<ILog> logs = null;
if (args[0].Equals("Database1"))
{
dbContext = new Database1DbContext();
}
else if (args[0].Equals("Database2"))
{
dbContext = new Database2DbContext();
}
else if (args[0].Equals("Database3"))
{
dbContext = new Database3DbContext();
}
logs = dbContext.Logs();
Console.WriteLine(JsonConvert.SerializeObject(logs));
}
}
But it throws:
The type 'ConsoleApp.Models.ILog' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive or generic, and does not inherit from EntityObject.
You could use some kind of factory or injection since youre predicating which type of database to instantiate based on a string input from the user/program. Maybe start down a path like this:
public interface IDbContext
{
DbSet<ILog> Logs { get; set; }
}
public interface IDbContextFactory
{
IDbContext CreateDb(string type);
}
Then the actual factory:
public class DbFactory : IDbContextFactory
{
public IDbContext Create(string name)
{
if (name == "1")
{
return new Database1DbContext();
}
// else if 2
// else if 3
}
}
Then your consuming code could look something like:
IDbContextFactory factory = new DbContextFactory();
IDbContext db = factory.Create(args[0]);
DbSet<ILog> logs = db.Logs;
Console.WriteLine(JsonConvert.SerializeObject(logs));

C# CodeFirst Enum EntityFramework

I'm trying to use codefirstdatabase in my applicantion, but I'm nothing having success in mapping the enum in my application.
I'm receiving that error when I try to use it.
(6,10) : error 3032: Problem in mapping fragments starting at line 6:Condition member 'BasicAccount.AccountType' with a condition other than 'IsNull=False' is mapped. Either remove the condition on BasicAccount.AccountType or remove it from the mapping.
public class PersonalDate
{
public int id { get; set; }
public ICollection<BaseAccount> BaseAccounts{ get; set; }
public PersonalDate()
{ }
}
public class BasicAccount
{
public int id { get; set; }
public AccountType AccountType { get; set; }
public PersonalDate PersonalDate { get; set }
public BasicAccount()
{
}
}
public class CurrentAccount: BasicAccount
{
public CurrentAccount()
{
AccountType = AccountType.CurrentAccount;
}
}
public class SavingAccount: BasicAccount
{
public SavingAccount()
{
AccountType = AccountType.SavingAccount;
}
}
public class SalaryAccount: BasicAccount
{
public SalaryAccount()
{
AccountType = AccountType.SalaryAccount;
}
}
public enum AccountType: int
{
undefined= 0,
SavingAccount= 1,
CurrentAccount= 2,
SalaryAccount= 3
}
public class BancoContext : DbContext
{
public DbSet<DadoPessoa> DadosPessoas { get; set; }
public DbSet<ContaBase> ContaBases { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BasicAccount>()
.HasRequired<PersonalDate>(x => x.PersonalDate);
modelBuilder.Entity<BasicAccount>()
.Map<BasicAccount>( x => x.Requires("AccountType").HasValue((int)AccountType.Undefined))
.Map<SavingAccount>(x => .Requires("AccountType").HasValue((int)AccountType.SavingAccount))
.Map<CurrentAccount>(x => x.Requires("AccountType").HasValue((int)AccountType.CurrentAccount))
.Map<SalaryAccount>(x => x.Requires("AccountType").HasValue((int)AccountType.SalaryAccount));
}

EF 6 Code First - Schema is not valid type not available

I did some refactoring to a code base, and created an abstract class SlottedHardware that held some common properties that other classes should use.
However, I am now getting the error:
Schema specified is not valid. Errors: The relationship 'MyProject.Models.NetworkDevice_Slots' was not loaded because the type 'MyProject.Models.Models.NetworkDevice' is not available.
when trying to create the database by setting my DbContext ctor to Database.SetInitializer<MyDbContext>(new CreateDatabaseIfNotExists<MyDbContext>());
I have been at this for hours, and would really appreciate if someone can lend a helping hand. Here are some of the entity classes, as well as the Fluent API mapping:
public abstract class EntityBase
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public abstract class SlottedHardware : EntityBase
{
public int MaxSlots { get; set; }
public virtual ICollection<Slot> Slots { get; set; }
}
public class Slot : EntityBase
{
public string SlotIdentifier { get; set; }
public List<Card> CompatibleCards { get; set; } = new List<Card>();
public State State { get; set; }
public virtual NetworkDevice NetworkDevice { get; set; }
}
public class NetworkDevice : SlottedHardware
{
public string Vendor { get; set; }
public string Model { get; set; }
public List<UnpublishedConfig> UnpublishedConfigs { get; set; }
public List<PublishedConfig> PublishedConfigs { get; set; }
public State State { get; set; }
/*** Constructors ***/
public NetworkDevice()
{
MaxSlots = 0;
Slots = new List<Slot>();
UnpublishedConfigs = new List<UnpublishedConfig>();
PublishedConfigs = new List<PublishedConfig>();
}
public NetworkDevice(string vendor, string model, int maxSlots) : this()
{
Vendor = vendor;
Model = model;
if(maxSlots > 0)
{
MaxSlots = maxSlots;
}
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// NetworkDevice entity
modelBuilder.Entity<NetworkDevice>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("NetworkDevices");
});
modelBuilder.Entity<NetworkDevice>().HasKey(t => t.Id);
modelBuilder.Entity<NetworkDevice>().
HasMany(t => t.Slots).
WithOptional(t => t.NetworkDevice);
modelBuilder.Entity<NetworkDevice>().
HasMany(t => t.PublishedConfigs).
WithMany();
modelBuilder.Entity<NetworkDevice>().
HasMany(t => t.UnpublishedConfigs).
WithMany();
modelBuilder.Entity<NetworkDevice>().Property(t => t.MaxSlots).IsRequired();
modelBuilder.Entity<NetworkDevice>().Property(t => t.Model).IsRequired();
// Slot entity
modelBuilder.Entity<Slot>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Slots");
});
modelBuilder.Entity<Slot>().HasKey(t => t.Id);
modelBuilder.Entity<Slot>().HasOptional(t => t.NetworkDevice).WithMany(x => x.Slots);
modelBuilder.Entity<Slot>().HasMany(t => t.CompatibleCards).WithMany(x => x.Slots);
modelBuilder.Entity<Slot>().Property(t => t.SlotIdentifier).IsRequired();
modelBuilder.Entity<Slot>().Ignore(t => t.Card);
}

Update inherited Entity in Entity Framework 7

I have the following BaseballDbContext class:
public class BaseballDbContext : DbContext
{
public DbSet<BaseballTeam> teams { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Hitter>();
modelBuilder.Entity<Pitcher>();
}
}
And my model classes are:
public class BaseballTeam
{
public int Id { get; set; }
public string teamName { get; set; }
public List<BaseballPlayer> players { get; set; }
}
public abstract class BaseballPlayer
{
public int Id { get; set; }
public int age { get; set; }
public string name { get; set; }
}
public class Hitter : BaseballPlayer
{
public int homeruns { get; set; }
}
public class Pitcher : BaseballPlayer
{
public int strikeouts { get; set; }
}
Initially seeded data in the players table:
Now I want to update name and homeruns property of one of the hitters:
BaseballTeam team = _ctx.teams.Include(q => q.players).FirstOrDefault();
Hitter hitter = team.players.OfType<Hitter>().FirstOrDefault();
hitter.name = "Tulowitzki"; //that property will be updated
hitter.homeruns = 399; //but that will not :(
int i = team.players.FindIndex(q => q.Id == hitter.Id);
team.players[i] = hitter;
_ctx.Update(team);
_ctx.SaveChanges();
After I run the code only player's name got update, but not the homeruns property:
How to update property of both child and parent class ?
From this answer but this is a workaround: Save changes to child class properties using base class query with Entity Framework TPH patten :
Do Not track changes using AsNoTracking()
using (var context = new BaseballDbContext())
{
var team = context.teams.Include(q => q.players).AsNoTracking().FirstOrDefault();
var hitter = team.players.OfType<Hitter>().FirstOrDefault();
hitter.name = "Donaldson";
hitter.homeruns = 999;
context.Update(team);
context.SaveChanges();
}
I think you should have a look at the opened issues related to inheritance and may be open a new issue

Entity Framework: Incorrect data while using Many-to-many relationship

I have following C# code that uses Entity Framework Code First approach. The tables are created in database; but the data entered is incorrect.
Person 1 is member of Club 1 and Club 3.
Person 2 is member of Club 2 and Club 3
That means Club 2 has only one member.
But using the following query it can be seen that the data reached in database is incorrect.
What change need to be done in C# code in order to make it correct?
static void Main(string[] args)
{
Database.SetInitializer<NerdDinners>(new MyInitializer());
string connectionstring = "Data Source=.;Initial Catalog=NerdDinners;Integrated Security=True;Connect Timeout=30";
using (var db = new NerdDinners(connectionstring))
{
Club club1 = new Club();
Club club2 = new Club();
Club club3 = new Club();
Person p1 = new Person();
Person p2 = new Person();
List<Club> clubsForPerson1 = new List<Club>();
clubsForPerson1.Add(club1);
clubsForPerson1.Add(club3);
List<Club> clubsForPerson2 = new List<Club>();
clubsForPerson2.Add(club2);
clubsForPerson2.Add(club3);
List<Person> personInClub1 = new List<Person>();
personInClub1.Add(p1);
List<Person> personInClub2 = new List<Person>();
personInClub2.Add(p2);
List<Person> personInClub3 = new List<Person>();
personInClub3.Add(p1);
personInClub3.Add(p2);
club1.Members=personInClub1;
club2.Members=personInClub2;
club3.Members=personInClub3;
p1.Clubs = clubsForPerson1;
p2.Clubs = clubsForPerson2;
db.Clubs.Add(club1);
db.Clubs.Add(club2);
db.Clubs.Add(club3);
db.Persons.Add(p1);
db.Persons.Add(p2);
int recordsAffected = db.SaveChanges();
}
}
namespace LijosEF
{
public class Person
{
public int PersonId { get; set; }
public virtual ICollection<Club> Clubs { get; set; }
}
public class Club
{
public int ClubId { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
public abstract class PaymentComponent
{
public int PaymentComponentID { get; set; }
public int MyValue { get; set; }
public abstract int GetEffectiveValue();
}
public partial class GiftCouponPayment : PaymentComponent
{
public override int GetEffectiveValue()
{
if (MyValue < 2000)
{
return 0;
}
return MyValue;
}
}
public partial class ClubCardPayment : PaymentComponent
{
public override int GetEffectiveValue()
{
return MyValue;
}
}
public partial class Payment
{
public int PaymentID { get; set; }
public List<PaymentComponent> PaymentComponents { get; set; }
public DateTime PayedTime { get; set; }
}
public class MyInitializer : CreateDatabaseIfNotExists<NerdDinners>
{
//Only one identity column can be created per table.
protected override void Seed(NerdDinners context)
{
}
}
//System.Data.Entity.DbContext is from EntityFramework.dll
public class NerdDinners : System.Data.Entity.DbContext
{
public NerdDinners(string connString): base(connString)
{
}
protected override void OnModelCreating(DbModelBuilder modelbuilder)
{
//Fluent API - Plural Removal
modelbuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<Person> Persons { get; set; }
public DbSet<Club> Clubs { get; set; }
}
}
There was no problem actually. I thought about it as a problem due to the order of id creation. I have got some other issues that I have posted in Entity Framework: Duplicate Records in Many-to-Many relationship.
It uses
((IObjectContextAdapter)db).ObjectContext.Attach((IEntityWithKey)entity);

Categories