1

I'm working on ASP.NET Boilerplate. I have the problem where I try to get a record from a table called Buildings and make an update on it. I get the record from database by:

var buildingApp = _buildingsAppService.getBuildingsById(buildingInput);

And after that, I make some changes on the data as follows:

buildingApp.streetName = Request["buildingaddress"];
buildingApp.isInHoush = Convert.ToBoolean(Request["buildingOutput.isInHoush"]);
buildingApp.houshName = Request["HoushName"];

And then copy the buildingApp to another object, which has the same properties, in order to pass the new object to update method as follows:

var updateBuildingInput = new UpdateBuidlingsInput()
{
    Id = buildingApp.Id,
    buildingID = buildingApp.buildingID,
    numOfBuildingUnits = buildingApp.numOfBuildingUnits,
    numOfFloors = buildingApp.numOfFloors,
    streetName = buildingApp.streetName,
    buildingNo = buildingApp.buildingNo,
    neighborhoodID = buildingApp.neighborhoodID,
    buildingTypeID = buildingApp.buildingTypeID,
    GISMAP = buildingApp.GISMAP,
    houshProperty = buildingApp.houshProperty,
    houshName = buildingApp.houshName,
    X = buildingApp.X,
    Y = buildingApp.Y,
    buildingName = buildingApp.buildingName,
    isInHoush = buildingApp.isInHoush,
    buildingUsesID = buildingApp.buildingUsesID
};

And the update method is as follows:

_buildingsAppService.update(updateBuildingInput);

The problem is when it executes the previous line, I get the following error:

System.InvalidOperationException: 'Attaching an entity of type 'TaawonMVC.Models.Buildings' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.'

I can see that when I initialize the object updateBuildingInput manually, the update method runs without error. But when it depends on the object obtained from database using buildingApp, the error happens. It seems like the get method gets data from database and keeps holding on to the record from database, and when I try to update the same record, the conflict happens. This is the whole action where all of get and update happens:

public ActionResult UpdateApplication (UpdateApplicationsInput model)
{
    var updateApplication = new UpdateApplicationsInput();
    updateApplication.buildingId = Convert.ToInt32(Request["buildingnumber"]);
    updateApplication.buildingUnitId = Convert.ToInt32(Request["dropDownBuildingUnitApp"]);
    //==== get building and unit related to application for update ======
    var buildingInput = new GetBuidlingsInput()
    {
        Id = updateApplication.buildingId
    };
    var buildingUnitInput = new GetBuildingUnitsInput()
    {
        Id = updateApplication.buildingUnitId
    };
    var buildingApp = _buildingsAppService.getBuildingsById(buildingInput);

    var buildingUnitApp = _buildingUnitsAppService.GetBuildingUnitsById(buildingUnitInput);
        buildingApp.streetName = Request["buildingaddress"];
        buildingApp.isInHoush = Convert.ToBoolean(Request["buildingOutput.isInHoush"]);
        buildingApp.houshName = Request["HoushName"];
        // buildingUnitApp.BuildingId = updateApplication.buildingId;
        buildingUnitApp.ResidenceStatus = Request["residentstatus"];
        // copy object getBuildingUnitInput to updateBuildingUnitInput

    var updateBuildingUnitInput = new UpdateBuildingUnitsInput()
    {
        BuildingId = buildingUnitApp.BuildingId,
        ResidentName = buildingUnitApp.ResidentName,
        ResidenceStatus = buildingUnitApp.ResidenceStatus,
        NumberOfFamilyMembers = buildingUnitApp.NumberOfFamilyMembers,
        Floor = buildingUnitApp.Floor,
        UnitContentsIds = buildingUnitApp.UnitContentsIds
    };

    //============================================
    // copy object from getBuildingOutput to updateBuildingInput
    var updateBuildingInput = new UpdateBuidlingsInput()
    {
        Id = buildingApp.Id,
        buildingID = buildingApp.buildingID,
        numOfBuildingUnits = buildingApp.numOfBuildingUnits,
        numOfFloors = buildingApp.numOfFloors,
        streetName = buildingApp.streetName,
        buildingNo = buildingApp.buildingNo,
        neighborhoodID = buildingApp.neighborhoodID,
        buildingTypeID = buildingApp.buildingTypeID,
        GISMAP = buildingApp.GISMAP,
        houshProperty = buildingApp.houshProperty,
        houshName = buildingApp.houshName,
        X = buildingApp.X,
        Y = buildingApp.Y,
        buildingName = buildingApp.buildingName,
        isInHoush = buildingApp.isInHoush,
        buildingUsesID = buildingApp.buildingUsesID
    };

    //======================================================
    updateApplication.Id = Convert.ToInt32(Request["applicationId"]);
    updateApplication.fullName = model.fullName;
    updateApplication.phoneNumber1 = model.phoneNumber1;
    updateApplication.phoneNumber2 = model.phoneNumber2;
    updateApplication.isThereFundingOrPreviousRestoration = model.isThereFundingOrPreviousRestoration;
    updateApplication.isThereInterestedRepairingEntity = model.isThereInterestedRepairingEntity;
    updateApplication.housingSince = model.housingSince;
    updateApplication.previousRestorationSource = model.previousRestorationSource;
    updateApplication.interestedRepairingEntityName = model.interestedRepairingEntityName;
    updateApplication.PropertyOwnerShipId = Convert.ToInt32(Request["PropertyOwnerShip"]);
    updateApplication.otherOwnershipType = model.otherOwnershipType;
    updateApplication.interventionTypeId = Convert.ToInt32(Request["interventionTypeName"]);
    updateApplication.otherRestorationType = model.otherRestorationType;
    updateApplication.propertyStatusDescription = model.propertyStatusDescription;
    updateApplication.requiredRestoration = model.requiredRestoration;
    updateApplication.buildingId = Convert.ToInt32(Request["buildingnumber"]);
    updateApplication.buildingUnitId = Convert.ToInt32(Request["dropDownBuildingUnitApp"]);

    // ==== get of restoration types which it is multi select drop down list ======
    var restorationTypes = Request["example-getting-started"];
    string[] restorationTypesSplited = restorationTypes.Split(',');
    byte[] restorationTypesArray = new byte[restorationTypesSplited.Length];
    for (var i = 0; i < restorationTypesArray.Length; i++)
    {
        restorationTypesArray[i] = Convert.ToByte(restorationTypesSplited[i]);
    }

    updateApplication.restorationTypeIds = restorationTypesArray;
    // ====== end of RestorationTypes
    _buildingsAppService.update(updateBuildingInput);
    _applicationsAppService.Update(updateApplication);

    // _buildingUnitsAppService.Update(updateBuildingUnitInput);

    // ==== get list of applications ==============
    var applicationsUpdate = _applicationsAppService.getAllApplications();
    var applicationsViewModel = new ApplicationsViewModel()
    {
        Applications = applicationsUpdate
    };

    return View("Applications", applicationsViewModel);
}

How ASP.NET Boilerplate template, which I use, makes CRUD Operation to database:

public class BuildingsManager : DomainService, IBuildingsManager
{
    private readonly IRepository<Buildings> _repositoryBuildings;

    public BuildingsManager(IRepository<Buildings> repositoryBuildings)
    {
        _repositoryBuildings = repositoryBuildings;
    }
    // create new building in table buildings .
    public async Task<Buildings> create(Buildings entity)
    {
        var building = _repositoryBuildings.FirstOrDefault(x => x.Id == entity.Id);
        if(building!=null)
        {
            throw new UserFriendlyException("Building is already exist");
        }
        else
        {
            return  await _repositoryBuildings.InsertAsync(entity);
        }
    }
    // delete a building from buildings table .
    public void delete(int id)
    {
        try
        {
            var building = _repositoryBuildings.Get(id);
            _repositoryBuildings.Delete(building);
        }
        catch (Exception)
        {
            throw new UserFriendlyException("Building is not exist");
        }
    }

    public IEnumerable<Buildings> getAllList()
    {
        return _repositoryBuildings.GetAllIncluding(b => b.BuildingType, n => n.NeighboorHood,u=>u.BuildingUses);
    }

    public Buildings getBuildingsById(int id)
    {
        return _repositoryBuildings.Get(id);
    }

    public void update(Buildings entity)
    {
        _repositoryBuildings.Update(entity);
    }
}

How can I solve this problem? Many thanks for help.

