How can update a junction table in many to many relation - asp.net

I am going to update schooltypeid which is in the intermediates table, here is my repository class.
public async Task UpdateSchoolsAsync(SchoolUpdateVm schoolUpdateVm)
{
if (_GpsContext != null)
{
var schoolsObj = _GpsContext.School.FirstOrDefault(x => x.ID == Guid.Parse(schoolUpdateVm.id));
var schoolTypeObj = _GpsContext.SchoolsSchoolTypes.FirstOrDefault(x => x.SchoolTypeId == Guid.Parse(schoolUpdateVm.schoolTypeId));
Schools schools = new Schools();
{
schoolsObj.Name = schoolUpdateVm.name;
schoolsObj.Email = schoolUpdateVm.email;
schoolsObj.Phone = schoolUpdateVm.phone;
schoolsObj.Description = schoolUpdateVm.description;
schoolsObj.StateID = Guid.Parse(schoolUpdateVm.stateID);
schoolsObj.CountryId = Guid.Parse(schoolUpdateVm.countryId);
schoolTypeObj.SchoolTypeId = Guid.Parse(schoolUpdateVm.schoolTypeId); //here i can`t update schoolYype
}
_GpsContext.School.Update(schoolsObj);
await _GpsContext.SaveChangesAsync();
}
}
This is my School table in entity framework:
public partial class Schools
{
public Guid ID { get; set; }
public string Name { get; set; }
// Navigations
public ICollection<SchoolsSchoolType> SchoolsSchoolTypes { get; set; }// this is my junction table
}
This is my SchoolsSchoolTypes table:(This is intermediates table)

