How can I examine and edit an entity's properties from PropertyBuilder? - entity-framework-core

I'm writing a mapper to map data types between different databases.
When iterating through entities after they have been set by OnModelCreating I need to examine these entity property values that have previously been set by HasColumnType and HasDefaultValueSql in order to alter these settings in a generic way.
How can I read and change these properties?
I'm currently using this function as a first approach:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes())
foreach (IMutableProperty prop in entity.GetDeclaredProperties())
if (prop.ClrType == _byteArrayType)
modelBuilder.Entity(entity.ClrType).Property(prop.Name).HasColumnType("VARBINARY");
}
But that's not the best approach. I need to read (and alter) the original SQL value that has been set (e.g. XMLType data type or SYSDATE default value.)

Related

Any Point to the DbSet Property Name?

public DbSet<Lecture> Lectures{ get; set; }
Does the property name here matter at all? It seems that if I want to use the model, I use "Lecture". The generated table is just a plural of whatever is in <>, e.g., if I understand correctly, I can change "Lectures" to "Leprechauns" and my table will still be called "Lectures" based on <Lecture> and I will use context.Lectures to select from it. Does the property name have any point?
I didn't find the answers in this tutorial or on msdn.
Edit: Upon further testing - the db table name is based on the model name in the angle brackets, but to actually select from the db (in the C# code), you use the property name specified in DbSet propertyName. Still would like to hear how this works in detail.
Entity Framework builds a model of the database, where each class/model represents an entity type, and each DbSet represents a set of entities of a single type. When you declare a DbSet<T> property in your DbContext, that tells EF to include the class of type T as one of the entity types, and it automatically includes any other connected types (e.g. navigation properties) in the object graph as well.
All this to say, the name of the property itself probably doesn't matter. In fact, you could use the Fluent API to add entity types as well, not declare any DbSet properties if you wanted, in which case you'd use context.Set<T> to retrieve the DbSets. The properties are really just for convenience.
Maybe this is helpful as well: https://msdn.microsoft.com/en-us/data/jj592675.aspx
DbSet corresponds to a table or view in your database, So you will be using your DbSet's to get access, create, update, delete and modify your table data.
By the way you can remove the convention:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
The property name matters. The EF translates the name of the property into the name of the table. If the property name is not the same with the table name you'll get an error. Unless you specifically tell the builder the name of the table like this:
public void Configure(EntityTypeBuilder<Lecture> builder)
{
builder.ToTable("License");
}

Why is Entity Framework building non-SARgable predicates

We are using Entity Framework 6.1.1 with code first and DbContext.
Take this a simple query:
Properties.Where(p => p.PropertyID == "aPRoperty")
The generated and executed SQL query is:
WHERE N'aPRoperty' = [Extent1].[PropertyID]
However, propertyID in the database is varchar(10). So the above predicate does an Index Scan rather than Index Seek. This is due to the different datatypes in the predicate.
I can force the correct query by using:
Properties.Where(p => p.PropertyID == DbFunctions.AsNonUnicode("aPRoperty"))
This generates the parameter without the N unicode specifier and we get an Index Seek.
Is this a bug? The fields is mapped IsUnicode(false) in the code first mapping.
Is there a global way to configure this without having to use the Dbfunction in every query we do against a char or varchar field in the database?
You can globally map all strings to varchar in the OnModelCreating event.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(c => c.IsUnicode(false));
}
Sorry for the false alarm all. It seems like it is working correctly after I rebuilt my model dll and reloaded it into linqpad.
My apologies to the EF team for cursing your name for a few minutes there.

Entity Framework MapToStoredProcedures - Ignore Parameters

