What is the correct way to find and set the primary key generically with EF Core - asp.net-core

I am trying to access the primary key from a generic parameter on my entity class which is what all of my DB object inhert from.
The following code works. But is it bad practise to access the DBcontext from within the Entity class? If so, how else can I do this? Is there something like IObjectContextAdapter from EF6?
public abstract class Entity
{
private DBContext _context = new DBContext(null);
[NotMapped]
public int ID
{
get
{
return _context.PrimaryKeyValueInt(this);
}
set
{
_context.PrimaryKeyProperty(this).PropertyInfo.SetValue(this, value);
}
}
}

So, I assume you have a bunch of entity classes with various names for their primary keys, like DoodadId, WidgetId, etc. and want them all to extend a base class with a single property to access the primary key? If so, I've done something like that with Attributes and Reflection. Create an Attribute to use to identify Primary Key properties:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class EntityIdAttribute : Attribute
{
}
Then create the base class and add the property to it. In my case I use Guid for primary keys:
public abstract class Entity
{
[NotMapped]
public virtual Guid EntityId
{
get
{
return (Guid)EntityIdProperty.GetValue(this);
}
set
{
EntityIdProperty.SetValue(this, value);
}
}
private PropertyInfo EntityIdProperty => this.GetType().GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(EntityIdAttribute)))
.FirstOrDefault();
}
Then in your entity classes, extend Entity and decorate the primary key property with [EntityId]:
public class Widget : Entity
{
[EntityId]
public Guid WidgetId { get; set; }
// ......
}

Related

BlToolkit insert data failure in BaseRepository class