I think your model in the question is not complete. It lacks the "joining" entity.
That said, if you have a "pure" joining entity, with no additional attributes besides the key (made up of foreign keys), you should add the type to the "SchoolsSchoolTypes" collection in the "Schools" class. The code to add the entity should be something like this:
var schoolsObj = _GpsContext.School.Include(s => s.SchoolsSchoolTypes ).FirstOrDefault(x => x.ID == Guid.Parse(schoolUpdateVm.id)); //Include types to verify isn't already added
if (schoolsObj == null) throw new Exception("School not found");
if(schoolsObj.SchoolsSchoolTypes.Any(st=>st.SchoolTypeId == schoolUpdateVm.schoolTypeId) throw new Exception("School already has this type");
var schoolTypeObj = _GpsContext.SchoolsSchoolTypes.FirstOrDefault(x => x.SchoolTypeId == Guid.Parse(schoolUpdateVm.schoolTypeId));
if (schoolsObj == null) throw new Exception("School type not found");
schoolsObj.SchoolsSchoolTypes.Add(schoolTypeObj);
await _GpsContext.SaveChangesAsync();
If the "joining entity" has additional attributes (I suspect this is the case), then you have to create the new joining entity before adding it to the collection:
var schoolsObj = _GpsContext.School.Include(s => s.SchoolsSchoolTypes ).FirstOrDefault(x => x.ID == Guid.Parse(schoolUpdateVm.id)); //Include types to verify isn't already added
if (schoolsObj == null) throw new Exception("School not found");
if(schoolsObj.SchoolsSchoolTypes.Any(st=>st.SchoolTypeId == schoolUpdateVm.schoolTypeId) throw new Exception("School already has this type");
var schoolTypeObj = _GpsContext.SchoolTypes.FirstOrDefault(x => x.SchoolTypeId == Guid.Parse(schoolUpdateVm.schoolTypeId));
if (schoolsObj == null) throw new Exception("School type not found");
var newSchollType = new SchoolsSchoolTypes()
{
SchollId = Guid.Parse(schoolUpdateVm.id),
SchoolTypeId = Guid.Parse(schoolUpdateVm.schoolTypeId),
OtherProperty = "OtherPropertyValue"
}
schoolsObj.SchoolsSchoolTypes.Add(newSchollType);
await _GpsContext.SaveChangesAsync();

Related

The procedure does not work properly Entity Framework ASP.NET MVC 5 C#5

I have been facing this problem with assigning users to a proper role. The code looks just fine, but in reality half of the users gets a proper role, the other half stays without a role at all. Here is the method which does it:
public IdentityResult RefreshUserGroupRoles(long? userId)
{
if (userId == null) throw new ArgumentNullException(nameof(userId));
var user = _userManager.FindById(userId.Value);
if(user == null)
{
throw new ArgumentNullException(nameof(userId));
}
// Remove user from previous roles:
var oldUserRoles = _userManager.GetRoles(userId.Value);
if (oldUserRoles.Count > 0)
{
_userManager.RemoveFromRoles(userId.Value, oldUserRoles.ToArray());
}
// Find the roles this user is entitled to from group membership:
var newGroupRoles = this.GetUserGroupRoles(userId.Value);
// Get the damn role names:
var allRoles = _roleManager.Roles.ToList();
var addTheseRoles = allRoles.Where(r => newGroupRoles.Any(gr => gr.AppRoleId == r.Id));
var roleNames = addTheseRoles.Select(n => n.Name).ToArray();
//_db.Database.CurrentTransaction.Commit();
// Add the user to the proper roles
var transaction = _db.Database.BeginTransaction();
IdentityResult result;
try
{
result = _userManager.AddToRoles(userId.Value, roleNames);
transaction.Commit();
_db.DbContextTransactionAu.Commit(); //This is for Audit
}
catch (Exception)
{
transaction.Rollback();
throw;
}
_db.DbContextTransactionAuDispose?.Dispose();
return result;
}
public IEnumerable<AppGroupRole> GetUserGroupRoles(long userId)
{
var userGroups = this.GetUserGroups(userId).ToList();
if (userGroups.Count == 0) return new Collection<AppGroupRole>().AsEnumerable();
var userGroupRoles = new List<AppGroupRole>();
foreach(var group in userGroups)
{
userGroupRoles.AddRange(group.AppRoles.ToArray());
}
return userGroupRoles;
}
Any idea what could be wrong?

Validate Modified Model Using Annotations in EntityFramwork and ASPNET

I have this class as a part of EF Model:
class Person {
public int Id { get; set; }
[MaxLength(100, ErrorMessage="Name cannot be more than 100 characters")]
public string Name { get; set; }
}
And I have this method in my controller:
public IActionResult ChangeName(int id, string name) {
var person = db.Persons.Find(id);
if(person == null) return NotFound();
person.Name = name;
db.SaveChanges();
return Json(new {result = "Saved Successfully"});
}
Is there any way to validate person after changing the Name property using the annotation MaxLength rather than manually check for it. Becuase sometimes I might have more than one validation and I don't want to examine each one of them. Also, I might change these parameters in the future (e.g. make the max length 200), and that means I have to change it everywhere else.
So is it possible?
Your method works as long as there is one validation error per property. Also, it's quite elaborate. You can use db.GetValidationErrors() to get the same result. One difference is that errors are collected in a collection per property name:
var errors = db.GetValidationErrors()
.SelectMany(devr => devr.ValidationErrors)
.GroupBy(ve => ve.PropertyName)
.ToDictionary(ve => ve.Key, ve => ve.Select(v => v.ErrorMessage));
Okay, I found a solution to my problem, I created a method that takes the model and checks for errors:
private IDictionary<string, string> ValidateModel(Person model)
{
var errors = new Dictionary<string, string>();
foreach (var property in model.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes())
{
var validationAttribute = attribute as ValidationAttribute;
if(validationAttribute == null) continue;
var value = property.GetValue(model);
if (!validationAttribute.IsValid(value))
{
errors.Add(property.Name, validationAttribute.ErrorMessage);
}
}
}
return errors;
}
UPDATE:
As stated by #Gert Arnold, the method above returns only one validation per property. Below is the fixed version which returns a list of errors for each property
public static IDictionary<string, IList<string>> ValidateModel(Person model)
{
var errors = new Dictionary<string, IList<string>>();
foreach (var property in model.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes())
{
var validationAttribute = attribute as ValidationAttribute;
if (validationAttribute == null) continue;
var value = property.GetValue(model);
if (validationAttribute.IsValid(value)) continue;
if (!errors.ContainsKey(property.Name))
errors[property.Name] = new List<string>();
errors[property.Name].Add(validationAttribute.ErrorMessage);
}
}
return errors;
}

Values returned from my Repository model class is being cached