We have a db table that has the following columns.
WidgetId (PK)
WidgetName
WidgetCreatedOn
WidgetLastUpdatedOn
We have stored procedures that handle the update/delete/insert on the Widget table.
The Insert stored proc takes just the WidgetName as the parameter e.g.
exec Widget_Insert #WidgetName='Foo Widget'
Then the stored procedure puts the dates in for the WidgetCreatedOn WidgetLastUpdatedOn itself.
The Widget object has the same properties as the table e.g.
WidgetId (Key)
WidgetName
WidgetCreatedOn
WidgetLastUpdatedOn
Is it possible to tell the MapToStoredProcedures to ignore specific properties e.g.
modelBuilder.Entity<Widget>()
.MapToStoredProcedures(s =>
s.Insert(i => i.HasName("Widget_Insert")
.Parameter(a => a.WidgetName, "WidgetName")
.Parameter(a => a.WidgetCreatedOn, **dont map it**)
.Parameter(a => a.WidgetLastUpdatedOn, **dont map it**)));
We are doing Code-First
While there might be a way to manually change the MapToStoredProcedures configuration to do this, I have not uncovered it yet. Having said that, there is a way to accomplish this which I assume is how EF expects you to do things.
In your model mapping, specifying a DatabaseGeneratedOption of Identity or Computed will prevent that property from being sent to the insert proc.
If you think about it, this makes some sense. An insert proc will take as much information from the model as possible to do the insert. But an Identity/Computed property is one for which you're saying the DB will provide the data instead so it won't look to the model for that data.
A couple of things to note with this approach. EF will expect those Identity/Computed fields to come back from the proc so you'll need a select after the insert (filtering on SCOPE_IDENTITY() in sql server). EF also assumes that Identity fields won't come back as null, so those have to be Computed even if you don't intend them to be updated later.
If none of that seems palatable, the way to do this kind of thing in EF5 (and is a bit more flexible) is to override SaveChanges on the context and call the proc when the type is Widget and is EntityState.Added. Or you could throw an exception instead to force devs to call the proc on their own vs using EF's DBSet Add method.
Any properties that don't need to be passed to mapped stored procedures (ever) can be marked as computed. Just add the attribute [DatabaseGenerated(DatabaseGeneratedOption.Computed)] in front of your property definitions. The proc MUST return a result set with all the "computed" values after your procedure runs, or else there will be optimistic concurrency errors. A Select * from where should be fine.
If your classes are generated, you can make a partial class to keep all these attributes safe.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MyEfNamespace
{
[MetadataType(typeof(MetaData))]
public partial class Widget
{
public class MetaData
{
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public System.DateTime WidgetCreatedOn;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public System.DateTime WidgetLastUpdatedOn;
...
}
}
}

EDMX is overwriting Key data annotations

This applies to EF 5 and database first modeling. My model was built useing the EF generator from an existing DB.
I'm using the [Key] data annotation in my model classes because the primary key fields have names that are not in line with EF conventions.
Everything works, but when I open the root EDMX files, the model classes are updated and any manual changes I had made are lost.
Should I be making my changes in a different manner?
You could update your T4 template to add in the data annotation for you on primary keys?
if (simpleProperties.Any())
{
foreach (var edmProperty in simpleProperties)
{
if (ef.IsKey(edmProperty)){
#>
[Key]
<# } #>
the solution that worked for me was to overide the EF convention in OnModelCreating method in the context class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<abk_Bookings>()
.HasKey(e => e.booking_number);}

Is it possible to remap a single column, with a special character in it, with Entity Framework Code First?

I have a rather large table of mineral/vitamin definitions (44 columns) and every column name but one is correct. I have a column called [Tryptophan/60] in the SQL database table and I need to represent this in my EF DTO Object MineralDefinition:
public class MineralDefinition
{
public int DefinitionId {get;set;}
public string Tryptophan60 {get;set;}
// 43 other minerals/vitamins
}
Unfortunately I cannot change the columns in the database. Is there any way I can remap this single column without having to write out every column? And, come to think of it, how do I remap the field anyway as it has a / in it?
Update
It looks like it isn't possible to map only one column and is a feature that would need to be requested to get into the next EF.
This should get you on the right direction. You'll need to using System.Data.Entity.ModelConfiguration for the EntityMap
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Test>().MapSingleType(m => EntityMap.Row(
EntityMap.Column(m.Tryptophan60, "Tryptophan/60")
)).ToTable("MineralDefinition");
}
You might have to add modelBuilder.IncludeMetadataInDatabase = false; in order to avoid model change errors.