I'm using BaseRepository in asp .Net MVC project. Edit operation works but in Add operation, I should make a trick to make it work. In detail, my base repository and BaseEntity classes:
public class BaseRepository<TEntity, T> : IRepository<TEntity, T> where TEntity : BaseEntity<T>
{
private DbManager _context;
private Table<TEntity> Table
{
get { return _context.GetTable<TEntity>(); }
}
public BaseRepository(DbManager context)
{
_context = context;
}
//...
public TEntity Add(TEntity entity)
{
//...
return entity;
}
public TEntity Edit(TEntity entity)
{
_context.Update(entity);
return entity;
}
//...
}
public class BaseEntity<T>
{
[PrimaryKey]
public T Id { get; set; }
}
I tried three ways for Add operation to make it work. First two ways gave errors.
First way(Doesn't work):
public TEntity Add(TEntity entity)
{
_context.Insert(entity);
return entity;
}
Error Message:
Cannot insert explicit value for identity column in table '...' when IDENTITY_INSERT is set to OFF.
--
Second way(Doesn't work):
public TEntity Add(TEntity entity)
{
Table.Insert(() => entity);
return entity;
}
Error Message:
Operation is not valid due to the current state of the object.
--
Third way(Working):
public TEntity Add(TEntity entity)
{
var l = new List<TEntity> { entity };
_context.InsertBatch(l);
return entity;
}
--
Edit operation works without error, but for Add operation I need to make some trick. What is the problem with normal Add operation and, is there a way to make it work?
I tried advice of #Mladen Macanović and I added Identity attribute to primary key in base BaseEntity class, then errors shown above gone for entities having int type primary key.
Errors shown above gone for entities having int type of primary key:
public class BaseEntity<T>
{
[PrimaryKey Identity]
public T Id { get; set; }
}
But, this is not a full solution because, some of my entities have primary key in type of Guid, so adding Identity attribute to them gives another error.
InsertBatch method works without using Identity attribute. So, you can add data without using Identity attribute in BaseEntity class for Identity column. What is the difference of insertbatch method? How can I resolve errors shown above without using InsertBatch method?
The problem is with your database table. That is why you're getting the IDENTITY_INSERT error. Go to the SQL Server Management Studio, right click on the table, Design, and for the primary key column set the property Identity Specification -> (Is identity) to Yes.

How to tell if a property of an EntityObject is a primary key or foreign key?

Suppose that I have a class generated by Entity Framework called Student.
Student has the following properties:
Id int,
Name, string
Age, int
TeacherId int
Suppose further that Id refers to the primary key in SQL that identifies what student a Student object refers to and TeacherId is a foreign key that tells who the student's teacher is.
Suppose I want to write a function which takes any EntityObject (such as this one) as a parameter and returns information about which properties are primary keys and foreign keys.
How can I do this?
If this is not appropriate, then how can Entity Framework tell me which properties are primary and foreign keys?
For now, let's not take into consideration composite key fields.
Looking over the code that is autogenerated, I can see that primitive properties in the generated class have several attributes, and among these is an EdmScalarPropertyAttribute which has a boolean EntityKeyProperty which seems to indicate whether or not the property is a key.
How to read the values of the attributes is described in an article here: http://msdn.microsoft.com/en-us/library/71s1zwct.aspx
I'll bet that I can find a consistent pattern for how foreign keys are handled as well!
I have little support section in Repository for such questions.
See the GetEntityKeyFields method below
I dont have check routine already written FK, but if you check the entityField details, it will be there as well.
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public DbSet<T> EntityDbSet() { return Context.Set<T>(); } // cant be in core interface since it is EF types
public ObjectContext ObjectContext { get { return ((IObjectContextAdapter) this.Context).ObjectContext; } }
// cant be in core interface since it is EF types
public BosBaseDbContext Context { get; protected set; }
public EntityState GetEntityState(T entity) { return Context.Entry(entity).State; }
public ObjectSet<T> GetObjectSet() { return ObjectContext.CreateObjectSet<T>(); }
public string[] GetEntityFields() { return GetObjectSet().EntitySet.ElementType.Properties.Select(e => e.Name).ToArray(); }
public string[] GetEntityKeyFields() { return GetObjectSet().EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray(); }
public EntityKey GetEntityKey(T entity) {
if (entity == null) {
return null;
}
return ObjectContext.CreateEntityKey(GetObjectSet().EntitySet.Name, entity);
}

Setting CustomSqlType on References

I have a situation where my primary key is a char(2) in SqlServer 2008, and I want to reference it in a one-to-many relationship, but the ManyToOneBuilder (which is returned by ClassMap<>.References()) doesn't have a CustomSqlType() method. Specifically:
public class State
{
// state FIPS code is 2 characters
public virtual string StateCode { get; set; }
public virtual ICollection<County> { get; set; }
}
public class County
{
// state-county FIPS code is 5 characters
public virtual string StateCountyCode { get; set; }
public virtual State State { get; set; }
}
public class StateMap : ClassMap<State>
{
public StateMap()
{
Id(e => e.StateCode).CustomSqlType("char(2)").GeneratedBy.Assigned();
}
}
public class CountyMap : ClassMap<County>
{
public CountyMap()
{
Id(e => e.StateCountyCode).CustomSqlType("char(5)").GeneratedBy.Assigned();
References(e => e.State, "StateCode")
// Here's what I want to do, but can't because the method is not
// implemented on the class ManyToOneBuilder:
.CustomSqlType("char(2)");
}
}
Is there any way to accomplish this without modifying the ManyToOneBuilder? Is there a way to automatically map the FK (i.e. County.StateCode) to the correct type? It's trivial to add CustomSqlType to ManyToOneBuilder, but is that the right thing to do?
Keep your mapping definition as is, add your "State" column definition with
.CustomSqlType("char(2)")
and set for this column Insert=false and update=false.
I've the same problem and in AutoMapping I use this code:
mapping.Map(x => x.IdUniArticolo)
.CustomSqlType("varchar(50)")
.Not.Insert().Not.Update();
mapping.References(x => x.Articolo)
.Column("IdUniArticolo").PropertyRef("IdUniArticolo");
Keep in mind that if NHibernate itself doesn't support it, then Fluent NHibernate can't, and I don't NHibernate supports the scenario you have. I had a similar problem in that I had a 2 column composite key on a table and on one of the fields, I wanted to use an enumerated type which had a custom IUserType to translate it to its appropriate code value in the DB. Couldn't do it, so I was stuck keeping the property of the string type rather than the enumerated type.

How to map an interface in nhibernate?

I'm using two class NiceCustomer & RoughCustomer which implment the interface ICustomer.
The ICustomer has four properties. They are:
Property Id() As Integer
Property Name() As String
Property IsNiceCustomer() As Boolean
ReadOnly Property AddressFullText() As String
I don't know how to map the interface ICustomer, to the database.
I get an error like this in the inner exception.
An association refers to an unmapped class: ICustomer
I'm using Fluent and NHibernate.
You can map directly to interfaces in NHibernate, by plugging in an EmptyInterceptor during the configuration stage. The job of this interceptor would be to provide implementations to the interfaces you are defining in your mapping files.
public class ProxyInterceptor : EmptyInterceptor
{
public ProxyInterceptor(ITypeHandler typeHandler) {
// TypeHandler is a custom class that defines all Interface/Poco relationships
// Should be written to match your system
}
// Swaps Interfaces for Implementations
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
var handler = TypeHandler.GetByInterface(clazz);
if (handler == null || !handler.Interface.IsInterface) return base.Instantiate(clazz, entityMode, id);
var poco = handler.Poco;
if (poco == null) return base.Instantiate(clazz, entityMode, id);
// Return Poco for Interface
var instance = FormatterServices.GetUninitializedObject(poco);
SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);
return instance;
}
}
After this, all relationships and mappings can be defined as interfaces.
public Parent : IParent {
public int ID { get; set; }
public string Name { get; set; }
public IChild Child { get; set; }
}
public Child : IChild {
public int ID { get; set; }
public string Name { get; set; }
}
public class ParentMap : ClassMap<IParent>
{
public ParentMap()
{
Id(x => x.ID).GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Name)
}
}
...
This type of technique is great if you want to achieve true decoupling of your ORM, placing all configuration/mappings in a seperate project and only referencing interfaces. Your domain layer is then not being polluted with ORM, and you can then replace it at a later stage if you need to.
how are you querying? If you're using HQL you need to import the interface's namespace with an HBM file with this line:
<import class="name.space.ICustomer, Customers" />
If you're using Criteria you should just be able to query for ICustomer and it'll return both customer types.
If you're mapping a class that has a customer on it either through a HasMany, HasManyToMany or References then you need to use the generic form:
References<NiceCustomer>(f=>f.Customer)
If you want it to cope with either, you'll need to make them subclasses
Subclassmap<NiceCustomer>
In which case I think you'll need the base class Customer and use that for the generic type parameter in the outer class:
References<Customer>(f=>f.Customer)
Regardless, you shouldn't change your domain model to cope with this, it should still have an ICustomer on the outer class.
I'm not sure if the 1.0RTM has the Generic form working for References but a quick scan of the changes should show the change, which I think is a two line addition.
It is not possible to map an interface in nhibernate. If your goal is to be able to query using a common type to retrieve both types of customers you can use a polymorphic query. Simply have both your classes implement the interface and map the classes normally. See this reference:
https://www.hibernate.org/hib_docs/nhibernate/html/queryhql.html (section 11.6)

Using Fluent NHibernate Auto Mapping to map IDs of type object from base Entity class

In the project I'm working on now, we have base Entity class that looks like this:
public abstract class Entity<T> where T : Entity<T>
{
public virtual object Id { get; protected set }
// Equals, GetHashCode overrides, etc...
}
Most classes inheriting from Entity should map Id to int column in SQL Server database, but at least one will need to map to long (bigint).
Is it possible to create FluentNH Auto Mapping convention to map those object Ids to int by default? Then we could use another convention or IAutoMappingOverride to handle long Ids.
Thanks!
To answer my own question... It's possible.
You can define convention like this:
internal class PrimaryKeyConvention : IIdConvention
{
public bool Accept(IIdentityPart id)
{
return true;
}
public void Apply(IIdentityPart id)
{
if (<ID should be long>)
id.SetAttribute("type", "Int64");
else
id.SetAttribute("type", "Int32");
}
}