Setting MaxLength for all strings in Entity Framework code first - c#

I'm developing a code-first database, using Entity Framework 6.
I know I can set [MaxLength(myLen)] on the property of a model.
What I wondered, is if this is possible to do in a filter or a custom attribute, so that all strings take on a default, of say 250, unless specified directly on the property.
Failing this, is there a way to change the default of nvarchar(max)?

Entity Framework introduced Custom Code First Conventions for this in 6.1
modelBuilder.Properties<string>()
.Configure(c => c.HasMaxLength(250));
Conventions operate in a last wins manner and the Fluent API and Data Annotations can be used to override a convention in specific cases

You can do this, which ensures all strings are the maximum length supported by the database provider:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(p => p.IsMaxLength());
}
Add this method (or modify the existing one) in your DbContext class.

In EF6 you can use a custom code first convention, but you will also need to have a way to specify nvarchar(max) data type to a string property. So, I came up with the following solution.
Also see:
https://msdn.microsoft.com/en-us/data/jj819164#order
/// <summary>
/// Set this attribute to string property to have nvarchar(max) type for db table column.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class TextAttribute : Attribute
{
}
/// <summary>
/// Changes all string properties without System.ComponentModel.DataAnnotations.StringLength or
/// Text attributes to use string length 16 (i.e nvarchar(16) instead of nvarchar(max) by default).
/// Use TextAttribute to a property to have nvarchar(max) data type.
/// </summary>
public class StringLength16Convention : Convention
{
public StringLength16Convention()
{
Properties<string>()
.Where(p => !p.GetCustomAttributes(false).OfType<DatabaseGeneratedAttribute>().Any())
.Configure(p => p.HasMaxLength(16));
Properties()
.Where(p => p.GetCustomAttributes(false).OfType<TextAttribute>().Any())
.Configure(p => p.IsMaxLength());
}
}
public class CoreContext : DbContext, ICoreContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Change string length default behavior.
modelBuilder.Conventions.Add(new StringLength16Convention());
}
}
public class LogMessage
{
[Key]
public Guid Id { get; set; }
[StringLength(25)] // Explicit data length. Result data type is nvarchar(25)
public string Computer { get; set; }
//[StringLength(25)] // Implicit data length. Result data type is nvarchar(16)
public string AgencyName { get; set; }
[Text] // Explicit max data length. Result data type is nvarchar(max)
public string Message { get; set; }
}

In this code ModelBuilder class defines the shape of your entities, the relationships between them, and how they map to the database.
public class WebsiteDBContext : DbContext
{
public WebsiteDBContext(DbContextOptions<WebsiteDBContext> options) : base(options)
{
}
public DbSet<Global> Globals { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
// it should be placed here, otherwise it will rewrite the following settings!
base.OnModelCreating(builder);
builder.Entity<Global>();
builder.Entity<Global>(entity =>
{
entity.Property(global => global.MainTopic).HasMaxLength(150).IsRequired();
entity.Property(global => global.SubTopic).HasMaxLength(300).IsRequired(false);
entity.Property(global => global.Subject).IsRequired(false);
entity.Property(global => global.URL).HasMaxLength(150).IsRequired(false);
});
}
}

Related

Foreign key in Owned entity: "There is no corresponding CLR property or field"