I have the following Post Edit action method:-
[HttpPost]
[ValidateAntiForgeryToken]
[CheckUserPermissions(Action = "Edit", Model = "StorageDevice")]
public ActionResult Edit(SDJoin sdj, FormCollection formValues)
{
//code goes here
if (ModelState.IsValid)
{
repository.Save();
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = (TMSStorageDevice)entry.GetDatabaseValues().ToObject();
var clientValues = (TMSStorageDevice)entry.Entity;
var databaseTechnology2 = repository.FindTechnology2(sdj.StorageDevice.TMSStorageDeviceID);
if (sdj.NetworkInfo.IPAddress != databaseTechnology2.TechnologyIPs.SingleOrDefault(a=>a.IsPrimary == true).IPAddress )
ModelState.AddModelError("NetworkInfo.IPAddress", "Value Has Changed "
);
if (sdj.NetworkInfo.MACAddress != databaseTechnology2.TechnologyIPs.SingleOrDefault(a => a.IsPrimary == true).MACAddress)
ModelState.AddModelError("NetworkInfo.MACAddress", "Value Has Changed "
);
if (databaseValues.RackID != clientValues.RackID)
ModelState.AddModelError("StorageDevice.RackID", "Value Has Changed "
);
But currently the values returned from the
var databaseTechnology2 = repository.FindTechnology2(sdj.StorageDevice.TMSStorageDeviceID);
will return a cached value inside the server , instead of retrieving the current database value. The repository method is :-
public Technology FindTechnology2(int id)
{
return tms.Technologies.Include(a=>a.TechnologyIPs).SingleOrDefault(a => a.TechnologyID == id);
}
Can anyone advice ?
I'm posting this as a follow up to my comment in order to show code properly.
You can set MergeOptions.OverwriteChanges as follows:
var objectContext = ((IObjectContextAdapter)entities).ObjectContext; //assuming 'entities' is your context here
var set = objectContext.CreateObjectSet<TechnologyRoles>();
set.MergeOption = MergeOption.OverwriteChanges;
var query = set.SingleOrDefault(a => a.TechnologyID == id);
Hope this helps,

Event Up-Conversion With Keeping Event-Class Name

NEventStore 3.2.0.0
As far as I found out it is required by NEventStore that old event-types must kept around for event up-conversion.
To keep them deserializing correctly in the future they must have an unique name. It is suggested to call it like EventEVENT_VERSION.
Is there any way to avoid EventV1, EventV2,..., EventVN cluttering up your domain model and simply keep using Event?
What are your strategies?
In a question long, long time ago, an answer was missing...
In the discussion referred in the comments, I came up with an - I would say - elegant solution:
Don't save the type-name but an (versioned) identifier
The identifier is set by an attribute on class-level, i.e.
namespace CurrentEvents
{
[Versioned("EventSomethingHappened", 0)] // still version 0
public class EventSomethingHappened
{
...
}
}
This identifier should get serialized in/beside the payload. In serialized form
"Some.Name.Space.EventSomethingHappened" -> "EventSomethingHappened|0"
When another version of this event is required, the current version is copied in an "legacy" assembly or just in another Namespace and renamed (type-name) to "EventSomethingHappenedV0" - but the Versioned-attribute remains untouched (in this copy)
namespace LegacyEvents
{
[Versioned("EventSomethingHappened", 0)] // still version 0
public class EventSomethingHappenedV0
{
...
}
}
In the new version (at the same place, under the same name) just the version-part of the attribute gets incremented. And that's it!
namespace CurrentEvents
{
[Versioned("EventSomethingHappened", 1)] // new version 1
public class EventSomethingHappened
{
...
}
}
Json.NET supports binders which maps type-identifiers to types and back. Here is a production-ready binder:
public class VersionedSerializationBinder : DefaultSerializationBinder
{
private Dictionary<string, Type> _getImplementationLookup = new Dictionary<string, Type>();
private static Type[] _versionedEvents = null;
protected static Type[] VersionedEvents
{
get
{
if (_versionedEvents == null)
_versionedEvents = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.IsDynamic == false)
.SelectMany(x => x.GetExportedTypes()
.Where(y => y.IsAbstract == false &&
y.IsInterface == false))
.Where(x => x.GetCustomAttributes(typeof(VersionedAttribute), false).Any())
.ToArray();
return _versionedEvents;
}
}
public VersionedSerializationBinder()
{
}
private VersionedAttribute GetVersionInformation(Type type)
{
var attr = type.GetCustomAttributes(typeof(VersionedAttribute), false).Cast<VersionedAttribute>().FirstOrDefault();
return attr;
}
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
var versionInfo = GetVersionInformation(serializedType);
if (versionInfo != null)
{
var impl = GetImplementation(versionInfo);
typeName = versionInfo.Identifier + "|" + versionInfo.Revision;
}
else
{
base.BindToName(serializedType, out assemblyName, out typeName);
}
assemblyName = null;
}
private VersionedAttribute GetVersionInformation(string serializedInfo)
{
var strs = serializedInfo.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
if (strs.Length != 2)
return null;
return new VersionedAttribute(strs[0], strs[1]);
}
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.Contains('|'))
{
var type = GetImplementation(GetVersionInformation(typeName));
if (type == null)
throw new InvalidOperationException(string.Format("VersionedEventSerializationBinder: No implementation found for type identifier '{0}'", typeName));
return type;
}
else
{
var versionInfo = GetVersionInformation(typeName + "|0");
if (versionInfo != null)
{
var type = GetImplementation(versionInfo);
if (type != null)
return type;
// else: continue as it is a normal serialized object...
}
}
// resolve assembly name if not in serialized info
if (string.IsNullOrEmpty(assemblyName))
{
Type type;
if (typeName.TryFindType(out type))
{
assemblyName = type.Assembly.GetName().Name;
}
}
return base.BindToType(assemblyName, typeName);
}
private Type GetImplementation(VersionedAttribute attribute)
{
Type eventType = null;
if (_getImplementationLookup.TryGetValue(attribute.Identifier + "|" + attribute.Revision, out eventType) == false)
{
var events = VersionedEvents
.Where(x =>
{
return x.GetCustomAttributes(typeof(VersionedAttribute), false)
.Cast<VersionedAttribute>()
.Where(y =>
y.Revision == attribute.Revision &&
y.Identifier == attribute.Identifier)
.Any();
})
.ToArray();
if (events.Length == 0)
{
eventType = null;
}
else if (events.Length == 1)
{
eventType = events[0];
}
else
{
throw new InvalidOperationException(
string.Format("VersionedEventSerializationBinder: Multiple types have the same VersionedEvent attribute '{0}|{1}':\n{2}",
attribute.Identifier,
attribute.Revision,
string.Join(", ", events.Select(x => x.FullName))));
}
_getImplementationLookup[attribute.Identifier + "|" + attribute.Revision] = eventType;
}
return eventType;
}
}
...and the Versioned-attribute
[AttributeUsage(AttributeTargets.Class)]
public class VersionedAttribute : Attribute
{
public string Revision { get; set; }
public string Identifier { get; set; }
public VersionedAttribute(string identifier, string revision = "0")
{
this.Identifier = identifier;
this.Revision = revision;
}
public VersionedAttribute(string identifier, long revision)
{
this.Identifier = identifier;
this.Revision = revision.ToString();
}
}
At last use the versioned binder like this
JsonSerializer.Create(new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
Binder = new VersionedSerializationBinder()
});
For a full Json.NET ISerialize-implementation see (an little outdated) gist here:
https://gist.github.com/warappa/6388270

