GUID COMB strategy in EF - c#

Is there any way to implement the Guid COMB identity strategy for objects in the new Entity Framework 4.1 using the CodeFirst design? I thought setting the StoreGeneratedPattern would work, but it still gives me normal GUIDs.

Why worry about defaults for Guid columns in the database at all? Why not just generate the Guid on the client like any other value. That requires you have a method in your client code that will generate COMB-like guids:
public static Guid NewGuid()
{
var guidBinary = new byte[16];
Array.Copy( Guid.NewGuid().ToByteArray(), 0, guidBinary, 0, 8 );
Array.Copy( BitConverter.GetBytes( DateTime.Now.Ticks ), 0, guidBinary, 8, 8 );
return new Guid( guidBinary );
}
One of the advantages of the Guid is specifically that you can generate them on the client without a round trip to the database.

I guess you are using SQL server as your database. This is nice example of inconsistency among different MS tools. SQL server team doesn't recommend using newid() as default value for UNIQUEIDENTIFIER columns and ADO.NET team use it if you specify Guid property as autogenerated in the database. They should use newsequentialid() instead!
If you want sequential Guids generated by database you must modify generated table and it is really complex because you must find autogenerated default constraint, drop it and create new constraint. This all can be done in custom database initializer. Here you have my sample code:
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new CustomInitializer());
using (var context = new Context())
{
context.TestEntities.Add(new TestEntity() { Name = "A" });
context.TestEntities.Add(new TestEntity() { Name = "B" });
context.SaveChanges();
}
}
}
public class CustomInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
base.Seed(context);
context.Database.ExecuteSqlCommand(#"
DECLARE #Name VARCHAR(100)
SELECT #Name = O.Name FROM sys.objects AS O
INNER JOIN sys.tables AS T ON O.parent_object_id = T.object_id
WHERE O.type_desc LIKE 'DEFAULT_CONSTRAINT'
AND O.Name LIKE 'DF__TestEntities__Id__%'
AND T.Name = 'TestEntities'
DECLARE #Sql NVARCHAR(2000) = 'ALTER TABLE TestEntities DROP Constraint ' + #Name
EXEC sp_executesql #Sql
ALTER TABLE TestEntities
ADD CONSTRAINT IdDef DEFAULT NEWSEQUENTIALID() FOR Id");
}
}
public class TestEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Context : DbContext
{
public DbSet<TestEntity> TestEntities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TestEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}