This is a tale of optional owned entities and foreign keys.
I'm working with EF 5 (code first) and I do this :
public class Parent {
public Guid Id { get; private set; }
public OwnedType1? Owned1 { get; private set; }
public OwnedType2? Owned2 { get; private set; }
public Parent(Guid id, OwnedType1? owned1, OwnedType2? owned2) {
Id = id; Owned1 = owned1; Owned2 = owned2;
}
}
public class OwnedType1 {
public Guid? OptionalExternalId { get; private set; }
public OwnedType1 (Guid? optionalExternalId) {
OptionalExternalId = optionalExternalId;
}
}
public class OwnedType2 {
public Guid? OptionalExternalId { get; private set; }
public OwnedType2 (Guid? optionalExternalId) {
OptionalExternalId = optionalExternalId;
}
}
public class Shared {
public Guid Id { get; private set; }
public Shared (Guid id) {
Id = id;
}
}
Now, the configuration :
//-------- for Parent ------------
public void Configure(EntityTypeBuilder<Parent> builder) {
builder
.ToTable("Parents")
.HasKey(p => p.Id);
builder
.OwnsOne(p => p.Owned1)
.HasOne<Shared>()
.WithMany()
.HasForeignKey(x => x.OptionalExternalId);
builder
.OwnsOne(p => p.Owned2)
.HasOne<Shared>()
.WithMany()
.HasForeignKey(x => x.OptionalExternalId);
}
//-------- for OwnedType1 ------------
// (there's no builder as they're owned and EntityTypeBuilder<Parent> is enough)
//-------- for OwnedType2 ------------
// (there's no builder as they're owned and EntityTypeBuilder<Parent> is enough)
//-------- for Shared ---------------
public void Configure(EntityTypeBuilder<Shared> builder) {
builder
.ToTable("Shareds")
.HasKey(p => p.Id);
}
Side note : If you're wondering why OwnedType1 and OwnedType2 don't each have a property called 'ParentId', it's because it's created implicitly by the "OwnsOne".
My problem is this :
When I create a new Migration, then OwnedType1 works like a charm, but for OwnedType2 (which is quasi-identical), I get his error :
The property 'OptionalExternalId' cannot be added to the type
'MyNameSpace.OwnedType2' because no property type was specified and
there is no corresponding CLR property or field. To add a shadow state
property, the property type must be specified.
I don't understand what it's complaining about. And why it's complaining only for one of them.
I know that you probably can't work it out with this simplified version of my schema, but what I'm asking is what you think it might be (follow your guts of EF guru) :
Some missing constructor?
Incorrect visibility on one of the fields?
Bad navigation definition?
A typo?
Something tricky (like : If you're going to have TWO different entity classes having a one-to-many relation with Shared, then they can't use the same name for external key. Or I need to use a composite key. Or whatnot).
It was a configuration issue that had nothing to do with Owned entities. Another case of "EF error message is obscure but issue is somewhere there in plain sight".
Unfortunately I don't remember how I fixed it. But it was along the lines of "Need an extra constructor with all the paramaters" or "one of the fields had a different name in the constructor parameters" or one of those classic EF mishaps.

Setting an entity property DB properties to IsRequired() and defaultValue: "('')" workaround?