Generate GUID for Primary key automatically In EF5

I'm using Guids as primary key for the entities in my database, using the model binding feature of asp.net 4.5 web forms when im inserting a record in the database using entity framework 5 im doing something like
public void onInsert([Control("ControlID")] int? countryID){
if(countryID.hasValue){
var DbEntityToInsert = new DbEntity(); //where DbEntity is the class generated by the EF
TryUpdateModel(DbEntityToInsert);
DbEntityToInsert.GuidPK = Guid.NewGuid();
if(Page.ModelState.IsValid){
using(var db = new DatabaseContext()){
db.Add(DbEntityToInsert);
db.Save();
}//using ends
}//modelstate.isvalid if ends
}//countryid.hasvalue ends
}//main method ends
now i wanted to ask is there a way i can tell EF to generate a Guid for the PK while inserting a new record so i dont have to write the line
DbEntityToInsert.GuidPK = Guid.NewGuid();
You can try to override SaveChanges in your derived context. The main task is to find out if an entity has a GuidPK property as primary key. Here is an attempt using reflection:
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
var addedEntities = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
.Select(e => new
{
Entity = e.Entity,
PropertyInfo = e.Entity.GetType().GetProperty("GuidPK")
})
.Where(x => x.PropertyInfo != null && x.PropertyInfo.CanWrite);
foreach (var x in addedEntities)
x.PropertyInfo.SetValue(x.Entity, Guid.NewGuid());
return base.SaveChanges();
}
To avoid reflection here you could have a common interface that is implemented by all your entities that use a GuidPK property as PK:
public interface IEntityWithGuidPK
{
Guid GuidPK { get; set; }
}
public class DbEntity : IEntityWithGuidPK
{
public Guid GuidPK { get; set; }
// ...
}
Then the code in SaveChanges could be:
//...
var addedEntities = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added &&
e.Entity is IEntityWithGuidPK)
.Select(e => e.Entity as IEntityWithGuidPK);
foreach (var e in addedEntities)
e.GuidPK = Guid.NewGuid();
//...

Resources