Im trying to give a list of propertynames to my entity framework update method and these propertynames should be marked as unchanged. However It does get the properties, but than it gives the following error when trying to change the state:
The entity type RuntimePropertyInfo is not part of the model for the current context.
Which makes sense but Is there a way to get the object property that is on the current context?
Here is my code:
if (propertiesToSkip != null)
{
foreach (var propertyName in propertiesToSkip)
{
var prop = entity.GetType().GetProperty(propertyName);
_context.Entry(prop).State = EntityState.Unchanged;
}
}
I hope anybody can help.
Edit
public static void MigrateIqrAccountDetails(ref IUnitOfWork uow, Account account,
IEnumerable<List<string>> iqrContacts)
{
if (account.IqrAccount == null) throw new ArgumentException("Cannot migrate Account");
// Addresses
var addresses = ContactMigrationHelper.MigrateIqrContactAddresses(contact);
if (addresses != null)
account.Addresses.AddRange(addresses.Except(account.Addresses, new Comparers.AddressComparer()));
uow.Repository<IqrAccount>().Update(account.IqrAccount);
uow.Repository<Account>().Update(account, new List<string> {"Name"});
}
Here is the method that edits the object and should update it on the context.
The account is being gathered like this:
var accountToUpdate =
uow.Repository<Account>()
.GetAll(a => a.IqrAccount != null, "PhoneNumbers", "EmailAddresses", "Addresses")
.OrderBy(a => a.IqrAccount.UpdatedAt)
.FirstOrDefault();
In the repository that looks like this:
public IEnumerable<T> GetAll(Func<T, bool> predicate = null, params string[] includes)
{
var query = Include(_objectSet, includes);
return predicate != null ? query.Where(predicate).Where(o => !o.IsRemoved) : query.Where(o => !o.IsRemoved);
}
protected virtual IQueryable<T> Include(IQueryable<T> query, params string[] includes)
{
if (includes == null) return query;
query = includes.Aggregate(query, (current, item) => current.Include(item));
return query;
}
and the error im getting is:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Addresses_dbo.Accounts_AccountId". The conflict occurred in database "Test", table "dbo.Accounts", column 'Id'.
The statement has been terminated.
When savechanges is called.
Also here are my models:
public class Account : BaseEntity
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual List<Address> Addresses { get; set; }
public int? CommentCollectionId { get; set; }
public virtual LabelCollection LabelCollection { get; set; }
public virtual User AccountManager { get; set; }
[ForeignKey("ParentAccount")]
public virtual int? ParentAccountId { get; set; }
public virtual Account ParentAccount { get; set; }
public virtual ICollection<Account> SubAccounts { get; set; }
}
public class Address : BaseEntity
{
public int Id { get; set; }
public string Country { get; set; }
public string Region { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Number { get; set; }
public string NumberExtension { get; set; }
public string ZipCode { get; set; }
public bool IsActive { get; set; }
public string Premise { get; set; }
public string SubPremise { get; set; }
public Location Coordinates { get; set; }
[ForeignKey("Account")]
public int AccountId { get; set; }
public Account Account { get; set; }
}
I guesse the problem is that the account is passed as a seperate object parameter and so it is no longer on the context? What is the good way of doing this?
Allright so I have fixed the issue now by removing the Account foreign key from the Address entity because the relation is also described on the Account entity. Although it does work now I still do not fully understand why it didn't when the foreign key was present on the entity. If anyone knows the answer I would still like to know.
Related
With universal PredicateBuilder it is possible to build predicates without link to DbSet. For examle I have classes
public class Сountry
{
public int Id { get; set; }
public bool IsSchengen { get; set; }
}
public class Institute
{
public int Id { get; set; }
public int CountryId { get; set; }
public bool IsNational { get; set; }
public string Title { get; set; }
}
Build predicate with PredicateBuilder:
public static Expression<Func<Institute, bool>> BuilPredicate(bool isNational)
{
var predicate = new PredicateBuilder<Institute>()
.And(ao => ao.IsNational == isNational);
return predicate.Build();
}
Now I need to filter Institutes by Сountries:
public static Expression<Func<Institute, bool>> BuilPredicate(
bool isNational,
bool countryIsSchengen,
DbContext db) // <== don't want to pass link to DbContext
{
var predicate = new PredicateBuilder<Institute>()
.And(ii => ii.IsNational == isNational);
// filter by countries
// question: how to make the same without DbContext
var countyIds = db.Set<Сountry>()
.Where(cc => cc.IsSchengen == countryIsSchengen)
.Select(cc => cc.Id);
predicate.And(ii => countyIds.Contains(ii.CountryId));
return predicate.Build();
}
Is it possible to create such predicate without link to DbContext?
And without navigation Country property:
public class Institute
{
public int Id { get; set; }
// without this
// public Country Country { get; set; }
public int CountryId { get; set; }
public bool IsNational { get; set; }
public string Title { get; set; }
}
Hello I want to sort my end result using related entity property which is in this case Locality. I got the keyword from client end as a string that includes column name and sort direction eg. "locality=asc" but when I do orderby with any parent entity properties it run fine however, the property with related entity gives me an error by saying that customer object does not have any locality property
here is my both class customer and Address
public class Customer : IEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
public Guid UserId { get; set; }
public DateTime DateCreated { get; set; }
public DateTime LastUpdated { get; set; }
[ForeignKey("Address")]
public Guid AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address: IEntity
{
public Guid Id { get; set; }
public string Lat { get; set; }
public string Lon { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Locality { get; set; }
}
Here I am trying to sort it with Address property like locality
int skip = (pageNum - 1) * pageSize;
if (skip < 0)
{
skip = 0;
}
searchTerm = searchTerm.ToLower();
var query = _context.Customers.Include(q => q.Address)
.Where(c => c.FirstName.ToLower().Contains(searchTerm)
|| c.LastName.ToLower().Contains(searchTerm)
|| c.Email.ToLower().Contains(searchTerm)
|| c.Mobile.ToLower().Contains(searchTerm));
//var sortOrderSplit = sortOrder.Split('=');
if (sortOrderSplit[0].ToLower() != "locality")
{
query = query.OrderByField("Email", "asc");
}
{
query = query.OrderByField("locality", "asc"); //that gives me an error because type is Address not Customer
}
var customers = query
.Skip(skip)
.Take(pageSize)
.ToList();
u want order by Locality ASC,right?
I think Class type of query is IEnumerable,so you can use lumbda expression.
because Locality is in Address Class,should follow the flow Customer => Address => Locality,not only search property Locality.
if (sortOrderSplit[0].ToLower() != "locality")
{
query = query.OrderBy(o => o.Email);
}
else
{
query = query.OrderBy(o => o.Address.Locality);
}
If your two entity classes have One-to-One relationship, you must add
public Customer Customer { get; set; }
into your Address class too.
Do this and try again.
I'm trying to store older versions of entities in my database. To do that I am copying the existing values before I update them. For some reason EF Core won't let me use the same batch.Values property twice.
public async Task<Batch> UpdateBatch(Batch batch, Batch updatedBatch)
{
foreach (var valueParameter in batch.Values)
{
batch.ValuesHistory.Add(new ParameterValueHistory
{
Parameter = valueParameter.Parameter,
ParameterBatchNumber = valueParameter.ParameterBatchNumber,
Value = valueParameter.Value
});
}
batch.Values = updatedBatch.Values;
batch.Version++;
await this.context.SaveChangesAsync();
return batch;
}
The foreach loop and batch.Values = updatedBatch.Values; work exactly like they should when only one of them exists. But whenever they're both active I get the following error:
The instance of entity type 'ParameterValue' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
These are the relevant models:
ParameterValue:
public class ParameterValue
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public virtual RecipeParameter Parameter { get; set; }
public string Value { get; set; }
public string? ParameterBatchNumber { get; set; }
}
ParameterValueHistory:
public class ParameterValueHistory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public virtual RecipeParameter Parameter { get; set; }
public string Value { get; set; }
public string? ParameterBatchNumber { get; set; }
}
RecipeParameter for context:
public class RecipeParameter
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Type { get; set; }
public string Unit { get; set; }
public string Value { get; set; }
public bool BatchRequired { get; set; }
}
Batch:
public class Batch
{
[Key]
[MaxLength(12)]
public string BatchNumber { get; set; }
public virtual List<ParameterValue> Values { get; set; }
public virtual List<ParameterValueHistory> ValuesHistory { get; set; }
public int Version { get; set; }
[Required]
public bool IsResearch { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
}
This is my DbContext class:
public class ApplicationDataContext : DbContext
{
public ApplicationDataContext(DbContextOptions<ApplicationDataContext> options)
: base(options)
{
}
public DbSet<Product> Product { get; set; }
public DbSet<Batch> Batch { get; set; }
public DbSet<ParameterValue> ParameterValue { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
base.OnConfiguring(optionsBuilder);
}
}
Why does this error keep showing up? Even when I am just accessing the propety as batch.Values more than once, it gives me this error.
UPDATE:
This is the controller method that calls the UpdateBatch method.
[HttpPut("{productId}/batches/{batchNumber}")]
public async Task<ActionResult<Batch>> PutBatch(string batchNumber, Batch updatedBatch)
{
Batch batch = await this.repository.GetBatchByBatchNumber(batchNumber);
if (batch == null)
{
return NotFound()
}
return await this.repository.UpdateBatch(batch, updatedBatch);
}
When you use batch.Values = updatedBatch.Values;, because batch.Values contains the foreign key of Batch, and if the value in updatedBatch.Values also contains the key value,if the equal operation is performed directly, due to the foreign key constraint, the foreign key cannot be modified directly, which will cause your error.
Therefore, you cannot include the key value in the Values in your updateBatch.
Regarding your question. I did a simple test. You can see the following code(updateBatch.Values have no Id).
var batch = _context.Batches.Include(c => c.Values)
.ThenInclude(c => c.Parameter)
.Include(b => b.ValuesHistory)
.ThenInclude(c => c.Parameter)
.Where(c => c.BatchNumber == "1")
.FirstOrDefault();
var updateBatch = new Batch
{
Version = 3,
CreatedOn = new DateTime(),
IsResearch = true,
Values = new List<ParameterValue>
{
new ParameterValue
{
Value = "hello",
Parameter = new RecipeParameter
{
BatchRequired = true,
Name = "h",
Type = "e",
Unit = "l",
Value = "o"
}
},
},
ValuesHistory = new List<ParameterValueHistory>()
};
foreach (var valueParameter in batch.Values)
{
batch.ValuesHistory.Add(new ParameterValueHistory
{
Parameter = valueParameter.Parameter,
ParameterBatchNumber = valueParameter.ParameterBatchNumber,
Value = valueParameter.Value
});
}
batch.Values = updateBatch.Values;
batch.Version++;
_context.SaveChanges();
Test result:
start by making these changes..
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
should not be on
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
instead model like
public class Batch
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
//you can add index on this
[MaxLength(12)]
public string BatchNumber { get; set; }
public int Version { get; set; }
[Required]
public bool IsResearch { get; set; }
[Required]
public DateTime CreatedOn { get; set; };// set this in the repo or create do another way
//you add this but don't see the linkage aka ParameterValue does not have a BatchId
public virtual List<ParameterValue> Values { get; set; }
public virtual List<ParameterValueHistory> ValuesHistory { get; set; }
}
I have created classes using EF Code First that have collections of each other.
Entities:
public class Field
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<AppUser> Teachers { get; set; }
public Field()
{
Teachers = new List<AppUser>();
}
}
public class AppUser
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public virtual List<Field> Fields { get; set; }
public AppUser()
{
Fields = new List<FieldDTO>();
}
}
DTOs:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<AppUserDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<AppUserDTO>();
}
}
public class AppUserDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
Mappings:
Mapper.CreateMap<Field, FieldDTO>();
Mapper.CreateMap<FieldDTO, Field>();
Mapper.CreateMap<AppUserDTO, AppUser>();
Mapper.CreateMap<AppUser, AppUserDTO>();
And I am getting StackOverflowException when calling this code (Context is my dbContext):
protected override IQueryable<FieldDTO> GetQueryable()
{
IQueryable<Field> query = Context.Fields;
return query.ProjectTo<FieldDTO>();//exception thrown here
}
I guess this happens because it loops in Lists calling each other endlessly. But I do not understand why this happens. Are my mappings wrong?
You have self-referencing entities AND self-referencing DTOs. Generally speaking self-referencing DTOs are a bad idea. Especially when doing a projection - EF does not know how to join together and join together and join together a hierarchy of items.
You have two choices.
First, you can force a specific depth of hierarchy by explicitly modeling your DTOs with a hierarchy in mind:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<TeacherDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<TeacherDTO>();
}
}
public class TeacherDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
}
public class AppUserDTO : TeacherDTO
{
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
This is the preferred way, as it's the most obvious and explicit.
The less obvious, less explicit way is to configure AutoMapper to have a maximum depth it will go to traverse hierarchical relationships:
CreateMap<AppUser, AppUserDTO>().MaxDepth(3);
I prefer to go #1 because it's the most easily understood, but #2 works as well.
Other option is using PreserveReferences() method.
CreateMap<AppUser, AppUserDTO>().PreserveReferences();
I use this generic method:
public static TTarget Convert<TSource, TTarget>(TSource sourceItem)
{
if (null == sourceItem)
{
return default(TTarget);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings);
return JsonConvert.DeserializeObject<TTarget>(serializedObject);
}
...
MapperConfiguration(cfg =>
{
cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...
When you giving 1 navigation_property to 2nd entity and visa-versa it go in an infinite loop state. So, the compiler automatically throws a Stackoverflow exception.
So, to avoid that, you just need to remove one navigation_property from any of the entities.
PROBLEM:
I am very new to EF and to LINQ, so please bear with me.
I am trying to create a EF6 model using the database first approach. Simply speaking, I have 2 database tables tblUser and tblMilkMan which have a foreign key relationship on the UserID column.
To avoid cyclic references and to shape the entity data I have created DTO classes for both the models.
I made the MilkManDTO class contain a reference to a UserDTO instance.(This is probably stupid, if so, please guide me to the right way).My aim is to be able to load a milkmen and the related User data
Anyway in my API call, when I try to load a MilkMan by ID, I do not know how to load the related UserDTO. I found examples online on how to load related Entities but not related DTOs.
DB Schema:
Models:
MilkMan Model and DTO:
namespace MilkMan.Models
{
using System;
using System.Collections.Generic;
public partial class tblMilkMan
{
public int RecordID { get; set; }
public int UserID { get; set; }
public bool IsMyTurn { get; set; }
public int RoundRobinOrder { get; set; }
public virtual tblUser tblUser { get; set; }
}
public class MilkManDTO
{
public int RecordID { get; set; }
public int UserID { get; set; }
public bool IsMyTurn { get; set; }
public int RoundRobinOrder { get; set; }
public virtual UserDTO User { get; set; }
}
}
User Model and DTO:
public partial class tblUser
{
public tblUser()
{
this.tblMilkMen = new HashSet<tblMilkMan>();
}
public int UserID { get; set; }
public string LogonName { get; set; }
public string Password { get; set; }
public int PasswordExpiresAfter { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
:
// more fields
:
public virtual ICollection<tblMilkMan> tblMilkMen { get; set; }
}
public class UserDTO
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Web API Controller Method:
// GET api/MilkMan/5
[ResponseType(typeof(MilkManDTO))]
public async Task<IHttpActionResult> GettblMilkMan(int id)
{
//tblMilkMan tblmilkman = await db.tblMilkMen.FindAsync(id);
MilkManDTO milkMan = await db.tblMilkMen.Select(b => new MilkManDTO()
{
RecordID = b.RecordID,
UserID = b.UserID,
IsMyTurn = b.IsMyTurn,
RoundRobinOrder = b.RoundRobinOrder,
User = //???? Error//
}).SingleOrDefaultAsync(b => b.RecordID == id);
if (milkMan == null)
{
return NotFound();
}
return Ok(milkMan);
}
You can nest a new UserDTO and use the same initialization list technique.
MilkManDTO milkMan = await db.tblMilkMen.Select(b => new MilkManDTO()
{
RecordID = b.RecordID,
UserID = b.UserID,
IsMyTurn = b.IsMyTurn,
RoundRobinOrder = b.RoundRobinOrder,
User = new UserDTO {
UserID = b.User.UserID,
FirstName = b.User.FirstName,
LastName = b.User.LastName,
}
}).SingleOrDefaultAsync(b => b.RecordID == id);
This code may throw a null reference exception on b.User.UserID if there is not associated User and thus User could be null. You would need to deal with this with either a ?? coalesce, ternary (b.User == null ? "DefaultFirstName" : b.User.FirstName) or omit the entire reference User = (b.User == null ? (UserDTO)null : new UserDTO { ... }). null's make this kind of thing fun.
With C# 6 we have null reference operator .? that makes this much more succinct.