The simplest answer
public class User
{
public User(Guid? id = null, DateTime? created = null)
{
if (id != null)
Id = id;
if (created != null)
Created = created;
}
public User()
{
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime? Created { get; internal set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid? Id { get; internal set; }
}
This assumes you have your database table set with the default of newsequentialid() which in my case is managed by FluentMigrator migrations.

if you use SQL Server, when a GUID property is configured as value generated on add, the provider automatically performs value generation client-side, using an algorithm to generate optimal sequential GUID values. refer to for more.
https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=fluent-api

Related

Pipelined function as entity in EF/MVC

I have a .Net MVC app using entity framework, and normally I'd use a table or a view in a data entity... eg.
[Table("company_details", Shema = "abd")]
public class CompanyDetails
{
[Key]
[Column("cd_id_pk")]
public int CompanyDetailsId { get; set; }
etc ...
etc ...
...where company_details is an oracle table.
However I need to try to utilise a pipelined function.... eg the sql would be:
SELECT * FROM TABLE(abd.company_pck.f_single_rprt('1A122F', '01-Feb-2020','Y'));
This had been used in a report used in Oracle forms, but now it's to be included in an .Net MVC app.
How can I include a pipelined function in my entity?
thanks in advance
I just tried this and it seems to work. First create a class as you would to be able to map the return from your DbContext. In your case you just call the Pipelined table function from Oracle. I used a TVF in SQL to demonstrate. The TVF returned 3 columns of data, 2 INT and 1 NVarChar.
public class ReturnThreeColumnTableFunction
{
public int ColumnOne { get; set; }
public int ColumnTwo { get; set; }
public string ColumnThree { get; set; }
}
Then based on your Oracle Pipelined function, (see my MSSQL TVF below)
/* SQL TableValuedFunction */
ALTER FUNCTION [dbo].[ReturnThreeColumnTableFunction]
(
#ColumnOne INT,
#ColumnTwo INT,
#ColumnThree NVARCHAR(10)
)
RETURNS TABLE
AS
RETURN
(
SELECT #ColumnOne AS ColumnOne, #ColumnTwo AS ColumnTwo, #ColumnThree AS ColumnThree
)
Then in your DbContext class you setup your CodeFirst entities, be sure to add the complex type in the OnModelCreating method.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>();
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>().Property(x => x.ColumnOne).HasColumnName("ColumnOne");
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>().Property(x => x.ColumnTwo).HasColumnName("ColumnTwo");
modelBuilder.ComplexType<ReturnThreeColumnTableFunction>().Property(x => x.ColumnThree).HasColumnName("ColumnThree");
}
Then you return this easily using the SqlQuery
var items = context.Database.SqlQuery<ReturnThreeColumnTableFunction>("SELECT * FROM dbo.ReturnThreeColumnTableFunction(1,2,'3')")

Mapping stored procedure results to a custom class using EF Core FromSql returns instances with default values

I have a stored procedure like this:
CREATE PROCEDURE PLogin
#pin varchar(50), #ipaddress varchar(50)
AS
BEGIN
SET NOCOUNT ON;
//Some code here ...
SELECT * FROM [dbo].[QRelaceUzivatele_Aktivni] WHERE Pin = #pin AND IPAdresa=#ipaddress
END
I need to execute it from ASP MVC using EF Core 2.1.
I have used the approach mentioned here.
I have a custom C# class AktivniRelaceUzivatele that maps the fields of the QRelaceUzivatele_Aktivni view.
public class AktivniRelaceUzivatele
{
int ProvozID { get; set; }
string NazevProvozu { get; set; }
int UzivatelID { get; set; }
string PIN { get; set; }
string Nick { get; set; }
In MVC controller I call:
var qpin = new SqlParameter("p", pin);
var qip = new SqlParameter("ip", ip);
var ar = db.Query<AktivniRelaceUzivatele>()
.FromSql("EXECUTE dbo.PLogin #pin=#p, #ipaddress=#ip", qpin, qip).FirstOrDefault();
In dbContext class I have added:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Query<AktivniRelaceUzivatele>();
I get no compile error the stored procedure gets executed but the result values are not insterted into objects's properties, instead I get instances that contain only default values like null, 0 etc.
Can u try to add public classifiers to class variables? I think I got the similar issues, where I missed both getters and setters and public classifiers.

Entity Framework: RowVersion value is null

I am using Entity Framework 6.2.0 and a local MSSQL (MDF) database.
I have several types that all descend from my main type "Entity" ("Table per Type" strategy is used). Now, I am trying to implement optimistic locking.
In my EDMX file, I added a property RowVersion to Entity (a fixed length byte array of 8 bytes, in SQL-DB : "[RowVersion] binary(8) NOT NULL") and set the Concurrency mode of that proeprty to "Fixed". I flagged the property inside the Entity class with the "Timestamp" attribute:
[System.ComponentModel.DataAnnotations.Schema.Table("EntitySet", Schema = "RightsManager")]
public partial class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public System.DateTime ActiveFrom { get; set; }
public Nullable<System.DateTime> ActiveUntil { get; set; }
[System.ComponentModel.DataAnnotations.Timestamp]
public byte[] RowVersion { get; set; }
}
I also added code to OnModelCreating of my DBContext descendant to indicate RowVersion to be used:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<RightsManagerContext>(null);
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Entity>().Property(p => p.RowVersion).IsRowVersion();
modelBuilder.Entity<Product>().Property(p => p.RowVersion).IsRowVersion();
}
The problem: Upon insert of a new Product, an SQL error is thrown. This is the unit test i am using:
[TestMethod]
public void TestCreateProduct()
{
using (var context = GetContext())
{
var newProduct = new Product
{
Name = "New product",
ActiveFrom = DateTime.Now
};
context.Entry(newProduct).State = System.Data.Entity.EntityState.Added;
var objectsWritten = context.SaveChanges();
Assert.AreNotEqual(0, objectsWritten);
};
}
The innermost exception thrown:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'RowVersion', table 'P:\VISUAL STUDIO\PROJECTS\RIGHTSMANAGER\DATABASE\RIGHTSMANAGER.MDF.RightsManager.EntitySet'; column does not allow nulls. INSERT fails.
Obviously, EF is not filling in a value automatically, it's handling the field like any other one. What am i missing here?
I think i misunderstood the IsRowVersion/Timestamp thing to be a database-agnostic one. It seems that this whole mechanism only works if using the MSSQL-specific database field type "rowversion" when creating the table. All other databases such as Oracle, DB2 etc are not in scope.
As I am trying to have DBMS neutrality in my project, I will have to manually implement such a feature with "IsConcurrencyToken".

Entity Framework 6 GUID as primary key: Cannot insert the value NULL into column 'Id', table 'FileStore'; column does not allow nulls

I have an entity with primary key "Id" which is Guid:
public class FileStore
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
}
And some configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<FileStore>().Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
base.OnModelCreating(modelBuilder);
}
When I try to insert a record I get a following error:
Cannot insert the value NULL into column 'Id', table 'FileStore'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.
I don't want to generate Guid manually. I just want to insert a record and get Id generated by SQL Server. If I set .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity), Id column is not Identity column in SQL Server.
How can I configure Entity Framework to autogenerate Guid in SQL Server?
In addition to adding these attributes to your Id column:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
in your migration you should change your CreateTable to add the defaultValueSQL property to your column i.e.:
Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"),
This will prevent you from having to manually touch your database which, as you pointed out in the comments, is something you want to avoid with Code First.
try this :
public class FileStore
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
}
You can check this SO post.
You can set the default value of your Id in your db to newsequentialid() or newid(). Then the identity configuration of EF should work.
This works for me (no Azure), SQL 2008 R2 on dev server or localdb\mssqllocaldb on local workstation. Note: entity adds Create, CreateBy, Modified, ModifiedBy and Version columns.
public class Carrier : Entity
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
then create a mapping configuration class
public class CarrierMap : EntityTypeConfiguration<Carrier>
{
public CarrierMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(p => p.Code)
.HasMaxLength(4)
.IsRequired()
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute { IsClustered = true, IsUnique = true }));
Property(p => p.Name).HasMaxLength(255).IsRequired();
Property(p => p.Created).HasPrecision(7).IsRequired();
Property(p => p.Modified)
.HasColumnAnnotation("IX_Modified", new IndexAnnotation(new IndexAttribute()))
.HasPrecision(7)
.IsRequired();
Property(p => p.CreatedBy).HasMaxLength(50).IsRequired();
Property(p => p.ModifiedBy).HasMaxLength(50).IsRequired();
Property(p => p.Version).IsRowVersion();
}
}
This creates an Up method in the initial DbMigration when you execute add-migration like this
CreateTable(
"scoFreightRate.Carrier",
c => new
{
Id = c.Guid(nullable: false, identity: true),
Code = c.String(nullable: false, maxLength: 4),
Name = c.String(nullable: false, maxLength: 255),
Created = c.DateTimeOffset(nullable: false, precision: 7),
CreatedBy = c.String(nullable: false, maxLength: 50),
Modified = c.DateTimeOffset(nullable: false, precision: 7,
annotations: new Dictionary<string, AnnotationValues>
{
{
"IX_Modified",
new AnnotationValues(oldValue: null, newValue: "IndexAnnotation: { }")
},
}),
ModifiedBy = c.String(nullable: false, maxLength: 50),
Version = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
})
.PrimaryKey(t => t.Id)
.Index(t => t.Code, unique: true, clustered: true);
Note: that the Id columns does not get a default value, don't worry
Now execute Update-Database, and you should end up with a table definition in your database like this:
CREATE TABLE [scoFreightRate].[Carrier] (
[Id] UNIQUEIDENTIFIER DEFAULT (newsequentialid()) NOT NULL,
[Code] NVARCHAR (4) NOT NULL,
[Name] NVARCHAR (255) NOT NULL,
[Created] DATETIMEOFFSET (7) NOT NULL,
[CreatedBy] NVARCHAR (50) NOT NULL,
[Modified] DATETIMEOFFSET (7) NOT NULL,
[ModifiedBy] NVARCHAR (50) NOT NULL,
[Version] ROWVERSION NOT NULL,
CONSTRAINT [PK_scoFreightRate.Carrier] PRIMARY KEY NONCLUSTERED ([Id] ASC)
);
GO
CREATE UNIQUE CLUSTERED INDEX [IX_Code]
ON [scoFreightRate].[Carrier]([Code] ASC);
Note: we have a overridden the SqlServerMigrationSqlGenerator to ensure it does NOT make the Primary Key a Clustered index as we encourage our developers to set a better clustered index on tables
public class OurMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AddPrimaryKeyOperation addPrimaryKeyOperation)
{
if (addPrimaryKeyOperation == null) throw new ArgumentNullException("addPrimaryKeyOperation");
if (!addPrimaryKeyOperation.Table.Contains("__MigrationHistory"))
addPrimaryKeyOperation.IsClustered = false;
base.Generate(addPrimaryKeyOperation);
}
protected override void Generate(CreateTableOperation createTableOperation)
{
if (createTableOperation == null) throw new ArgumentNullException("createTableOperation");
if (!createTableOperation.Name.Contains("__MigrationHistory"))
createTableOperation.PrimaryKey.IsClustered = false;
base.Generate(createTableOperation);
}
protected override void Generate(MoveTableOperation moveTableOperation)
{
if (moveTableOperation == null) throw new ArgumentNullException("moveTableOperation");
if (!moveTableOperation.CreateTableOperation.Name.Contains("__MigrationHistory")) moveTableOperation.CreateTableOperation.PrimaryKey.IsClustered = false;
base.Generate(moveTableOperation);
}
}
It happened to me before.
When the table has been created and I added in .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) later, the code migration somehow could not assign default value for the Guid column.
The fix:
All we need is to go to the database, select the Id column and add newsequentialid() manually into Default Value or Binding.
No need to update dbo.__MigrationHistory table.
Hope it helps.
The solution of adding New Guid() is generally not preferred, because in theory there is possibility that you might get a duplicate accidentally.
And you shouldn't worry about directly editing in the database. All Entity Framework do is automate part of our database work.
Translating
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
into
[Id] [uniqueidentifier] NOT NULL DEFAULT newsequentialid(),
If somehow our EF missed one thing and did not add in the default value for us, just go ahead and add it manually.
Entity Framework – Use a Guid as the primary key
Using a Guid as your tables primary key, when using Entity Framework, requires a little more effort than when using a integer. The setup process is straightforward, after you’ve read/been shown how to do it.
The process is slightly different for the Code First and Database First approaches. This post discusses both techniques.
enter image description here
Code First
Using a Guid as the primary key when taking the code first approach is simple. When creating your entity, add the DatabaseGenerated attribute to your primary key property, as shown below;
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
Entity framework will create the column as you would expect, with a primary key and uniqueidentifier data type.
codefirst-defaultvalue
Also notice, very important, that the default value on the column has been set to (newsequentialid()). This generates a new sequential (continuous) Guid for each row. If you were so inclined, you could change this to newid()), which would result in a completely random Guid for each new row. This will be cleared each time your database gets dropped and re-created, so this works better when taking the Database First approach.
Database First
The database first approach follows a similar line to the code first approach, but you’ll have to manually edit your model to make it work.
Ensure that you edit the primary key column and add the (newsequentialid()) or (newid()) function as the default value before doing anything.
enter image description here
Next, open you EDMX diagram, select the appropriate property and open the properties window. Ensure that StoreGeneratedPattern is set to identity.
databasefirst-model
No need to give your entity an ID in your code, that will be populated for you automatically after the entity has been commited to the database;
using (ApplicationDbContext context = new ApplicationDbContext())
{
var person = new Person
{
FirstName = "Random",
LastName = "Person";
};
context.People.Add(person);
context.SaveChanges();
Console.WriteLine(person.Id);
}
Important Note: Your Guid field MUST be a primary key, or this does not work. Entity Framework will give you a rather cryptic error message!
Summary
Guid (Globally Unique Identifiers) can easily be used as primary keys in Entity Framework. A little extra effort is required to do this, depending on which approach you are taking. When using the code first approach, add the DatabaseGenerated attribute to your key field. When taking the Database First approach, explicitly set the StoredGeneratedPattern to Identity on your model.
[1]: https://i.stack.imgur.com/IxGdd.png
[2]: https://i.stack.imgur.com/Qssea.png
According to this, DatabaseGeneratedOption.Identity is not detected by a specific migration if it's added after the table has been created, which is the case I run into. So I dropped the database and that specific migration and added a new migration, finally update the database, then everything works as expected. I am using EF 6.1, SQL2014 and VS2013.
If you do Code-First and already have a Database:
public override void Up()
{
AlterColumn("dbo.MyTable","Id", c => c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"));
}
You can not. You will / do break a lot of things. Like relationships. WHich rely on the number being pulled back which EF can not do in the way you set it up. THe price for breaking every pattern there is.
Generate the GUID in the C# layer, so that relationships can continue working.
And what something like this?
public class Carrier : Entity
{
public Carrier()
{
this.Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
If you want to automatically generate a compatible migration without use the DataAnnotations, you must add the following in the OnModelCreating method override in the your DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//This is implicit when property is called Id or <ClassName>Id
modelBuilder.Entity<FileStore>(x => x
.HasKey(f => f.Id)
.IsClustered());
modelBuilder.Entity<FileStore>(x => x
.Property(f => f.Id)
.IsRequired() //Set column as not nullable
.ValueGeneratedOnAdd() //Optional (but recommended)
.HasDefaultValueSql("newid()")); //Or: "newsequentialid()"
}
In case you want to use an abstract class or interface to share the [(Guid) Id] property across multiple classes...
public interface IEntity
{
public Guid Id { get; set; }
}
public class FileStore : IEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
}
public class FolderStore : IEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
}
You can define the same directives in this generic way:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var t in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IEntity).IsAssignableFrom(t.ClrType))
{
//This is implicit when property is called Id or <ClassName>Id
modelBuilder.Entity(t.ClrType, x => x
.HasKey(nameof(IEntity.Id))
.IsClustered());
modelBuilder.Entity(t.ClrType, x => x
.Property(nameof(IEntity.Id))
.IsRequired()
.ValueGeneratedOnAdd()
.HasDefaultValueSql("newid()")); //Or: "newsequentialid()"
}
}
}

