I want to seed data with EfCore to my Owned Entities who can be nullable
Entities:
public class RootEntity
{
protected RootEntity() { }
public Guid Id { get; set; }
public OwnedEntityLevel1? OwnedEntityLevel1 { get; set; } // can be nullable
}
public class OwnedEntityLevel1
{
public Guid Id { get; set; }
}
Model configuration for DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<RootEntity>(b =>
{
b.OwnsOne(x => x.OwnedEntityLevel1, ob =>
{
ob.HasData(RootEntity.All.Select(x => new
{ x.OwnedEntityLevel1?.Id, RootEntityId = x.Id }));
});
b.HasData(RootEntity.All.Select(x => new { x.Id }));
});
}
When i try to create my migration with:
dotnet ef migrations add Initial --context NullableObjectDbContext -o Migrations/NullableObject
i receive the error:
The seed entity for entity type 'OwnedEntityLevel1' cannot be added because no value was provided for the required property 'Id'.
The message oviously is correct. But i do not understand if you could seed nullable objects with .HasData somehow?
The data i am trying to seed:
public static RootEntity PredefinedEntity11 { get; } =
new(
Guid.Parse("96e1d442-bdd0-4c6f-9d01-624b27abbac3"),
new OwnedEntityLevel1
{
Id = Guid.Parse("8f8eea73-0b43-412a-b0aa-a9338db6e067")
}
);
public static RootEntity PredefinedEntity12 { get; } =
new(
Guid.Parse("aae51dac-016e-472e-ad51-2f09f8cb9fbb"),
null! // When i add this the migration fails with The seed entity for entity type 'OwnedEntityLevel1' cannot be added because no value was provided for the required property 'Id'
);
public static IReadOnlyList<RootEntity> All { get; } =
new List<RootEntity>(new[] { PredefinedEntity11, PredefinedEntity12 }).AsReadOnly();
I my normal program flow i can add nullable objects without a problem:
var ctx = new NullableObjectDbContext();
var rootEntity = new RootEntity(Guid.NewGuid(), null);
ctx.Add(rootEntity);
ctx.SaveChanges();
I have created a minimal reproducible example here: https://github.com/enterprisebug/EfCoreHasDataNestedOwnedTypes/tree/main/EfCoreHasDataNestedOwnedTypes/NullableObject
Model data seeding with anonymous types matches properties by both name and type.
In your case, even though the seeding type has property called Id, its type is different from the type of the Id property of the seeded entity (Nullable<Guid> inferred from ?. operator vs Guid), hence is not mapped and is generating the confusing error message.
new
{
x.OwnedEntityLevel1?.Id, // Guid? Id
RootEntityId = x.Id // Guid RootEntityId
}
The solution is to generate and populate a Guid Id property in the anonymous type by first filtering out null objects, e.g. (null forgiving operator is used to suppress the NRT warning):
ob.HasData(RootEntity.All
.Where(x => x.OwnedEntityLevel1 != null)
.Select(x => new
{
x.OwnedEntityLevel1!.Id, // Guid Id
RootEntityId = x.Id // Guid RootEntityId
}));
Related
I'm writing an MVC app in ASP.NET with the help of EF and I'm trying to seed my database. I have the following model:
public class Team
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ForeignKey("ParentTeam")]
public int? ParentTeamId { get; set; }
[Required(ErrorMessage = "Cannot create a Team without a name")]
[Index(IsUnique = true)]
[MaxLength(30)]
public string Name { get; set; }
public IEnumerable<string> Members { get; set; }
public virtual Team ParentTeam { get; set; }
public Team() { }
public Team(string name)
{
Name = name;
}
}
My migration says:
var team = new Team("Admin");
var team2 = new Team("Test Team");
var team3 = new Team("Test Team 2");
context.Teams.AddOrUpdate(t => t.Name, team, team2, team3);
context.SaveChanges();
And then, when I run Update-Database, I get:
System.Data.SqlClient.SqlException: Cannot insert duplicate key row in
object 'dbo.Teams' with unique index 'IX_Name'. The duplicate key
value is (Admin).
It's a little confusing - I thought I told AddOrUpdate to identify rows to update by their names, but this does not happen. I cannot add Name to Team's primary key, because it has a self-referencing foreign key (I could add ParentTeamName as a property, but I don't feel that it should be necessary). Am I misunderstanding the behaviour of AddOrUpdate? Did I specify the condition wrong?
I had the exact same reason. In my case, it was working fine, until I needed to use an Unique Index, when it broke.
My solution was to create a CustomAddOrUpdate method where I try to find the existing instance first based on a Where predicate. If I find it, I just update the properties and if not, it is added to the context.
However, before updating the instance, I had to copy the key values from the original instance to the new instance, to avoid an EF exception telling you cannot change key properties.
Here are the code snippets:
1) First the code in the context class
public void CustomAddOrUpdate<TEntity>(Expression<Func<TEntity, bool>> whereExpression, TEntity entity) where TEntity : class
{
var entitySet = this.EntitySet<TEntity>();
var foundInstance = entitySet.Where(whereExpression).FirstOrDefault();
if (foundInstance != null)
{
CopyKeyProperties<TEntity>(foundInstance, entity);
Entry(foundInstance).CurrentValues.SetValues(entity);
}
else
{
entitySet.Add(entity);
}
}
private void CopyKeyProperties<TEntity>(TEntity source, TEntity target) where TEntity : class
{
string[] keys = this.GetKeyNames<TEntity>();
foreach(var keyName in keys)
{
Entry(target).Property(keyName).CurrentValue = Entry(source).Property(keyName).CurrentValue;
}
}
2) Then on my seed code:
var entityList = new List<MyExempleEntity>()
{
new MyExampleEntity { Prop1 = "a p1", Prop2 = "a p2" },
new MyExampleEntity { Prop1 = "b p1", Prop2 = "b p2" },
new MyExampleEntity { Prop1 = "c p1", Prop2 = "c p2" },
}
foreach(var item in entityList)
{
context.CustomAddOrUpdate<MyExampleEntity>(x => x.Prop1 == item.Prop1 && x.Prop2 == item.Prop2, item);
}
context.SaveChanges()
3) And to wrap up, here you are the code to get the KeyProperties from an entity:
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace System.Data.Entity
{
public static class DbContextExtensions
{
public static string[] GetKeyNames<TEntity>(this DbContext context)
where TEntity : class
{
return context.GetKeyNames(typeof(TEntity));
}
public static string[] GetKeyNames(this DbContext context, Type entityType)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MetadataWorkspace metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
// Get the mapping between CLR types and metadata OSpace
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
// Get metadata for given CLR type
var entityMetadata = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == entityType);
return entityMetadata.KeyProperties.Select(p => p.Name).ToArray();
}
}
}
The above code was grabbed from this blog:
https://romiller.com/2014/10/07/ef6-1-getting-key-properties-for-an-entity/
I'm using EF Code-First to an existing database method and have a IsActive field in my database. The problem is that the field is VARCHAR when it should be a boolean. I can't change the Database schema.
Example value in the database are "Y" (true) or "N" (false)
When mapping, I want to convert those values to either true/false and keep my Entity class with the boolean value.
Is this possible?
My Entity and mapping classes are the following but I would like to change the IsActive field to be a boolean.
public class Employee
{
public int ID { get; set; }
public string SSN { get; set; }
public string Email { get; set; }
public string IsActive { get; set; }
}
public class EmployeeMap : EntityTypeConfiguration<Employee>
{
public EmployeeMap()
{
this.ToTable("Employees");
this.HasKey(t => t.ID);
this.Property(t => t.ID).HasColumnName("ID_Employee");
this.Property(t => t.SSN).HasColumnName("sReference");
this.Property(t => t.Email).HasColumnName("Email");
this.Property(t => t.IsActive).HasColumnName("IsActive");
}
}
EDIT: I found no other solution than this: https://stackoverflow.com/a/6709186/1053611
As others have pointed out you need two properties, but you may be interested to know that you can make one of the properties private and still map it to the database:
private string isActive { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public bool IsActive
{
get { return isActive == "Y"; }
set { isActive = value ? "Y" : "N"; }
}
If you are using EF6 you can use a custom convention in the OnModelCreating method to map the private property
modelBuilder.Types().Configure(c =>
{
//NB the syntax used here will do this for all entities with a
//private isActive property
var properties = c.ClrType.GetProperties(BindingFlags.NonPublic
| BindingFlags.Instance)
.Where(p => p.Name == "isActive");
foreach (var p in properties)
c.Property(p).HasColumnName("IsActive");
});
References:
Mapping private properties using custom conventions
Mapping private properties without custom conventions (before EF6)
Edit:
Here's another way of identifying the private properties that should be mapped to the database:
First add the column attribute to the private property:
[System.ComponentModel.DataAnnotations.Schema.Column]
private string isActive { get; set; }
Then use the presence of that attribute to identify private properties in your OnModelCreating method:
modelBuilder.Types().Configure(c =>
{
var properties = c.ClrType
.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(propInfo =>
propInfo.GetCustomAttributes(typeof(ColumnAttribute), true).Length > 0);
foreach (var p in properties)
c.Property(p).HasColumnName(p.Name);
});
Reference: Mapping a private property with entity framework
I have a solution to actually map the type meaning additional properties are not required. As I had a similar question for DateTimes, but easily convertible for bools:
Is there a simple way using data annotations or a custom type to use a value stored as a string in SQL as a DateTime in EF?
I am in the process of creating a domain model and would like to have a "BaseEntity" class with an "Id" property (and some other audit tracking stuff). The Id property is the primary key and each Entity in my Domain Model will inherit from the BaseEntity class. Pretty straightforward stuff.....
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime LastUpdate { get; set; }
public string LastUpdateBy { get; set; }
}
public class Location : BaseEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
Using the example above, I would like to map the "Id" field to a "LocationId" column. I understand that I can use the modelBuilder to do this for each entity explicitly by doing something like this:
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
But I would like to do this for every Entity in my domain model and it would be ugly.
I tried the following bit of reflection but did not have any luck. For whatever reason, the compiler "cannot resolve symbol type":
foreach (var type in GetTypesInNamespace(Assembly.Load("Domain.Model"),"Domain.Model"))
{
modelBuilder.Entity<type>().Property(x=>x.Id).....
}
Is there a way to define a convention to override the default PrimaryKey convention to map my "Id" property to a "ClassNameId" property in the database? I am using Entity Framework 6.
You should take a look at Custom Code First Conventions. You need EF6 for it to work, but it looks like you're already using it.
Just to give you an overview, take a look at the following convention I've used to convert PascalCase names to underscore names. It includes a convention for id properties... It also includes an optional table name prefix.
public class UnderscoreNamingConvention : IConfigurationConvention<PropertyInfo, PrimitivePropertyConfiguration>,
IConfigurationConvention<Type, ModelConfiguration>
{
public UnderscoreNamingConvention()
{
IdFieldName = "Id";
}
public string TableNamePrefix { get; set; }
public string IdFieldName { get; set; }
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
var columnName = propertyInfo.Name;
if (propertyInfo.Name == IdFieldName)
columnName = propertyInfo.ReflectedType.Name + IdFieldName;
configuration().ColumnName = ToUnderscore(columnName);
}
public void Apply(Type type, Func<ModelConfiguration> configuration)
{
var entityTypeConfiguration = configuration().Entity(type);
if (entityTypeConfiguration.IsTableNameConfigured) return;
var tableName = ToUnderscore(type.Name);
if (!string.IsNullOrEmpty(TableNamePrefix))
{
tableName = string.Format("{0}_{1}", TableNamePrefix, tableName);
}
entityTypeConfiguration.ToTable(tableName);
}
public static string ToUnderscore(string value)
{
return Regex.Replace(value, "(\\B[A-Z])", "_$1").ToLowerInvariant();
}
}
You use it like this
modelBuilder.Conventions.Add(new UnderscoreNamingConvention { TableNamePrefix = "app" });
EDIT: In your case, the Apply method should be something like this:
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
if (propertyInfo.Name == "Id")
{
configuration().ColumnName = propertyInfo.ReflectedType.Name + "Id";
}
}
Try this out in your DbContext class;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name.Equals("Id"))
.Configure(c => c.HasColumnName(c.ClrPropertyInfo.ReflectedType.Name + "Id"));
}
int is the CLR Type of my Primary Key fields. I want to refer to all keys in code as Id but DBA's require keys to be Id with Table entity name prefix. Above gives me exactly what I want in my created database.
Entity Framework 6.x is required.
In Entity Framework 6 Code First:
modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
and update-database...
Change in model
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
to:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long role_id { get; set; }
Then remove this:
//modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
A start to the Dynamic approach if NOT using custom conventions
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
You can do this using reflection on the context. Pseudo Code as explanation:
Reflect Context to get a list of POCO names
For each POCO in a dbcontext.
Map Property Id -> string PocoName+Id
Here are the extensions I use for this type of solution.
// DBSet Types is the Generic Types POCO name used for a DBSet
public static List<string> GetModelTypes(this DbContext context) {
var propList = context.GetType().GetProperties();
return GetDbSetTypes(propList);
}
// DBSet Types POCO types as IEnumerable List
public static IEnumerable<Type> GetDbSetPropertyList<T>() where T : DbContext {
return typeof (T).GetProperties().Where(p => p.PropertyType.GetTypeInfo()
.Name.StartsWith("DbSet"))
.Select(propertyInfo => propertyInfo.PropertyType.GetGenericArguments()[0]).ToList();
}
private static List<string> GetDbSetTypes(IEnumerable<PropertyInfo> propList) {
var modelTypeNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.PropertyType.GenericTypeArguments[0].Name)
.ToList();
return modelTypeNames;
}
private static List<string> GetDbSetNames(IEnumerable<PropertyInfo> propList) {
var modelNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.Name)
.ToList();
return modelNames;
}
However, you will still need to employee dynamic lambda to finish.
Continue that topic here: Dynamic lambda example with EF scenario
EDIT:
Add link to another question that address the common BAse Config class approach
Abstract domain model base class when using EntityTypeConfiguration<T>
Piggybacking on #Monty0018 's answer but this just need to be updated a little if, like me, you're using Entity Framework 7 and/or SQLite.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
try
{
_builder = modelBuilder;
var typeName = typeof(T).Name;
_builder
.Entity(typeof(T))
.Property<int>("Id")
.ForSqliteHasColumnName(typeName + "Id");
}
catch (Exception e)
{
throw e;
}
}
I am trying to create a basic example using Entity Framework to do the mapping of the output of a SQL Server Stored procedure to an entity in C#, but the entity has differently (friendly) names parameters as opposed to the more cryptic names. I am also trying to do this with the Fluent (i.e. non edmx) syntax.
What works ....
The stored procedure returns values called: UT_ID, UT_LONG_NM, UT_STR_AD, UT_CITY_AD, UT_ST_AD, UT_ZIP_CD_AD, UT_CT
If I create an object like this ...
public class DBUnitEntity
{
public Int16 UT_ID { get; set; }
public string UT_LONG_NM { get; set; }
public string UT_STR_AD { get; set; }
public string UT_CITY_AD { get; set; }
public string UT_ST_AD { get; set; }
public Int32 UT_ZIP_CD_AD { get; set; }
public string UT_CT { get; set; }
}
and an EntityTypeConfiguration like this ...
public class DbUnitMapping: EntityTypeConfiguration<DBUnitEntity>
{
public DbUnitMapping()
{
HasKey(t => t.UT_ID);
}
}
... which I add in the OnModelCreating of the DbContext, then I can get the entities just fine out of the database, which is nice, using this ....
var allUnits = _context.Database.SqlQuery<DBUnitEntity>(StoredProcedureHelper.GetAllUnitsProc);
BUT, What Doesn't Work
If I want an entity like this, with friendlier names ....
public class UnitEntity : IUnit
{
public Int16 UnitId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public Int32 Zip { get; set; }
public string Category { get; set; }
}
and an EntityTypeConfiguration like this ...
public UnitMapping()
{
HasKey(t => t.UnitId);
Property(t => t.UnitId).HasColumnName("UT_ID");
Property(t => t.Name).HasColumnName("UT_LONG_NM");
Property(t => t.Address).HasColumnName("UT_STR_AD");
Property(t => t.City).HasColumnName("UT_CITY_AD");
Property(t => t.State).HasColumnName("UT_ST_AD");
Property(t => t.Zip).HasColumnName("UT_ZIP_CD_AD");
Property(t => t.Category).HasColumnName("UT_CT");
}
When I try to get the data I get a System.Data.EntityCommandExecutionException with the message ....
"The data reader is incompatible with the specified 'DataAccess.EFCodeFirstSample.UnitEntity'. A member of the type, 'UnitId', does not have a corresponding column in the data reader with the same name."
If I add the "stored procedure named" property to the entity, it goes and complains about the next "unknown" property.
Does "HasColumnName" not work as I expect/want it to in this code-first stored procedure fluent style of EF?
Update:
Tried using DataAnnotations (Key from ComponentModel, and Column from EntityFramework) ... ala
public class UnitEntity : IUnit
{
[Key]
[Column("UT_ID")]
public Int16 UnitId { get; set; }
public string Name { get; set; }
That did remove the need for any EntityTypeConfiguration at all for the DBUnitEntity with the database-identical naming (i.e. just adding the [Key] Attribute), but did nothing for the entity with the property names that don't match the database (same error as before).
I don't mind using the ComponentModel Annotations in the Model, but I really don't want to use the EntityFramework Annotations in the model if I can help it (don't want to tie the Model to any specific data access framework)
From Entity Framework Code First book (page 155):
The SQLQuery method always attempts the column-to-property matching based on property name...
None that the column-to-property name matching does not take any mapping into account. For example, if you had mapped the DestinationId property to a column called Id in the Destination table, the SqlQuery method would not use this mapping.
So you cannot use mappings when calling stored procedure. One workaround is to modify your stored procedure to return result with aliases for each column that will match your object properties' names.
Select UT_STR_AD as Address From SomeTable etc
This isn't using Entity Framework but it is stemming from dbcontext. I have spent hours upon hours scouring the internet and using dot peek all for nothing. I read some where that the ColumnAttribute is ignored for SqlQueryRaw. But I have crafted up something with reflection, generics, sql datareader, and Activator. I am going to be testing it on a few other procs. If there is any other error checking that should go in, comment.
public static List<T> SqlQuery<T>( DbContext db, string sql, params object[] parameters)
{
List<T> Rows = new List<T>();
using (SqlConnection con = new SqlConnection(db.Database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.CommandType = CommandType.StoredProcedure;
foreach (var param in parameters)
cmd.Parameters.Add(param);
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
if (dr.HasRows)
{
var dictionary = typeof(T).GetProperties().ToDictionary(
field => CamelCaseToUnderscore(field.Name), field => field.Name);
while (dr.Read())
{
T tempObj = (T)Activator.CreateInstance(typeof(T));
foreach (var key in dictionary.Keys)
{
PropertyInfo propertyInfo = tempObj.GetType().GetProperty(dictionary[key], BindingFlags.Public | BindingFlags.Instance);
if (null != propertyInfo && propertyInfo.CanWrite)
propertyInfo.SetValue(tempObj, Convert.ChangeType(dr[key], propertyInfo.PropertyType), null);
}
Rows.Add(tempObj);
}
}
dr.Close();
}
}
}
return Rows;
}
private static string CamelCaseToUnderscore(string str)
{
return Regex.Replace(str, #"(?<!_)([A-Z])", "_$1").TrimStart('_').ToLower();
}
Also something to know is that all of our stored procs return lowercase underscore delimited. The CamelCaseToUnderscore is built specifically for it.
Now BigDeal can map to big_deal
You should be able to call it like so
Namespace.SqlQuery<YourObj>(db, "name_of_stored_proc", new SqlParameter("#param",value),,,,,,,);
The example posted by "DeadlyChambers" is great but I would like to extend the example to include the ColumnAttribute that you can use with EF to add to a properties to map a SQL field to a Class property.
Ex.
[Column("sqlFieldName")]
public string AdjustedName { get; set; }
Here is the modified code.
This code also include a parameter to allow for custom mappings if needed by passing a dictionary.
You will need a Type Converter other than Convert.ChangeType for things like nullable types.
Ex. If you have a field that is bit in the database and nullable boolean in .NET you will get a type convert issue.
/// <summary>
/// WARNING: EF does not use the ColumnAttribute when mapping from SqlQuery. So this is a "fix" that uses "lots" of REFLECTION
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="database"></param>
/// <param name="sqlCommandString"></param>
/// <param name="modelPropertyName_sqlPropertyName">Model Property Name and SQL Property Name</param>
/// <param name="sqlParameters">SQL Parameters</param>
/// <returns></returns>
public static List<T> SqlQueryMapped<T>(this System.Data.Entity.Database database,
string sqlCommandString,
Dictionary<string,string> modelPropertyName_sqlPropertyName,
params System.Data.SqlClient.SqlParameter[] sqlParameters)
{
List<T> listOfT = new List<T>();
using (var cmd = database.Connection.CreateCommand())
{
cmd.CommandText = sqlCommandString;
if (cmd.Connection.State != System.Data.ConnectionState.Open)
{
cmd.Connection.Open();
}
cmd.Parameters.AddRange(sqlParameters);
using (var dataReader = cmd.ExecuteReader())
{
if (dataReader.HasRows)
{
// HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection.
var convertTo = typeof(GenericExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mi => mi.Name == "ConvertTo").Where(m => m.GetParameters().Count() == 1).FirstOrDefault();
// now build a new list of the SQL properties to map
// NOTE: this method is used because GetOrdinal can throw an exception if column is not found by name
Dictionary<string, int> sqlPropertiesAttributes = new Dictionary<string, int>();
for (int index = 0; index < dataReader.FieldCount; index++)
{
sqlPropertiesAttributes.Add(dataReader.GetName(index), index);
}
while (dataReader.Read())
{
// create a new instance of T
T newT = (T)Activator.CreateInstance(typeof(T));
// get a list of the model properties
var modelProperties = newT.GetType().GetProperties();
// now map the SQL property to the EF property
foreach (var propertyInfo in modelProperties)
{
if (propertyInfo != null && propertyInfo.CanWrite)
{
// determine if the given model property has a different map then the one based on the column attribute
string sqlPropertyToMap = (propertyInfo.GetCustomAttribute<ColumnAttribute>()?.Name ?? propertyInfo.Name);
string sqlPropertyName;
if (modelPropertyName_sqlPropertyName!= null && modelPropertyName_sqlPropertyName.TryGetValue(propertyInfo.Name, out sqlPropertyName))
{
sqlPropertyToMap = sqlPropertyName;
}
// find the SQL value based on the column name or the property name
int columnIndex;
if (sqlPropertiesAttributes.TryGetValue(sqlPropertyToMap, out columnIndex))
{
var sqlValue = dataReader.GetValue(columnIndex);
// ignore this property if it is DBNull
if (Convert.IsDBNull(sqlValue))
{
continue;
}
// HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection.
var newValue = convertTo.MakeGenericMethod(propertyInfo.PropertyType).Invoke(null, new object[] { sqlValue });
propertyInfo.SetValue(newT, newValue);
}
}
}
listOfT.Add(newT);
}
}
}
}
return listOfT;
}
I would like to use SQL Server xml type as a column type for an entity class.
According to this thread it's possible to map such a column to string type:
public class XmlEntity
{
public int Id { get; set; }
[Column(TypeName="xml")]
public string XmlValue { get; set; }
}
The table is correctly generated in the datebase by this definition. New XmlEntity objects are also can be created.
But then I try to get some entity from the database:
var entity = db.XmlEntities.Where(e => e.Id == 1).FirstOrDefault();
An error occurs:
One or more validation errors were detected during model generation
System.Data.Edm.EdmEntityType: EntityType 'XElement' has no key defined. Define the key for this EntityType.
The problem was with my wrapper property:
[NotMapped]
public XElement XmlValueWrapper
{
get { return XElement.Parse(XmlValue); }
set { XmlValue = value.ToString(); }
}
I didn't specified NotMapped attribute.
Just to be complete. Here's all code needed, in one part.
[Column(TypeName = "xml")]
public String XmlContent { get; set; }
[NotMapped]
public XElement InitializedXmlContent
{
get { return XElement.Parse(XmlContent); }
set { XmlContent = value.ToString(); }
}
This's how you do that in Data Annotations, if you want to use Fluent API (and use a mapping class) then:
public partial class XmlEntityMap : EntityTypeConfiguration<XmlEntity>
{
public FilterMap()
{
// ...
this.Property(c => c.XmlContent).HasColumnType("xml");
this.Ignore(c => c.XmlValueWrapper);
}
}
If you use Fluent API by overriding OnModelCreating on DbContext then just change those "this" with modelBuilder.Entity<XmlEntity>()