Including read-only columns in an Entity Framework model - entity-framework

Suppose I have a .NET Entity Framework model class:
public class Foo
{
public int FooId { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public DateTime LastUpdated { get; set; }
}
The Created and LastUpdated columns in my SQL Server table, both of type DATETIME2, have a DEFAULT constraint (SYSUTCDATETIME()). An AFTER UPDATE trigger sets LastUpdated to SYSUTCDATETIME whenever the Description is changed.
In my code, when I'm reading from the Foo table, I want Created and LastUpdated included, because I want to use their values. But when I'm adding a row to the table, I don't want them included in the Add because I want SQL Server to use the default value I've configured it to use. I thought it would just have been a matter of having
Foo foo = new Foo
{
Description = "This is my latest foo."
}
but C# is giving the two date properties their own default value of 0001-01-01T00:00:00.000000, which isn't null, and this is what's getting recorded in the table.
Isn't there an attribute that tells the framework not to write a property back to the database? It isn't NotMapped because that would prevent the values from being read.

`Don't you hate when you find the answer right after posting your question?
[DatabaseGenerated(DatabaseGeneratedOption.Computed)] omits the property from inserts and updates.
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] omits the property from inserts.
The latter will take care of EndDate, which I didn't illustrate in my post. I have the database set a default value of 9999-12-31T23:59:59 on insert, but my application will change its value later when Foo is meant to expire.
What kills me is they based the naming on specific use cases that wouldn't come to mind in a different scenario. They ought to have gone with [SkipOnInsert] and [SkipOnUpdate] (which could then be combined as needed).

Related

Table Splitting - Migration Warning

My scenario:
I have a Product that has various properties such a price, size, etc. that are declared in the Product Entity.
Additionally, a Product can have a collection of StockRequirements, i.e. when that Product is used the constituent StockItems can be depleted by the StockRequirement quantity accordingly.
Under one use case I just want the Product so that I can play with the core properties. For another use case I want the Product with its StockRequirements.
This means that when retrieving a Product I may be using it in different contexts. My chosen approach has been to use EF table splitting.
I have one repository for Products and one repository for ProductStockRequirements. They are referring to the same unique Product.
The Product repository will provide a Product Entity with the core details only.
The ProductStockRequirements repository will provide ProductStockRequirements entity which does not have the core details, but does have the list of StockRequirements.
This seemed a reasonable approach so that I am not retrieving 'owned' StockRequirements when I only want to change the price of the product. Similarly, if I'm only interested in playing with the StockRequirements then I don't retrieve the other core details.
Entities
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
}
class ProductStockRequirements
{
public int Id { get; set; }
public List<StockRequirement> StockRequirements { get; set; }
}
Product Mapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.Property(p => p.CoreProperty).IsRequired();
ProductStockRequirementsMapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.OwnsMany<StockRequirement>(p => StockRequirements, b =>
{
b.ToTable("StockRequirements");
b.WithOwner().HasForeignKey("ProductId");
}
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id");
When running a migration, I get the warning:
The entity type 'ProductStockRequirements' is an optional dependent
using table sharing without any required non shared property that
could be used to identify whether the entity exists. If all nullable
properties contain a null value in database then an object instance
won't be created in the query. Add a required property to create
instances with null values for other properties or mark the incoming
navigation as required to always create an instance.
Focusing on the advice:
mark the incoming navigation as required to always create an instance
I have tried:
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id")
.IsRequired();
and
b.HasOne<Product>()
.WithOne()
.IsRequired()
.HasForeignKey<ProductStockRequirements>("Id");
to no avail.
The warning does not appear to result in any bad behaviour. All my tests are passing. But, it seems that I should be able to create a map that removed this warning, but cannot find the way.
This should really just be
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
public List<StockRequirement> StockRequirements { get; set; } = new List<StockRequirement>();
}
As the StockRequiremens are not part of the Product entity, and related data isn't loaded unless you request it.
And the Entity model is simply not the correct layer to define your aggregates. An Aggregate is defined by selecting a single Entity from your entity model along with 0-few related entities. Typically you include the closely-related and weak entities together in an aggregate.
If your entity model is a graph of 23 related entities, you might organize it into 10 separate and partially-overlapping aggregates or sub-graphs.

EF Core set Id to Int.MinValue and try to insert in database

I am using EF Core and I have a problem when I save a new entity.
Here is my model class
[Column("Id")]
public int ID { get; set; }
[Required]
[Column("Pratica", TypeName = "varchar(10)")]
public string PRATICA { get; set; }
[Column("Anno")]
public int ANNO { get; set; }
[Required]
[Column("Variante", TypeName = "varchar(2)")]
public string VARIANTE { get; set; }
Here I create and initialize a new PRAT object:
var prat = new PRAT();
prat.PRATICA = "Prova";
prat.ANNO = 2000;
prat.VARIANTE = "0";
context.PRAT.Add(prat);
context.SaveChangesAsync();
Just after the context.PRAT.Add(prat) line if I check prat.ID member I get something like -2147482647
After context.SaveChangesAsync I get the error "Cannot insert explicit value for identity column in table 'Prat' when IDENTITY_INSERT is set to OFF"
This is the generated SQL statement:
INSERT INTO [Prat] ([Id], [Anno], [Pratica], [Variante]) VALUES (#p0, #p1, #p2, #p3);
As you can see the Id Field is added to the list of fields, but this field is Identity!
If, before context.SaveChangesAsync() I set
prat.ID = 0
the generated SQL Statement is
INSERT INTO [Prat] ([Anno], [Pratica], [Variante]) VALUES (#p0, #p1, #p2);
And all works fine.
Thank you.
I think you need to configure your model with the DatabaseGenerated attribute, or configure it with fluent api
...
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Id")]
public int ID { get; set; }
...
The primary key property is of type int, by convention EF Core assumes that the database will use the SQL
IDENTITY command to create a unique key when a new row is added. So you must define your database column as identity column.
For anyone still dealing with this, the other answers are insufficient. Primary keys for ints, shorts, guids etc in EF core are automatically generated.
The DatabaseGeneratedOption.Identity is for columns that are not primary keys.
The real problem is that somewhere in your code (potentially your database seeder if you have one) is pushing entities with manually entered primary keys.
For example:
_context.Jobs.Add(
new Job()
{
JobId = 1,
Name = "Truck Driver",
},
);
_context.SaveChanges();
Doing so tells ef core that you will be supplying primary keys for that entity and it will not know how to generate them. I am unsure why this is because you would think ef core could just grab the max value primary key and add 1 but I think the PK value generation code under the hood is the same for all primary key datatypes (including guid where max value isn't a thing).
Anyways, remove the code where you are manually inserting primary keys and the Add functionality should work as expected.

How do I access a table that has a composite key I can't model using Code First?

I have an existing database that I cannot change, but want to access using EF. For 90% of the database, I have been able to get Code First EF to work, and I am very impressed.
I've run into a case that I am wondering how to model or access the data through a navigation property.
In one case, the tables are like this (this example is totally made up, but represents the problem):
CREATE TABLE Dog (
id INTEGER PRIMARY KEY,
name VARCHAR(50) NULL,
breed_id INTEGER NOT NULL
);
CREATE TABLE Breed (
id INTEGER NOT NULL,
organization_id INTEGER NOT NULL,
description VARCHAR(100) NOT NULL,
Primary key (id, organization)
);
CREATE TABLE Organization (
id INTEGER PRIMARY KEY,
description VARCHAR(100) NOT NULL
);
In Table breed, the organization represents an organization that has defined a breed. A dog can have several breeds, but the program only displays one, the results being 'filtered' by the organization id - which is a value that is configured when the program is set up.
An example of the data that might be present is this:
id organizationID Description
1 1 Basset Hound
2 1 Great Dane
2 2 Grande Dane
Where organization 2 has chosen to call the breed something different than organization 1. The unique primary key is a combination of id and organizationID. A dog has a breed, but does not have a property to define one or more associated organizations. It takes additional information from another table, or a configured value (perhaps an enumerated value) to find the breed of a dog.
In my case, to find a particular dog breed, you have to have a dog id and another piece of information (organization_id) which is related to program configuration.
The dog, breed and organization classes look like this:
public class Dog {
public int id { get; set; };
public string name { get; set; };
public int breedID { get; set; };
public virtual Breed { get; set; }
}
public class Breed {
public int id { get; set; };
public int organizationID { get; set; };
public string description { get; set; };
public virtual Organization { get; set; }
}
public class Organization {
public int id { get; set; };
public string description { get; set; };
}
As seen in the code, I'd like to use a "Navigation" Property on Dog that returns a breed, but don't think I can configure this in code first.
I've tried a few different things (in fluent API, and leaving organization out - since that's easy) and will also document things I don't think work:
1)
modelBuilder.Entity<Dog>().HasKey(t => t.id);
modelBuilder.Entity<Breed>().HasKey(t => t.id);
modelBuilder.Entity<Dog>().HasRequired(d => d.Breed).WithMany().HasForeignKey(d => d.breedID);
Of course, the problem with this is that more than one breed will be returned and entity framework will throw an exception because the breedID itself is insufficient to yield a single value - which the model is calling for.
2)
Change class dog:
Remove:
public virtual Breed { get; set; }
Add:
public virtual ICollection<Breed> Breeds { get; set; }
public Breed Breed {
get {
// Assume 1 is configured organization value
return Breeds.Single(t => t.OrganizationId == 1);
};
set {
Breeds.Add(value);
}
}
Change model:
I don't know how to do this for the given classes. Since it's a Collection, it must look something like
modelBuilder.Entity<Dog>().HasMany(d => d.Breeds)...
but I don't see how to specify that breedID is a foriegn key into the breed table.
If I could get the model to work, the rest will work, but it does seem wierd and inefficient.
3)
Change model to account for composite key (using first set of classes):
modelBuilder.Entity<Breed>().HasKey(b => new { b.id, b.organizationId });
modelBuilder.Entity<Dog>().HasRequired(t => t.Breed).WithMany().HasForeignKey(t => new { t.breedID, configuredValue });
I don't know how to "inject" configuredValue as in the last line, so this doesn't work either.
If none of the above methods work, or if I can't find another way to configure code first properly, then I'd like to specify that when the Breed navigation property getter is called, it should use a query that can get the appropriate breed and return it appropriately.
However, I don't want to dirty my POCO with the Context calls to return the result of the query. In other words, I'd like to have a property on Dog that does NOT look something like this:
public Breed Breed {
get {
return context.Breed.Where(b => b.id == this.id && b.organizationID == 1).Single();
}
}
Ideally, it would work like the Navigation collections do, where EF does it's magic and returns the appropriate results.
Intuitively, it seems like I should be able to either configure this using POCO-like code or use/extend a proxy to extend the configuration to use the particular query I want when the accessor is called. Or - it seems like I ought to be able to populate the property on any read and dirty the POCO on write. I'm just not familiar enough with EF to know how to do this.
Is this possible?
As an addition to the first post, because I desire to keep my POCO classes clean, I think I will probably implement the Repository pattern to encapsulate the complex queries like the ones I've described, as well as support other operations also.
Looking at the EDMX that I've generated from my Code First model, it's not apparent how to implement the model from a database first perspective, either.
Any thoughts are greatly appreciated.
In short, you can't do that. You have illogical database structure (foreign key referencing non-unique column), you'll have to map it somewhere. You can't do that in EF configuration, because it's dynamic, so you'll have to do that inside you entity classes. And I see no ways to do that without direct call to context inside your entity class (or helper class).

How do I map a one to zero-or-one relationship in Entity Framework 4.2 when joining properties are of different types?

I am using Entity Framework 4.2 in a class library project. The database already exists, and I cannot modify it in any way whatsoever.
I have two model/domain classes that model two database tables. The tables both expose an Id column value, which I will refer to as ThingsId. Lets call the tables TableOfThings1 and TableOfThings2. Here are my classes:
public class TableOfThings1
{
public string ThingId { get; set; }
public virtual Thing Thing { get; set; }
}
public class TableOfThings2 //qse
{
public Int64? ThingId {get; set;}
public string ThingName { get; set; }
}
The problem is that the TableOfThings1 exposes ThingsId as a nullable varchar(64), while TableOfThings2 exposes ThingsId as a non-nullable bigint.
How can I tell the Entity Framework to join on these two keys? I have tried using this:
HasOptional(things1 => things1.thing).WithMany().HasForeignKey(t => t.ThingId);
in the EntityTypeConfiguration forTableOfThings1.
I have also tried casting in the middle of that statement, which does not work. Using the setup shown above gets me this error message currently:
"The types of all properties in the Dependent Role of a referential
constraint must be the same as the corresponding property types in the
Principal Role".
Does anyone know for sure whether/how this is possible?
This is not possible with EF. You can not even create a foreign key in the database if the column types are different. Possible workaround would be to create a view of TableOfThings1 with ThingId column type matching the TableOfThings2s column type.

mongodb insert creation time separated from objectId

I am using mongodb with the official c# driver.
I am using Guids as Id field for my objects. I don't want to introduce a dependency on the mongodb bson classes so I am not using ObjectId in my domain layer.
Is it possible to instruct mongodb to insert a creation timestamp into objects that I insert into the datastore?
Example:
public class Foo
{
public Guid Id {get;set;}
public DateTime CreatedOn {get;set;}
}
Using mongodb idGenerators I can get the Guids generated upon insert. I know ObjectId has the timestamp included but as mentioned I wouldn't want my class to look like this
public class Foo
{
public ObjectId Id { get; set; }
public DateTime CreatedOn {get { return Id.CreationTime;}}
}
Is it important that it's the inserted timestamp and not the object's created timestamp? If not then do it in the constructor of the class. Or even better, a base class for your class(es).
public abstract class BaseClass
{
[BsonId]
public Guid Id {get;set;}
public DateTime CreatedOn {get;set;}
protected BaseClass()
{
Guid = new Guid();
CreatedOn = new DateTime.UtcNow;
}
}
public class Foo : BaseClass
{
}
Is this something you can use for it?
You can have an _id which is itself a document :
in json : { _id : {guid : ...., createdOn : ....} , field1 : ..., field2:....}
You just have to modify your idGenerator to have this behavior.
I recommend, however, that you really re-consider to use ObjectId.
If you're trying to make your domain layer pure and free of persistence concerns, then it makes sense to populate the creation date yourself in the domain layer, when deciding to create an entity, instead of relying on the database technology to put in a server timestamp.
This makes "creation date" a logical domain concept rather than the DB's concept of "timestamp when first stored in DB". The two can differ e.g. in cases of migrating data (but keeping the timestamp), deferring execution (e.g. in jobs), etc.
It also creates a healthy separation between "physical timestamp" and "logical timestamp" which you can further exploit during testing/mocking (e.g. you could have a test that says "do X, then change the logical time to 2 days in the future, then assert Y").
Finally, it forces you to think of what the creation date means in your domain layer instead of blindly assuming that it will be correct.
All this being said, if you insist on having it in MongoDB you can have a mapping that creates an ObjectID into some kind of hidden field (e.g. an explicitly-implemented interface) at insert time, and extracts its timestamp into the CreationDate field at read time.