I'm new to EF Core 2 and am trying to create Entity properties that are not null with a default value of a blank space. I'm using the fluent API. The end result is a default data binding of N'('''')' on the SqlServer side. I'm fairly certain this is a bug, but is there a known workaround?
I've tried string manipulation to account for the extra apostrophes as well as a variable. However, when I look at the migration script, it's correct so it seems it's happening between the update-database and server communication process and is out of my control.
public class EfficacyDBContext : DbContext
{
public virtual DbSet<Person> Person { get; protected set; }
public virtual DbSet<PersonType> PersonType { get; protected set; }
public EfficacyDBContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PersonEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new PersonTypeEntityTypeConfiguration());
}
}
public class PersonEntityTypeConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.ToTable("Person.Person");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).ValueGeneratedOnAdd();
builder.Property(x => x.FirstName).HasMaxLength(50)
.HasDefaultValue("('')").IsRequired();
}
}
On the SqlServer side I expect the field to default to a blank value when nothing is entered. However, because of the skewy data binding, I get a default value of ('')
You are using HasDefaultValue method incorrectly. It expects a value while your are passing SQL fragment. The later is supported, but by a different method - HasDefaultValueSql.
So either use
.HasDefaultValue("")
or
.HasDefaultValueSql("('')")
Reference: EF Core documentation - Default Values

.Net Core Entity Framework - Discrimator TPH

I'm currently trying to write to a table which inherits from an abstract base class. When I try to do this I get the following error (The ContactMethod property is the discriminator):
System.Data.SqlClient.SqlException: Invalid column name 'ContactMethod'.
EmailContactDetails.cs:
public class EmailContactDetail : ContactDetail
{
[ApiMember(Description = "The Contact Method")]
public override ContactMethod ContactMethod => ContactMethod.Email;
[ApiMember(Description = "Email Address")]
public string EmailAddress { get; set; }
}
EmailContactDetailConfiguration.cs:
public class EmailContactDetailsConfiguration : IEntityTypeConfiguration<EmailContactDetail>
{
public void Configure(EntityTypeBuilder<EmailContactDetail> builder) => Configure(builder, "dbo");
public void Configure(EntityTypeBuilder<EmailContactDetail> builder, string schema)
{
builder.Property(x => x.EmailAddress).HasColumnName("EmailAddress").HasColumnType("nvarchar(255)");
}
}
ContactDetail.cs:
public abstract class ContactDetail
{
[ApiMember(Description = "The Identifier")]
public Guid Id { get; set; }
[ApiMember(Description = "The Contact Method")]
public virtual ContactMethod ContactMethod { get; set; }
}
ContactDetailConfiguration.cs
public class ContactDetailsConfiguration : IEntityTypeConfiguration<ContactDetail>
{
public void Configure(EntityTypeBuilder<ContactDetail> builder) => Configure(builder, "dbo");
public void Configure(EntityTypeBuilder<ContactDetail> builder, string schema)
{
builder.ToTable("ContactDetails", schema);
// Table per hierarchy. all subclasses share the same db table for performance.
builder.HasDiscriminator(x => x.ContactMethod)
.HasValue<EmailContactDetail>(ContactMethod.Email);
builder.Property(x => x.Id).HasColumnName("Id").IsRequired().HasColumnType("uniqueidentifier").ValueGeneratedOnAdd();
}
}
I've tried hiding the discriminator "ContactMethod" by adding the following to the ContactDetailConfiguration.cs file:
builder.Ignore(x => x.ContactMethod);
Once I've done that I end up with the following error
The entity type 'EmailContactDetail' is part of a hierarchy, but does not have a discriminator property configured.
You shouldn't hide the property configured as TPH discriminator from EF because it is essential for EF Core implementation of the TPH strategy.
The initial error simply indicates that your model and database are out of sync. It's true that by convention EF Core uses string shadow property and column called Discriminator. But the whole purpose of HasDiscriminator fluent API is to allow changing the discriminator property/column type, as well as mapping it to an existing property of your entity model.
Which is the case here. You've told EF Core to use your existing property ContactMethod as discriminator, hence EF Core is looking for column named ContactMethod in the database table. So to resolve the issue, simply update your database from the model (using the usual procedure when model is changed - add new migration, update database etc).

Get ignored properties in Entity Framework

I work on a framework with EF. I want to get all ignored properties of an entity to build some special queries. How can I do it?
public class Customer
{
public int Id { get; set; }
public DateTime BirthDate { get; set; }
public int Age { get; set; }
}
public class CustomerContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
public static class DbContextExtensions
{
public static List<string> GetIgnoredProperties(this DbContext context, string entityTypeName)
{
// ???
}
}
I know this is not answering your original question, and in my comments I mentioned that you should use reflection, but that was only because I read your question wrong.
Here is an alternative using reflection, for if you do not come right.
If you assign the [NotMapped] attribute to the properties on your class that you would like to ignore, you could possibly retrieve all [NotMapped] properties using reflection. Below is an example of how this could be achieved.
var resultArray = yourClassInstance.GetType().GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(NotMappedAttribute)));
Hope this helps you in some way.
You can achieve what you want by calling the DbModelBuilder.Build. It will create a DbModel base on configuration setup by the DbModelBuilder. The DbModel expose a ConceptualModel that hold the types used by the context. The EdmModel hold each type that are declared in the context, and for each type, it hold the properties that has not been ignored by the DbModelBuilder during it's configuration. So, to achieve what you want, you have to intersect the properties of each entity type with those present in the EdmModel. It will give the delta between them, thefore the ignored properties. Here an example :
public class CustomerContext : DbContext
{
private static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> _ignoredProperties;
/// Hold the ignored properties configured from fluent mapping
public static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> IgnoredProperties
{
get
{
return _ignoredProperties;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
// Build ignored properties only if they are not
if (_ignoredProperties == null)
{
var model = modelBuilder.Build(this.Database.Connection);
var mappedEntityTypes = new Dictionary<Type, IReadOnlyCollection<PropertyInfo>>();
foreach (var entityType in model.ConceptualModel.EntityTypes)
{
var type = Type.GetType(entityType.FullName);
var typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var mappedProperties = entityType.DeclaredProperties.Select(t => t.Name)
.Union(entityType.NavigationProperties.Select(t => t.Name));
mappedEntityTypes.Add(type, new ReadOnlyCollection<PropertyInfo>(
typeProperties.Where(t => !mappedProperties.Contains(t.Name)).ToList()));
}
_ignoredProperties = new ReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>>(mappedEntityTypes);
}
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
The IgnoreProperties property is a singleton that will be initialized the first time you will use the context. It will be null before that, so will have to ensure that nothing use it until it's initialized. It's readonly, so you don't have to worrie about accidental clear of the collection. The entity type is used as key, and the value expose a collection that hold ignored properties. Example of use :
var properties = CustomerContext.IgnoredProperties[typeof(Customer)];
Cons :
With this approach is that the DbModel will be built twice, one time to gather the ignored properties, and second time by EntityFramework when the DbCompiledModel will be cached for futur ObjectContext creation. It can have an impact on the cold start of the DbContext, it means that the fist time you will execute a query over your context, it will be a bit slower. It will depend on the size of the DbContext. Warm queries should not suffer. OnModelCreating will be called once anyway.
Pros :
All changes made on de DbModelBuilder configuration will be automatically reflected in the IgnoredProperties property.

Fluent NHibernate ignore property inside the ClassMap, using FluentMappings

I am using NHibernate 3.1 and Fluent NHibernate as ORM in my project. I need to have a property of a POCO ignored by Fluent NHibernate. At first, my post might look as exact duplicate of this question, but it is not.
My complications come first from the fact that the POCOs are defined in a different assembly than the mapping and I am using fluent mappings for my POCOs. I have additional requirement not to write ingore-property code where the session factory configuration takes place (this happens at a centralized place outside the modules), but as part of the module that defines the mappings. Ideally, I believe the right place would be the concrete ClassMap implementation, since it knows exactly how to describe a POCO to the ORM.
However, I am stuck on this mainly because this is my first impact with NHibernate and its fluent API. Up to now I am having very good impression of its capabilities and extensibility, and I hope there is a way to achieve my requirement in a way that the mapping related code is encapsulated in its corresponding module.
Here is my configuration, from a centralized place:
List<Assembly> assemblies = GetModules().Select(x => x.GetType().Assembly).ToList();
ISessionFactory nhibernateSessionFactory = Fluently
.Configure()
.Mappings(m => assemblies.ForEach(asm => m.FluentMappings.AddFromAssembly(asm)))
.Database(
MsSqlConfiguration.MsSql2005
.ShowSql()
.ConnectionString(DatabaseConfig.Instance.ConnectionString))
.ExposeConfiguration(c => new SchemaUpdate(c).Execute(true, true))
.BuildSessionFactory();
I use standard class mappings that inherit from ClassMap:
public class User
{
public virtual int ID { get; set; }
public virtual String Username { get; set; }
public virtual String Password { get; set; }
public virtual DateTime DateCreated { get; set; }
public virtual DateTime DateModified { get; set; }
// Must ignore
public string ComputedProperty { get { ... } }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("User");
Id(x => x.ID).GeneratedBy.Identity();
Map(m => m.Username).Not.Nullable().Length(255).UniqueKey("User_Username_Unique_Key");
Map(m => m.Password).Not.Nullable().Length(255);
Map(m => m.DateCreated).Not.Nullable();
Map(m => m.DateModified).Not.Nullable();
}
}
I know this post is bit old, but I post anyway since I didn't find any up todate posts on the subject.
I guess the easiest way should be to add an attribute to each property we dont want to be persisted to a table. By add a extension that check if it has for eg. has a [NoEntity] attibute.
/// <summary>
/// Tells a single Property to not be persisted to table.
/// </summary>
public class NoEntity : Attribute { }
/// <summary>
/// Extension to ignore attributes
/// </summary>
public static class FluentIgnore
{
/// <summary>
/// Ignore a single property.
/// Property marked with this attributes will no be persisted to table.
/// </summary>
/// <param name="p">IPropertyIgnorer</param>
/// <param name="propertyType">The type to ignore.</param>
/// <returns>The property to ignore.</returns>
public static IPropertyIgnorer SkipProperty(this IPropertyIgnorer p, Type propertyType)
{
return p.IgnoreProperties(x => x.MemberInfo.GetCustomAttributes(propertyType, false).Length > 0);
}
}
And in the fluent config setup:
return Fluently.Configure()
.Database(DatabaseConfig)
.Mappings(m => m.AutoMappings.Add(AutoMap.Assembly(typeof(IDependency).Assembly)
.OverrideAll(p => {
p.SkipProperty(typeof(NoEntity));
}).Where(IsEntity)))
.ExposeConfiguration(ValidateSchema)
.ExposeConfiguration(BuildSchema)
.BuildConfiguration();
I think you are right that the ClassMap is the best place to ignore this property.
Example:
.Override<Shelf>(map =>
{
map.IgnoreProperty(x => x.YourProperty);
});
Documentation: https://github.com/jagregory/fluent-nhibernate/wiki/Auto-mapping#ignoring-properties
As far as getting the mappings from another assembly, it should be as easy as something like this (depending on your current configuration):
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<ProvideClassFromYourOtherAssembly>();
});
Will not Justin. This is the thing with this extension. Just the property you want gets ignored.
public class Person : IEntity{
public virtual string Name{..}
public virtual string Lastname{..}
[NoProperty]
public virtual string FullName{ // Not created property
get { return Name + " " + Lastname; }
}
}
public class Group : IEntity{
public virtual string FullName{..} //Created property
}

Categories

Resources