c# - Entity Framework 6.1.3: Navigation Properties - Foreign Key Relationships -
i've been able ienumerable of valuetuples
keys of given entity type (column name + propertyinfo
), have found myself in need of method identifies 1:many , many:many foreign keys given entity type.
i use cross-reference mappings give me column name + property info set of valuetuples
.
the main focus behind expand experience in ef , better understand how code first works.
below i'm using far inspect model key names, , clear context @ moment:
public static partial class entityextensions { private static readonly object _tablenamessyncobject = new object(); private static readonly object _entitytypecachesyncobject = new object(); private static readonly object _entitykeypropertyassociationssyncobject = new object(); private static readonly dictionary<(type dbcontexttype, type entitytype), string> _tablenames = new dictionary<(type, type), string>(); private static readonly dictionary<type, list<(type entitytype, string schemaname, propertyinfo setexposure)>> _entitytypecache = new dictionary<type, list<(type entitytype, string schemaname, propertyinfo setexposure)>>(); private static readonly dictionary<(type, type), list<(string propertyname, string dbname)>> _entitykeypropertyassociations = new dictionary<(type, type), list<(string propertyname, string dbname)>>(); public static ienumerable<object> cleardbcontext<tdbcontext>(this tdbcontext dbcontext) tdbcontext : dbcontext, iobjectcontextadapter { list<object> result = new list<object>(); var entitysets = dbcontext.getdbsettypeschemanameandexposuretriplets(); foreach (var entityset in entitysets) { var defctor = entityset.entitytype.getconstructor(type.emptytypes); if (defctor == null) { /* if there's no public constructor, how in model? */ dofulldelete(dbcontext, entityset, result); continue; } var keysetnames = dbcontext.getdbsetkeynames(entityset.entitytype); var keysetpropertiesandcolumns = keysetnames.select(x => (property: entityset.entitytype.getproperty(x.propertyname), columnname: x.dbname)) .toarray(); var tupletype = (keysetpropertiesandcolumns.length <= 0 || keysetpropertiesandcolumns.length > 7) ? null : getmutabletupletype(keysetpropertiesandcolumns.length); if (keysetpropertiesandcolumns.any(x => x.property == null) || tupletype == null) { /* went wrong in our inspection of entity. */ dofulldelete(dbcontext, entityset, result); continue; } var selectportion = string.join( ", ", keysetpropertiesandcolumns .select((x, i) => $"{entityset.schemaname}.[{x.columnname}] [item{(i + 1)}]")); var sqlquery = $"select {selectportion} {entityset.schemaname}"; var properties = keysetpropertiesandcolumns.select(y => y.property).toarray(); var datareader = dbcontext.database.sqlquery( tupletype.makegenerictype( keysetpropertiesandcolumns .select(x => x.property.propertytype) .toarray()), sqlquery); var dbset = entityset.getproxyfor(dbcontext);// dbcontext.set(entityset.entitytype); var entityelements = datareader.oftype<imutabletuplepassthrough>() .select(x => { var localequivalent = dbset.local.oftype<object>().singleordefault(y => x.areidentitiesequivalent(properties, y)); if (localequivalent != null) return localequivalent; var resultelement = activator.createinstance(entityset.entitytype); x.passpropertiesthroughto(properties, resultelement); dbset.attach(resultelement); return resultelement; }) .toarray(); foreach (var entity in entityelements) //dbcontext.entry(entity).state = entitystate.deleted; result.add(entity); } return result; } private static idbsetproxy getproxyfor(this (type entitytype, string schemaname, propertyinfo setexposure) target, dbcontext dbcontext) { if (target.setexposure == null) return dbsetproxy.createproxyfrom(dbcontext.set(target.entitytype)); var dbset = target.setexposure.getvalue(dbcontext); if (dbset == null) return dbsetproxy.createproxyfrom(dbcontext.set(target.entitytype)); return dbsetproxy.createproxyfrom(dbset); } private static void dofulldelete<tdbcontext>(tdbcontext dbcontext, (type entitytype, string schemaname, propertyinfo setexposure) entityset, list<object> resultset) tdbcontext : dbcontext, iobjectcontextadapter { if (entityset.setexposure != null) { var proxy = entityset.getproxyfor(dbcontext); if (proxy != null) { using (proxy) { var setarray = (ienumerable)proxy; var fullset = setarray.oftype<object>(); resultset.addrange(fullset); proxy.removerange(fullset); } return; } } { var set = dbcontext.set(entityset.entitytype); var setarray = (ienumerable)set; var fullset = setarray.oftype<object>(); resultset.addrange(fullset); set.removerange(fullset); } } /// <summary>returns generic <see cref="type"/> version of mutable tuple has <paramref name="itemcount"/> elements it.</summary> /// <param name="itemcount">the number of elements mutable tuple.</param> /// <returns>the generic <see cref="type"/> version of mutable tuple has <paramref name="itemcount"/> elements it.</returns> public static type getmutabletupletype(int itemcount) { switch (itemcount) { case 1: return typeof(mutabletuple<>); case 2: return typeof(mutabletuple<,>); case 3: return typeof(mutabletuple<,,>); case 4: return typeof(mutabletuple<,,,>); case 5: return typeof(mutabletuple<,,,,>); case 6: return typeof(mutabletuple<,,,,,>); case 7: return typeof(mutabletuple<,,,,,,>); default: throw new argumentoutofrangeexception(nameof(itemcount)); } } /* https://stackoverflow.com/questions/6106842/entity-framework-get-table-name-from-the-entity */ public static string gettablename<tdbcontext>(this tdbcontext dbcontext, type entitytype) tdbcontext : dbcontext, iobjectcontextadapter { var dbcontexttype = typeof(tdbcontext); var key = (dbcontexttype, entitytype); lock (_tablenamessyncobject) { if (!_tablenames.trygetvalue(key, out var result)) { readonlycollection<entitycontainermapping> storagemetadata = dbcontext.objectcontext.metadataworkspace.getitems<entitycontainermapping>(dataspace.csspace); result = gettablenameinlockimpl(storagemetadata, entitytype, dbcontexttype); } return result; } } private static string gettablenamewithstoragemetadata<tdbcontext>(readonlycollection<entitycontainermapping> storagemetadata, type entitytype) { var dbcontexttype = typeof(tdbcontext); var key = (dbcontexttype, entitytype); lock (_tablenamessyncobject) { if (!_tablenames.trygetvalue(key, out var result)) result = gettablenameinlockimpl(storagemetadata, entitytype, dbcontexttype); return result; } } private static string gettablenameinlockimpl(readonlycollection<entitycontainermapping> storagemetadata, type entitytype, type dbcontexttype) { string result = null; string entityname = entitytype.name; foreach (entitycontainermapping ecm in storagemetadata) { if (ecm.storeentitycontainer.trygetentitysetbyname(entityname, false, out var entityset)) { result = $"[{entityset.schema}].[{entityset.table}]"; break; } } _tablenames.add((dbcontexttype, entitytype), result); return result; } public static ienumerable<(string propertyname, string dbname)> getdbsetkeynames<tdbcontext>(this tdbcontext dbcontext, type t) tdbcontext : dbcontext, iobjectcontextadapter { var tdbcontext = typeof(tdbcontext); var key = (tdbcontext, t); lock (_entitykeypropertyassociationssyncobject) { if (!_entitykeypropertyassociations.trygetvalue(key, out var result)) { _entitykeypropertyassociations.add(key, result = new list<(string propertyname, string dbname)>()); var storagemetadata = dbcontext.objectcontext.metadataworkspace.getitems<entitycontainermapping>(dataspace.csspace); foreach (var ecm in storagemetadata) { var tablename = t.name; var entitysetbyname = ecm.entitysetmappings.where(x => x.entityset.elementtype.name == t.name).singleordefault(); if (entitysetbyname != null) { foreach (var prop in entitysetbyname.entityset.elementtype.keyproperties) { var firstpropmapping = (from typemapping in entitysetbyname.entitytypemappings fragment in typemapping.fragments propmap in fragment.propertymappings.oftype<scalarpropertymapping>() propmap.property == prop select propmap).singleordefault(); if (firstpropmapping != null) result.add((firstpropmapping.property.name, firstpropmapping.column.name)); /*use firstpropmapping in result*/ } } } } return result.toarray(); } } public static ienumerable<(type entitytype, string schemaname, propertyinfo setexposure)> getdbsettypeschemanameandexposuretriplets<tdbcontext>(this tdbcontext dbcontext) tdbcontext : dbcontext, iobjectcontextadapter { var tdbcontext = typeof(tdbcontext); lock (_entitytypecachesyncobject) { if (!_entitytypecache.trygetvalue(tdbcontext, out var resultset)) { _entitytypecache.add(tdbcontext, resultset = new list<(type entitytype, string schemaname, propertyinfo setexposure)>()); var assembliestosearch = new hashset<assembly> { tdbcontext.assembly }; /* setup few hashes avoid evaluating twice. */ var types = new hashset<type>(); var possibletypepropertieslookup = new dictionary<type, propertyinfo>(); var dbsettype = typeof(dbset<>); var currenttype = tdbcontext; /* scan context hierarchy public properties exposing dbsets, give 'isexposedasdbset' portion of question. */ while (currenttype != null) { var properties = currenttype.getproperties(bindingflags.public | bindingflags.instance); foreach (var prop in properties) if (prop.propertytype.isgenerictype && prop.propertytype.isconstructedgenerictype) { var proptypegenerictypedef = prop.propertytype.getgenerictypedefinition(); if (proptypegenerictypedef == dbsettype) { var enttype = prop.propertytype.getgenericarguments()[0]; if (!possibletypepropertieslookup.containskey(enttype)) possibletypepropertieslookup.add(enttype, prop); } } currenttype = currenttype.basetype; } foreach (var type in possibletypepropertieslookup.keys) assembliestosearch.add(type.assembly); readonlycollection<entitycontainermapping> storagemetadata = dbcontext.objectcontext.metadataworkspace.getitems<entitycontainermapping>(dataspace.csspace); foreach (entitycontainermapping ecm in storagemetadata) { foreach (var entitysetmapping in ecm.entitysetmappings) { var firstrelevanttype = assembliestosearch.select(x => x.gettype(entitysetmapping.entityset.elementtype.fullname, false)).where(x => x != null).toarray(); if (firstrelevanttype.length > 1) throw new indexoutofrangeexception("cannot determine distinct entity set type origin."); else if (firstrelevanttype.length == 1) { var firsttype = firstrelevanttype[0]; if (types.add(firsttype)) { if (possibletypepropertieslookup.containskey(firsttype)) resultset.add((firsttype, gettablenamewithstoragemetadata<tdbcontext>(storagemetadata, firsttype), possibletypepropertieslookup[firsttype])); else resultset.add((firsttype, gettablenamewithstoragemetadata<tdbcontext>(storagemetadata, firsttype), null)); } } } } } return resultset.toarray(); /* toarray detach set cache */ } } } /// <summary>provides proxy operate against <see cref="dbset"/> or <see cref="dbset{tentity}"/></summary> public interface idbsetproxy : idisposable, ienumerable { object attach(object entity); ienumerable removerange(ienumerable set); object remove(object entity); object create(); object create(type derivedentitytype); object find(params object[] keyvalues); ilist local { get; } } public class dbsetproxy : idbsetproxy { private dbset _original; public dbsetproxy(dbset original) => this._original = original; public object attach(object entity) => this._original?.attach(entity); public object create() => this._original.create(); public object create(type derivedentitytype) => this._original.create(derivedentitytype); public object find(params object[] keyvalues) => this._original.find(keyvalues); public ienumerator getenumerator() => ((ienumerable)this._original).getenumerator(); public ilist local => this._original.local; public object remove(object entity) => this._original.remove(entity); public ienumerable removerange(ienumerable set) => this._original.removerange(set); public static idbsetproxy createproxyfrom(object propertyset) { if ((propertyset ?? throw new argumentnullexception(nameof(propertyset))) dbset set) return new dbsetproxy(set); else { var type = propertyset.gettype(); if (type.isconstructedgenerictype && type.getgenerictypedefinition() == typeof(dbset<>)) { var entitytype = type.getgenericarguments()[0]; var constructedproxy = typeof(dbsetproxy<>).makegenerictype(entitytype); var constructor = constructedproxy.getconstructor(bindingflags.public | bindingflags.instance, type.defaultbinder, new[] { typeof(dbset<>).makegenerictype(entitytype) }, null); if (constructor == null) throw new missingmemberexception($"{nameof(dbsetproxy)}<{entitytype.name}>", $".ctor({entitytype.name})"); return (idbsetproxy)constructor.invoke(new[] { propertyset }); } else throw new argumentoutofrangeexception(nameof(propertyset)); } } public void dispose() { this._original = null; } } public class dbsetproxy<tentity> : idbsetproxy tentity : class { private dbset<tentity> _original; private dbset __originalimplicitcast; /* used create only, due local cache. */ public dbsetproxy(dbset<tentity> original) => this._original = original; public object attach(object entity) => (entity tentity e) ? this._original.attach(e) : null; public object create() => _original?.create(); public object create(type derivedentitytype) => (this.__originalimplicitcast ?? (__originalimplicitcast = _original)) .create(derivedentitytype); public void dispose() { this._original = null; this.__originalimplicitcast = null; } public object find(params object[] keyvalues) => this._original.find(keyvalues); public ienumerator getenumerator() => ((ienumerable)this._original).getenumerator(); public ilist local => this._original.local; public object remove(object entity) => (entity tentity e) ? this._original.remove(e) : null; public ienumerable removerange(ienumerable set) => this._original.removerange(set.oftype<tentity>()); } public interface imutabletuplepassthrough { void passpropertiesthroughto(propertyinfo[] properties, object target); bool areidentitiesequivalent(propertyinfo[] properties, object target); }
the method in sample code yields list of objects test assumption or 2 how ef understand entities related 1 (see below).
the dbsetproxy used ensure local copies of types considered, since user of context using these navigation properties.
from writing code hand, know 1 have navigation properties need (in pseudo code, using 'entry' dbentityentry current entity):
foreach (var navigationproperty in navigationproperties) { entry.originalvalues[navigationproperty.name] = navigationproperty.propinfo.getvalue(entity, null); }
i need know many:many sets since need cleared. optional associations further need cleared. these 2 steps need performed before actual logic in current cleardbcontext method.
if has resource on how grok better/quicker, i'm game, i've done far has been derived code on getting table name ef: entity framework - table name entity
this goes few steps beyond , seems learning curve appears non-linear.
Comments
Post a Comment