There are two ORM in my .NET Core 6.0 project, Entity Framework and Dapper. I would like to share the Fluent API with Dapper. Is there any way to do this? The main goal is to centralize the mapping of the entities.
It would be interesting if the dapper took advantage of the hasColumnName("String_Column_Name") of the fluent Api, to map the object according to Entity Framework.
For example: I have a view VW_PERSONAL_DATA that returns something like this:
ID, NAME, NAME_AND_SURNAME
and an entity:
public class PersonalData
{
public string? id { get; set; }
public string? MainName { get; set; }
public string? FullName { get; set; }
}
I have a Fluent API configuration file that looks like this:
public class PersonalDataConfig : IEntityTypeConfiguration<PersonalData>
{
public void Configure(EntityTypeBuilder<PersonalData> builder)
{
builder.ToView("VW_PERSONAL_DATA")
.HasNoKey();
builder.Property<string>(o => o.Id)
.HasColumnName("ID");
builder.Property<string>(o => o.MainName )
.HasColumnName("NAME");
builder.Property<string>(o => o.FullName)
.HasColumnName("NAME_AND_LASTNAME");
}
}
In my DbContext, I have this code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new PersonalDataConfig ());
}
When I use Entity Framework, it works.
private readonly IReadRepository<PersonalData> _repository;
var pd = (await _repository.ListAsync()).FirstOrDefault();
But when I use Dapper, it fails to map, and it's logical, because Dapper doesn't have its mapping configured:
private readonly IDapperRepository _repository;
var pd = await _repository.QueryFirstOrDefaultAsync<PersonalData>($"SELECT * FROM VW_PERSONAL_DATA", cancellationToken: cancellationToken);
I would like to take advantage of the mapper that Entity Framework uses also for Dapper, without having to rewrite everything again.
Centralizing all mappings in one place. Remembering that I use the Fluent API for migration. Therefore, it would be interesting to preserve it.
Please, I don't want us alias in query.
I hope that was clear. Thanks.
After the model is built, examine the mappings to drive a custom dapper mapping.
foreach (var e in db.Model.GetEntityTypes())
{
Console.WriteLine($"{e.Name} -> {e.GetTableName()}");
foreach (var p in e.GetProperties())
{
Console.WriteLine($"{p.Name} -> {p.GetColumnName()}");
}
}
Related
I have an old project developed with the Entity Framwork 4.0 which uses some complex types. Since I need to migrate it to .NET Core, I created a new project, installed all required libraries and used the command
PM> Scaffold-DbContext "Server=ServerInstance; Database=DBName; Trusted_Connection=True;"
Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
on the existing database in order to create the new models. The problem is that it does not generate the complex type classes.
I can imagine I could replace the generated properties with by hand created complex types, use the [ComplexType] attribute and set the property using OwnsOne command, but I was wandering if there is a sort of auto generation option.
Is there a way to do this?
I created a second partial class in order to add custom stuff to the created one. Added also a partial OnModelCreatingPartial method in which I define my complex types.
Here a snippet:
[Owned]
public class MyComplexType
{
public bool AField { get; set; }
public string BField { get; set; }
}
public partial class MyMainEntity
{
public MyComplexType MyComplexType { get; set; }
}
public partial class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("myConnectionString",
opts => opts.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds)
);
}
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyMainEntity>().OwnsOne(e => e.MyComplexType, myComplexType =>
{
myComplexType.Property(p => p.AField).HasColumnName("ColumnNameFieldA");
myComplexType.Property(p => p.BField).HasColumnName("ColumnNameFieldB");
});
}
}
You can specified a Unique Key with Fluent Api:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasIndex(u => u.Nickname)
.IsUnique();
}
}
public class User
{
public int UserId { get; set; }
public string Nickname { get; set; }
}
But can you do it with Data Annotations?
Edit
Methods change in EF7 Beta 8:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Index(u => u.Nickname)
.Unique();
}
I'm afraid create an Index using Data Annotation is not still supported in EF 7. Check this link.
I also tried to find some info related with that subject in the last releases and I couldn't find anything.
EF 7 beta 8 release notes
EF 7 RC1 release notes
I found now a post from one of the EF developers (divega) saying this:
In EF7 we support defining indexes using the fluent API but not an
attribute, at least no yet. The IndexAttribute you are possibly
referring to is something we added to the EF 6.x package at some point
but never really became a standard DataAnnotation.
We don't want to copy the original attribute from EF6 as is because
there are a few things in it that we would like to change. Also,
having it in DataAnnotations directly would likely make more sense
than adding it to the EF7 package.
I should mention though that it is highly unlikely that we will add
IndexAttribute in the EF7 RTM timeframe.
Update 1
Apparently this is a feature that will not be added to EF Core, at least for now.
From EF Core documentation:
Indexes can not be configured using Data Annotations.
But you can do it using Fluent Api:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasIndex(b => b.Url)
.HasName("Index_Url");
}
In the absence of built in support, you can use a custom attribute of your own to annotate model properties and apply in OnModelCreating():
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var prop in entity.GetProperties())
{
var index = prop.PropertyInfo.GetCustomAttribute<IndexAttribute>();
if (index != null)
{
entity.AddIndex(prop);
}
}
}
}
With a simple marker attribute class:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IndexAttribute : Attribute
{
}
Then in your model class, just add the attribute to create a secondary index:
public class User
{
public int UserId { get; set; }
[Index]
public string Nickname { get; set; }
}
We use EntityFramework 6.1 with CodeFirst in our web mvc application (StdWebApp). Now we want to make a new custom version of this application (CustomWebApp) .
The CustomWebApp will use most of the code of the standard one, in it's domain model it will extend the Person class.
In CustomDomain we make implement a new DbContext that must connect with the database of the custom app (CustomSqlDb).
In (C#) code there is no problem that there is a Person in Domain and in CustomDomain. However we have not been able to devise a mapping for Person in the Custom DbContext that will:
Create a single "Person" table.
Contains fields form "CustomDomain.Person" AND those from "Domain.Person".
We tried some variants like this:
modelBuilder.Entity<Person>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Person");
}
);
using this document as our inspiration msdn mapping types
But EF complains about the simple name beeing equal.
Obviously we could rename the "Person" in "CustomDomain" to "PersonCustom" but that could lead to a lot of silly names if we have to do this again in the future like "PersonCustomExtraSpecial" etc.
Thoughts anyone?
UPDATE
we tried the solution suggested by mr100, here is the complete code:
namespace Domain
{
public class Person
{
public int Id { get; set; }
public string Stuff { get; set; }
}
}
namespace CustomDomain
{
public class Person : Domain.Person
{
public string ExtraStuff { get; set; }
}
}
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
This still result in the error
The type 'CustomDomain.Person' and the type 'Domain.Person' both have the same simple name of 'Person' and so cannot be used in the same model. All types in a given model must have unique simple names. Use 'NotMappedAttribute' or call Ignore in the Code First fluent API to explicitly exclude a property or type from the model.
So we added the following code:
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Domain.Person>();
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
Still same result.
To achieve this your DbContext class in CustomWebApps should have property People defined like this:
public DbSet<CustomDomain.Person> People {get; set;}
and no property:
public DbSet<Domain.Person> People {get; set;}
even if it comes from StdWebApp DbContext class from which CustomWebApp DbContext class may derive (if that is the case for you). Additionally you may set properly table name:
modelBuilder.Entity<Person>().ToTable("Person");
I'm dynamically creating my DbContext by iterating over any entities that inherit from EntityBase and adding them to my Context:
private void AddEntities(DbModelBuilder modelBuilder)
{
var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var entityTypes = assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(EntityBase)) && !x.IsAbstract);
foreach (var type in entityTypes)
{
dynamic entityConfiguration = entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
EntityBase entity = (EntityBase)Activator.CreateInstance(type);
//Add any specific mappings that this class has defined
entity.OnModelCreating(entityConfiguration);
}
}
}
That way, I can have many namespaces but just one generic repository in my base namespace that's used everywhere. Also, in apps that make use of multiple namespaces, the base repository will already be setup to use all the entities in all the loaded namespaces. My problem is, I don't want to make EntityFramework.dll a dependency of every namespace in the company. So I'm calling OnModelCreating and passing the EntityTypeConfiguration to the class so it can add any mappings. This works fine and here's how I can add a mapping to tell the model that my "Description" property comes from a column called "Descriptor":
class Widget... {
public override void OnModelCreating(dynamic entity)
{
System.Linq.Expressions.Expression<Func<Widget, string>> tmp =
x => x.Description;
entity.Property(tmp).HasColumnName("Descriptor");
}
The good thing is, my entity class has no reference to EF, this method is only called once, when the context is created and if we scrap EF and go to something else in the future, my classes won't have all sorts of attributes specific to EF in them.
The problem is, it's super ugly. How can I let the model know about column mappings and keys in a simpler way than creating these Expressions to get properties to map without hard coding references to EF all over my poco classes?
You could define your own Attributes and use these to control the configuration within OnModelCreating(). You should be able to gain (using reflection) all the details you need for column mapping in one linq query a second query for the creation of the key.
public class DatabaseNameAttribute : Attribute
{
private readonly string _name;
public DatabaseNameAttribute(string name)
{
_name = name;
}
public string Name
{
get
{
return _name;
}
}
}
public class KeySequenceAttribute : Attribute
{
private readonly int _sequence;
public KeySequenceAttribute(int sequence)
{
_sequence = sequence;
}
public int Sequence
{
get
{
return _sequence;
}
}
}
[DatabaseName("BlogEntry")]
public class Post
{
[DatabaseName("BlogId")]
[KeySequence(1)]
public int id { get; set; }
[DatabaseName("Description")]
public string text { get; set; }
}
I am using EF Code first with database first approach.
"with Database.SetInitializer(null);"
My table has two columns createddate and amendddate. They are managed by SQL Server using triggers. The idea is that when data entry happens then these columns gets data via triggers.
Now What I want to do is to make this read only from EF Code first point of view. I.e. I want to be able to see the createddate and ameneded dates from my app but I dont want to amend these data.
I have tried using private modifiers on setter but no luck.When I try to add new data to the table it tried to enter DateTime.Max date to the database which throws error from SQL server.
Any idea?
You cannot use private modifiers because EF itself needs to set your properties when it is loading your entity and Code First can only do this when a property has public setter (in contrast to EDMX where private setters are possible (1), (2)).
What you need to do is mark your for CreatedDate with DatabaseGeneratedOption.Identity and your AmendDate with DatabaseGeneratedOption.Computed. That will allow EF to correctly load data from the database, reload data after insert or update so that entity is up to date in your application and at the same time it will not allow you to change the value in the application because the value set in the application will never be passed to the database. From an object oriented perspective it is not a very nice solution but from the functionality perspective it is exactly what you want.
You can do it either with data annotations:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedDate { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime AmendDate { get; set; }
Or with fluent API in OnModelCreating override in your derived context:
modelBuilder.Entity<YourEntity>()
.Property(e => e.CreatedDate)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<YourEntity>()
.Property(e => e.AmendDate)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
EF core 1.1 or later versions yes you can use read only property in poco classes. What you need to do is using backing-field.
public class Blog
{
private string _validatedUrl;
public int BlogId { get; set; }
public string Url
{
get { return _validatedUrl; }
}
public void SetUrl(string url)
{
using (var client = new HttpClient())
{
var response = client.GetAsync(url).Result;
response.EnsureSuccessStatusCode();
}
_validatedUrl = url;
}
}
class MyContext : DbContext
{
public DbSet Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasField("_validatedUrl");
}
}
and fluent api...
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasField("_validatedUrl")
.UsePropertyAccessMode(PropertyAccessMode.Field);
Take a look here..