Open omnilogix opened 3 years ago
Perhaps the more correct question is: Where do I find ODataEfCoreModelBuilder
@omnilogix
You can call "HasKey" to set the key property as below:
Type t= typeof(foo);
PropertyInfo keyProperty = cs.GetProperty("Id");
var config= builder.AddEntityType(t);
config.HasKey(keyProperty);
odataBuilder.AddEntitySet(t.Name, config);
And by the way, What content do you think in "ODataEfCoreModelBuilder"?
@xuzhg - Thanks for the example, but this is exactly what I am trying to avoid. Building the model in this way is duplicating the effort that was already put into my EF Core model builder. What would be very helpful is to have:
builder.ODataEfCoreModelBuilder(myDbContext);
This would presumably create a context and examine the metadata for each DbSet<> and construct the odata model to match. For now I have removed the EF Core fluent api calls that defined the primary keys and foreign keys, and placed [Key] and [ForeignKey] attributes onto the class properties. I am currently creating the model like this to avoid manually defining the odata model:
...
var odataBuilder = new ODataConventionModelBuilder();
var dbSets = typeof(myDbContext).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.PropertyType.IsGenericType
&& typeof(DbSet<>).IsAssignableFrom(x.PropertyType.GetGenericTypeDefinition()));
foreach (var dbSet in dbSets)
{
var pocoType = dbSet.PropertyType.GenericTypeArguments[0];
var entitySet = odataBuilder.AddEntitySet(pocoType.Name, odataBuilder.AddEntityType(pocoType));
}
return odataBuilder.GetEdmModel();
It's not bad, but I would like to see more compatibility with EF Core. If this functionality could be added to an extension method and packaged as a nuget (Microsoft.AspNetCore.OData.EfCore), it would be ideal. Since my domain model design comes first, it seems the natural place to describe the schema. After that, I would like odata to be able to scaffold from this existing schema whether it was declared using Attributes, or Fluent api.
@omnilogix I had exactly the same need as you, actually several years ago. So I just programmed it using reflection. You can just do the same. I made my own builder class inheriting from the convention builder. Probably you should do the same.
Any news on this investigation? I am facing the same problem... @henrikdahl8240, do you have any skeleton code to share to make this builder with reflection?
@ericcanadas, yes, it's OK. Here you go.
Perhaps you may respond, if it works for you?
public class HDODataConventionModelBuilder<T> : ODataConventionModelBuilder
where T : DbContext
{
internal HDODataConventionModelBuilder(T dbContext)
{
MethodInfo method = this.GetType().GetMethod("EntitySet");
typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(x => x.PropertyType.IsGenericType && x.PropertyType.ContainsGenericParameters == false && x.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)).ToList().ForEach(x =>
{
Type argType = x.PropertyType.GetGenericArguments()[0];
MethodInfo generic = method.MakeGenericMethod(argType);
var res = generic.Invoke(this, new object[] { x.Name });
});
}
}
Best regards,
Henrik Dahl
Hi @henrikdahl8240, It works great !! Thanks for your help
Hello @ericcanadas!
I am glad to hear, that you like it.
There is a detail, I would like to address to you.
As you may see, the where clause uses DeclaredOnly
. In case you don't know, DeclaredOnly
includes only properties from the current class and not its base classes.
It means, that if you make an inheritance hierarchy like DbContextHavingDBSetsWhichShouldBeExposedViaOData
: DbContextHavingDBSetsWhichShouldNOTBeExposedViaOData
: DbContext
, only the DbSet properties of the leaf level DbContext
will be served via OData. ´DbSet
properties of DbContextHavingDBSetsWhichShouldNOTBeExposedViaOData
will not be exposed via OData. Personally I use DbContextHavingDBSetsWhichShouldNOTBeExposedViaOData
for my IdP entity sets, because they should obviously not be exposed via OData.
Best regards,
Henrik Dahl
Ok @henrikdahl8240 ! Thanks for the clarification.
database first will result
public partial class GlobalSetting
{
public string Name { get; set; } = null!;
public object? Value { get; set; }
public DateTime Timestamp { get; set; }
public string LastModifiedBy { get; set; } = null!;
}
public partial class ODataDbContext : DbContext
{
public virtual DbSet<GlobalSetting> GlobalSettings { get; set; }
......
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<GlobalSetting>(entity =>
{
entity.HasKey(e => e.Name).HasName("PK__GlobalSe__737584F7B877EFE6");
entity.ToTable("GlobalSettings", "dt");
entity.Property(e => e.Name)
.HasMaxLength(300)
.IsUnicode(false);
entity.Property(e => e.LastModifiedBy)
.HasMaxLength(128)
.HasDefaultValueSql("(user_name())");
entity.Property(e => e.Timestamp).HasDefaultValueSql("(sysutcdatetime())");
entity.Property(e => e.Value).HasColumnType("sql_variant");
});
......
}
}
so best to convert the configuration in ModelBuilder
to ODataConventionModelBuilder
var odataBuilder = new ODataConventionModelBuilder();
When using the AddEntitySet(), my endpoints fail with the error: System.InvalidOperationException: 'The entity set 'foo' is based on type 'foo' that has no keys defined.'
I am using Ef Core with fluent api to set key and navigation attributes
How can I dynamically configure each EntitySet for my DbContext? Explicitly defining each of the hundreds of DbSet<> in my DbContext is both time consuming and error prone.