Primary Key Violation: Inheritance using EF Code First

I have following EF code first code. I am getting the following exception:
'GiftCouponPayment' does not contain an identity column.
The tables are successfully created in database. However, how can I get rid of this exception? Also, what is the reason for this exception?
Note: I am okay with any table schema as longs as the domain model (described using code first) is retained (and the data can be queried).
After continuing this exception, there is a another exception as below:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
{"Violation of PRIMARY KEY constraint 'PK_dbo.PaymentComponent'. Cannot insert duplicate key in object 'dbo.PaymentComponent'.\r\nThe statement has been terminated."}
Reference:
Entity Framework: Split table into multiple tables
Note: The resulting database schema is as shown below.
Code:
public class MyInitializer : CreateDatabaseIfNotExists<NerdDinners>
{
//Only one identity column can be created per table.
protected override void Seed(NerdDinners context)
{
//context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_Payment_PayedTime ON Payment (PayedTime)");
context.Database.ExecuteSqlCommand("DBCC CHECKIDENT ('Payment', RESEED, 1)");
context.Database.ExecuteSqlCommand("DBCC CHECKIDENT ('GiftCouponPayment', RESEED, 2)");
context.Database.ExecuteSqlCommand("DBCC CHECKIDENT ('ClubCardPayment', RESEED, 3)");
}
}
//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>();
//Fluent API - Table per Concrete Type (TPC)
modelbuilder.Entity<GiftCouponPayment>()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("GiftCouponPayment");
});
modelbuilder.Entity<ClubCardPayment>()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("ClubCardPayment");
});
}
public DbSet<GiftCouponPayment> GiftCouponPayments { get; set; }
public DbSet<ClubCardPayment> ClubCardPayments { get; set; }
public DbSet<Payment> Payments { 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; }
}
Client:
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))
{
GiftCouponPayment giftCouponPayment = new GiftCouponPayment();
giftCouponPayment.MyValue=250;
ClubCardPayment clubCardPayment = new ClubCardPayment();
clubCardPayment.MyValue = 5000;
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();
}
}
You are not specifying the ID field for your TPC / TPT mappings. Even with inheritance you need to do this when not running a TPH mappings. (To note, I'm also not sure on the MapInheritedProperties() call... This is generally used for TPH... not TPT)
//Fluent API - Table per Concrete Type (TPC)
modelbuilder.Entity<GiftCouponPayment>()
.HasKey(x => x.PaymentComponentID)
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("GiftCouponPayment");
})
.Property(x => x.PaymentComponentID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
This needs to be on each and every class mapping from the concrete type. If it were me I would go with TPH mapping where GiftCoupon as well as the other inheritance mapping so you end up with 1 table to represent the entire tree of objects using a discriminator column.
Regardless... The other thing you are missing in your base class is:
public byte[] Version { get; set; }
And the associated mapping for:
Property(x => x.Version).IsConcurrencyToken()
Which allows for optimistic concurrency.
Hopefully this helps a bit, let me know if you need further assistance or clarification.
I see that my initial advice about TPC wasn't correct because you are also using FK in the base class - do you see the PaymentComponent table? It should not be there in case of TPC inheritance. Try to use TPT inheritance (remove MapInheritedProperties from your mapping). This will end with the same correct database. Don't use reseed. The Id will be controlled just by identity column in PaymentComponent table (as it is at the moment).
On your PaymentComponent class decorate the ID with KeyAttribute
[Key]
public int PaymentComponentID { get; set; }

Categories

Resources