When a new Post is input into the system, a number of Tags will need to be instantiated and associated with the Post instance. Some of these Tags will already exist in the db, others will not and will need inserting.
An example:
var post = new Post {
Slug = "hello-world",
Title = "Hello, World!",
Content = "this is my first post.",
Tags = new List<Tag>()
};
var tag = new Tag { Name = "introduction" };
post.Tags.Add(tag);
When an associated Tag does not exist in the db, I can rely on a simple call to DbSet<T>.Add to insert both the post and the associated tags into the db.
However, attempting to insert a post with associated tags that already exist in the db causes a primary key violation on the tags table.
In an attempt to solve this problem, I tried to Attach each tag to the database context which works super when the tags already exist in the database but otherwise, an exception is thrown with the following inner exception:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.TagPosts_dbo.Tags_Tag_Name". The conflict occurred in
database "EF.Domain.BlogDb", table "dbo.Tags", column
'Name'. The statement has been terminated."}
I want to insert tags associated with the post into the database only when it is required. How can I achieve this?
Your predicament is quite interesting. If you wouldn't use a generic repository but a specific repository that knows the primary key of the entity you want to attach is you could just use a code like this:
var tagExists = Tags.Any(t => t.Name == tag.Name);
or
var tag = Tags.Find(tag.Name);
however in your case we need a more general approach such as getting the primary key of the entity the Repository class uses regardless the type of the entity. To achieve this I've created two extension methods on the DbContext class:
public static IList<string> GetPrimaryKeyNames<TEntity>(this DbContext context)
where TEntity : class
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var set = objectContext.CreateObjectSet<TEntity>();
return set.EntitySet.ElementType
.KeyMembers
.Select(k => k.Name)
.ToList();
}
public static IList<object> GetPrimaryKeyValues<TEntity>(this DbContext context, TEntity entity)
where TEntity : class
{
var valueList = new List<object>();
var primaryKeyNames = context.GetPrimaryKeyNames<TEntity>();
foreach(var propertyInfo in entity.GetType().GetProperties())
{
if (primaryKeyNames.Contains(propertyInfo.Name))
{
valueList.Add(propertyInfo.GetValue(entity));
}
}
return valueList;
}
Utilizing these methods you can change the Attach method on the Repository class like the following:
public void Attach(TEntity entity)
{
var storeEntity = _context.Set<TEntity>().Find(
_context.GetPrimaryKeyValues(entity).ToArray());
if (storeEntity != null)
{
_context.Entry(storeEntity).State = EntityState.Detached;
_context.Set<TEntity>().Attach(entity);
}
}
Related
I use Entity Framework 6.2 Code First (.net framework 4.6.1) and I map few entities to view via Table Attribute. It works for select operations and I handle Insert/Update/Delete with writing trigger to view at sql server side. It works as expected, however when I add a new migration, Entity Framework generate RenameTable scripts for used Table Attribute (actuallyis expected behavior for EF). But I want to intercept migration generation and change these entities tableName to original name.
my code like;
[MapToView("Users","UsersView")]
public class User
{
...
}
I wrote MapToView Attribute, this attribute inherited by TableAttribute and pass to second parameter to TableAttribute. I create this Attribute because if I intercept migration generation, return original table name with this attribute parameters.
In this case when I run "add-migration migrationName" it creates migration scripts like this;
RenameTable(name: "dbo.Users", newName: "UsersView");
but i want to create empty migration when I run "add-migration migrationName" script.
anyone can help me?
I solve the problem.
First: Problem is; When I Map Entity to View EF Code-first generate migration with ViewName. This is problem because I want to use View Instead of Table. So I solve problem with this instructions;
1- I Create BaseEntityConfiguration that Inherited from EntityTypeConfiguration and all entity configuration classes are inherited by.
for example:
public class UserConfig: BaseEntityConfiguration<User> //Generic Type is Entity
{
public UserConfig()
{
}
}
2- I Create MapToViewAttribute that inherited by TableAttribute
public class MapToViewAttribute : TableAttribute
{
public string TableName { get; }
public string ViewName { get; }
public MapToViewAttribute(string tableName, string viewName) : base(viewName)
{
TableName = tableName;
ViewName = viewName;
}
}
3- I Use MapToViewAttribute for example User Entity to use View.
[MapToView("User","UserView")]
public class User
{
...
}
And in BaseEntityConfiguration's Constructor I Get Generic Type and custom attributes. If any entity has MapToView Attribute, I pass to TableName parameter to ToTable Method. So at runtime EF uses View for these entities but doesn't create migration with RenameTable for these entities.
protected BaseEntityConfiguration()
{
var baseType = typeof(TEntityType);
var attributes = baseType.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr.GetType() == typeof(MapToViewAttribute))
{
var tableName = ((MapToViewAttribute)attr).TableName;
ToTable(tableName);
}
}
}
Last EF don't use your configuration files, so you must tell the EF to use this in DbContext class's InternalModelCreate method.
My implementation like this;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly()
.GetTypes().Where(IsConfigurationType);
foreach (var type in typesToRegister)
{
dynamic configurationInstance = type.BaseType != null
&& type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(BaseEntityConfiguration<>)
? Activator.CreateInstance(type, culture)
: Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
modelBuilder.Types().Configure(t => t.ToTable(t.ClrType.Name));
BaseDbContext.InternalModelCreate(modelBuilder);
}
But if you use this approach you must create Insert, Update and Delete Triggers/Rule (if you use SQLServer trigger is an option but if you use postgresql rule is better option) because EF uses this view for insert, update and delete operations.
I have a method that receives an IEnumerable<Guid> of IDs to objects I want to delete. One suggested method is as follows
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
DataContext.Attach(tempInstance); // Exception here
DataContext.Remove(tempInstance);
}
This works fine if the objects aren't already loaded into memory. But my problem is that when they are already loaded then the Attach method throws an InvalidOperationException - The instance of entity type 'MyEntity' cannot be tracked because another instance with the key value 'Id:...' is already being tracked. The same happens if I use DataContext.Remove without calling Attach.
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
DataContext.Remove(tempInstance); // Exception here
}
I don't want to use DataContext.Find to grab the instance of an already loaded object because that will load the object into memory if it isn't already loaded.
I cannot use DataContext.ChangeTracker to find already loaded objects because only objects with modified state appear in there and my objects might be loaded and unmodified.
The following approach throws the same InvalidOperationException when setting EntityEntry.State, even when I override GetHashCode and Equals on MyEntity to ensure dictionary lookups see them as the same object.
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
EntityEntry entry = DataContext.Entry(tempInstance);
entry.State == EntityState.Deleted; // Exception here
}
The only way so far I have found that I can achieve deleting objects by ID without knowing if the object is the following:
foreach(Guid id in ids)
{
var tempInstance = new MyEntity { Id = id };
try
{
DataContext.Attach(tempInstance); // Exception here
}
catch (InvalidOperationException)
{
}
DataContext.Remove(tempInstance);
}
It's odd that I am able to call DataContext.Remove(tempInstance) without error after experiencing an exception trying to Attach it, but at this point it does work without an exception and also deletes the correct rows from the database when DataContext.SaveChanges is executed.
I don't like catching the exception. Is there a "good" way of achieving what I want?
Note: If the class has a self-reference then you need to load the objects into memory so EntityFrameworkCore can determine in which order to delete the objects.
Strangely, although this is a quite common exception in EF6 and EF Core, neither of them expose publicly a method for programmatically detecting the already tracked entity instance with the same key. Note that overriding GetHashCode and Equals doesn't help since EF is using reference equality for tracking entity instances.
Of course it can be obtained from the DbSet<T>.Local property, but it would not be as efficient as the internal EF mechanism used by Find and the methods throwing the aforementioned exception. All we need is the first part of the Find method and returning null when not found instead of loading from the database.
Luckily, for EF Core the method that we need can be implemented relatively easily by using some of the EF Core internals (under the standard This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases. policy). Here is the sample implementation, tested on EF Core 2.0.1:
using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore
{
public static partial class CustomExtensions
{
public static TEntity FindTracked<TEntity>(this DbContext context, params object[] keyValues)
where TEntity : class
{
var entityType = context.Model.FindEntityType(typeof(TEntity));
var key = entityType.FindPrimaryKey();
var stateManager = context.GetDependencies().StateManager;
var entry = stateManager.TryGetEntry(key, keyValues);
return entry?.Entity as TEntity;
}
}
}
Now you can use simply:
foreach (var id in ids)
DataContext.Remove(DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));
or
DataContext.RemoveRange(ids.Select(id =>
DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));
I try to implement a XUnit Test for Asp.net Core DBContext, But I got below error.
Message: System.InvalidOperationException : The instance of entity type 'Ads' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
Here is my current code:
public class AdsServiceTest
{
private readonly DbContextOptions<SensingSiteDbContext> _options;
private readonly SensingSiteDbContext _context;
private readonly AdsService _AdsService;
public AdsServiceTest()
{
//initialize db options
_options = new DbContextOptionsBuilder<SensingSiteDbContext>()
.UseInMemoryDatabase()
.Options;
//get service
_context = new SensingSiteDbContext(_options);
//initialize dbcontext
List<Ads> listAds = new List<Ads>() {
new Ads(){ Id=1,AdsName="Ads1", Deleted=false},
new Ads(){ Id=2,AdsName="Ads1", Deleted=false},
new Ads(){ Id=3,AdsName="Ads1", Deleted=false}
};
_context.Advertisements.AddRange(listAds);
//context.Advertisements
BaseLib.SSDbContext<Ads, AdsService> ssDbContent = new BaseLib.SSDbContext<Ads, AdsService>(_context);
_AdsService = ssDbContent.GetService((x, y) => new AdsService(x, y));
}
[Theory]
[InlineData(1)]
public void FindById(int id)
{
Ads adsResult = _AdsService.FindById(id);
Ads adsTarget = _context.Advertisements.Find(adsResult.Id);
Assert.True(adsTarget.Equals(adsResult));
}
//Failed by error System.InvalidOperationException : The instance of entity type 'Ads' cannot be tracked because another instance of this type with the same key is already being tracked
[Fact]
public void Update()
{
Ads adsResult = new Ads() { Id = 1, AdsName = "UpdateAds1" };
_AdsService.UpdateAds(adsResult);
Ads adsTarget = _context.Advertisements.Find(adsResult.Id);
Assert.True(adsTarget.Equals(adsResult));
}
}
There is no problem for Find, but failed on Update. AdsService is implemented to call SensingSiteDbContext. It seems I need to use scope lifetime for SensingSiteDbContext. But, I do not know how to implement it.
I have changed ObjectState for Update.
public virtual void Update(TEntity entity)
{
entity.ObjectState = ObjectState.Modified;
_dbSet.Update(entity);
_context.SyncObjectState(entity);
}
Any help would be appreciated.
You are new'ing up your own entity, when, you should just get that entity you've already added from the context:
Ads adsResult = new Ads() { Id = 1, AdsName = "UpdateAds1" };
_AdsService.UpdateAds(adsResult);
With this code, Entity Framework is saying, "Hey, I already have an entity with that key (check your constructor, you're putting an entity in with that same Id), but this object; I don't know what to do with it (because it came from outside with a key that already exists)".
You can change it to exactly what you're doing in the previous test:
Ads adsResult = _AdsService.FindById(id);
//do your changing here
_AdsService.UpdateAds(adsResult);
I'm using Filehelpers to parse a very wide, fixed format file and want to be able to take the resulting object and load it into a DB using EF. I'm getting a missing key error when I try to load the object into the DB and when I try and add an Id I get a Filehelpers error. So it seems like either fix breaks the other. I know I can map a Filehelpers object to a POCO object and load that but I'm dealing with dozens (sometimes hundreds of columns) so I would rather not have to go through that hassle.
I'm also open to other suggestions for parsing a fixed width file and loading the results into a DB. One option of course is to use an ETL tool but I'd rather do this in code.
Thanks!
This is the FileHelpers class:
public class AccountBalanceDetail
{
[FieldHidden]
public int Id; // Added to try and get EF to work
[FieldFixedLength(1)]
public string RecordNuber;
[FieldFixedLength(3)]
public string Branch;
// Additional fields below
}
And this is the method that's processing the file:
public static bool ProcessFile()
{
var dir = Properties.Settings.Default.DataDirectory;
var engine = new MultiRecordEngine(typeof(AccountBalanceHeader), typeof(AccountBalanceDetail), typeof(AccountBalanceTrailer));
engine.RecordSelector = new RecordTypeSelector(CustomSelector);
var fileName = dir + "\\MOCK_ACCTBAL_L1500.txt";
var res = engine.ReadFile(fileName);
foreach (var rec in res)
{
var type = rec.GetType();
if (type.Name == "AccountBalanceHeader") continue;
if (type.Name == "AccountBalanceTrailer") continue;
var data = rec as AccountBalanceDetail; // Throws an error if AccountBalanceDetail.Id has a getter and setter
using (var ctx = new ApplicationDbContext())
{
// Throws an error if there is no valid Id on AccountBalanceDetail
// EntityType 'AccountBalanceDetail' has no key defined. Define the key for this EntityType.
ctx.AccountBalanceDetails.Add(data);
ctx.SaveChanges();
}
//Console.WriteLine(rec.ToString());
}
return true;
}
Entity Framework needs the key to be a property, not a field, so you could try declaring it instead as:
public int Id {get; set;}
I suspect FileHelpers might well be confused by the autogenerated backing field, so you might need to do it long form in order to be able to mark the backing field with the [FieldHidden] attribute, i.e.,
[FieldHidden]
private int _Id;
public int Id
{
get { return _Id; }
set { _Id = value; }
}
However, you are trying to use the same class for two unrelated purposes and this is generally bad design. On the one hand AccountBalanceDetail is the spec for the import format. On the other you are also trying to use it to describe the Entity. Instead you should create separate classes and map between the two with a LINQ function or a library like AutoMapper.
How can I update a single property of a record without retrieving it first?
I'm asking in the context of EF Code First 4.1
Says I have a class User, mapping to table Users in Database:
class User
{
public int Id {get;set;}
[Required]
public string Name {get;set;}
public DateTime LastActivity {get;set;}
...
}
Now I want to update LastActivity of a user. I have user id. I can easily do so by querying the user record, set new value to LastActivity, then call SaveChanges(). But this would result in a redundant query.
I work around by using Attach method. But because EF throws a validation exception on Name if it's null, I set Name to a random string (will not be updated back to DB). But this doesn't seem a elegant solution:
using (var entities = new MyEntities())
{
User u = new User {Id = id, Name="this wont be updated" };
entities.Users.Attach(u);
u.LastActivity = DateTime.Now;
entities.SaveChanges();
}
I would be very appriciate if someone can provide me a better solution. And forgive me for any mistake as this is the first time I've asked a question on SO.
This is a problem of validation implementation. The validation is able to validate only a whole entity. It doesn't validate only modified properties as expected. Because of that the validation should be turned off in scenarios where you want to use incomplete dummy objects:
using (var entities = new MyEntities())
{
entities.Configuration.ValidateOnSaveEnabled = false;
User u = new User {Id = id, LastActivity = DateTime.Now };
entities.Users.Attach(u);
entities.Entry(user).Property(u => u.LastActivity).IsModified = true;
entities.SaveChanges();
}
This is obviously a problem if you want to use the same context for update of dummy objects and for update of whole entities where the validation should be used. The validation take place in SaveChanges so you can't say which objects should be validated and which don't.
I'm actually dealing with this right now. What I decided to do was override the ValidateEntity method in the DB context.
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var errors = new List<DbValidationError>();
foreach (var error in result.ValidationErrors)
{
if (entityEntry.Property(error.PropertyName).IsModified)
{
errors.Add(error);
}
}
return new DbEntityValidationResult(entityEntry, errors);
}
I'm sure there's some holes that can be poked in it, but it seemed better than the alternatives.
You can try a sort of hack:
context.Database.ExecuteSqlCommand("update [dbo].[Users] set [LastActivity] = #p1 where [Id] = #p2",
new System.Data.SqlClient.SqlParameter("p1", DateTime.Now),
new System.Data.SqlClient.SqlParameter("p2", id));