aaron
  • 39,695
  • 6
  • 46
  • 102
  • Hi there! try this code before update: `_buildingsAppService.Entry(updateBuildingInput).State = EntityState.Modified;` – vasily.sib Sep 20 '18 at 11:39
  • https://stackoverflow.com/questions/30691024/updating-existing-data-in-ef-6-throws-exception-entity-of-the-same-type-al might help? – Ben Steele Sep 20 '18 at 11:39
  • the core layer where the CRUD operation on database using what called IRepository to inject statment to database : – Yassar Mansour Sep 20 '18 at 12:12
  • 1
    You don't need to copy the updated object to another object. As soon as you made changes to your object `buildingApp`, you can either call `SaveChanges()` if you want to update it right away, or just return and the unit of work will update it for you automatically when the request is finished. – Dejan Janjušević Sep 20 '18 at 12:34

3 Answers3

0

By creating a new entity (updateBuildingInput) with the same primary key as one you have already read in your context, Entity will throw an error when you attempt an operation on the new entity (as you have seen) as it is already tracking an entity with that primary key in the context.

If _buildingsAppService is a DbContext and all you need to do is make some changes to an entity, you can:

  1. Read the entity
  2. Make changes directly to that entity object
  3. Call _buildingsAppService.SaveChanges()

SaveChanges() will:

Saves all changes made in this context to the underlying database.

Anthony
  • 6,422
  • 2
  • 17
  • 34
  • I read the entity from database then make changes to it then try to update it , not create new one with the same primary key , and i use boilerplate which it use IRepository to inject statment to database like update , delete , read and create . – Yassar Mansour Sep 20 '18 at 12:00
  • @YassarMansour this line `Id = buildingApp.Id` is setting the Id (guessing this is the Primary Key) of the updating entity to the one you read – Anthony Sep 20 '18 at 14:42
  • yes it is the primary key , do i need to remove this line? – Yassar Mansour Sep 23 '18 at 11:41
  • when i removed it , it gave me error that more than one row affected . – Yassar Mansour Sep 23 '18 at 11:44
0

Use .AsNoTracking():

public class BuildingsManager : DomainService, IBuildingsManager
{
    public Buildings getBuildingsById(int id)
    {
        return _repositoryBuildings.GetAll().AsNoTracking().First(b => b.Id == id);
    }

    // ...
}
aaron
  • 39,695
  • 6
  • 46
  • 102
  • it gives me this error : Could not load type 'System.Data.Entity.DbExtensions' from assembly 'EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. – Yassar Mansour Sep 23 '18 at 06:58
  • by the way first do not take integer as argument , it takes lambda expression and i change it , but it stop returning data as above – Yassar Mansour Sep 23 '18 at 07:45
  • Could not repro. Create a repro project on GitHub. – aaron Sep 23 '18 at 09:19
0

When getting the record from db you can use .AsNoTracking()

Or if you really need to update an attached entity first locate the attached copy and detach it, then modify and update;

public async Task<bool> UpdateAsync<T>(T entity)
        where T : class, IHasId
    {
        // check if entity is being tracked
        var local = _context.Set<T>().Local.FirstOrDefault(x => x.Id.Equals(entity.Id));

        // if entity is tracked detach it from context
        if (local != null)
            _context.Entry<T>(local).State = EntityState.Detached;

        _context.Attach(entity).State = EntityState.Modified;

        var result = await _context.SaveChangesAsync();


        // detach entity if it was not tracked, otherwise it will be kept tracking
        if(local == null)
            _context.Entry(entity).State = EntityState.Detached;

        return result > 0;
    }

btw, IHasId is a simple interface to make Id property accessible for generic types;

public interface IHasId {
    int Id { get; set; }
}
LazZiya
  • 5,286
  • 2
  • 24
  • 37
  • i get the recored from database as following : var buildingApp = _buildingsAppService.getBuildingsById(buildingInput); , the problem is i do not get it directly from database , but i use asp.net boilerplate where there are two layers , one called application and the other called core , in core i Implement what called IRepository interface in order to inject CRUD operation on database , so when i use AsNoTracking() on core layer the Repository stop retrieve data , and give me error , try to add it in the controller also not working . – Yassar Mansour Sep 23 '18 at 07:53