Entity Framework: Explicity set Primary key Value - entity-framework

Is it possible to set a primary key value of your own choice ?
I'm working with data from an API and i'd like the objects to have the same id in my database as they have originally.
For example, i have an object with these attributes:
_context = new ApplicationDbContext();
Object
{
id = 1234,
Name = "Pitbull",
Owner = "Greg"
};
_context.saveChanges(Object);
id is the PK for object in the database. But, if i save this the id is discarded and the database creates it's own value.
Thanks for reading ! :)

Yes there is.
The proper way to do this is to override your three DbContext.SaveChanges methods. In these methods you'll give the primary keys of all added objects an Id.
In the example below this is done in method GenerateIds:
public override int SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync()
{
GenerateIds();
return await base.SaveChangesAsync();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
GenerateIds();
return await base.SaveChangesAsync(cancellationToken);
}
private void GenerateIds()
{
var addedEntries = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
foreach (var addedEntry in addedEntries)
{
((IId)addedEntry.Entity).Id = this.CreateId();
}
}
CreateId should create a unique Id.
One of the Id generators I use often is nuget package IDgen (by RobIII). It is simple to install and to use. It generates unique System.Int64 identifiers, which have the advantage of being much smaller than a GUID. It works even if you have generators for the same database on multiple servers. The method it uses is the method twitter uses to generate ids for all its servers
Code is a one liner. In your DbContext:
private static IdGen.IdGenerator idGenerator = new IdGen.IdGenerator(0);
private long CreatedId()
{
return idGenerator.CreateId();
}

Related

Entity Framework - Replace child collection using a detached model

I have a many-to-many relationship between Entity A and Entity B. Entity Framework has automatically created a junction table in SQL Server after running the migration. (I don't have this junction table defined anywhere in the code.) For example:
class EntityA
{
// ...
public ICollection<EntityB> Foo { get; set; }
}
class EntityB
{
// ...
public ICollection<EntityA> Bar { get; set; }
}
I need to replace the Foo collection on EntityA using a (detached) list coming in from a client application. I've spent the better part of a day trying to figure this out. Here is what I've tried:
[HttpPut]
public async Task<IActionResult> Update(EntityA someEntity)
{
var entry = context.EntityA.Attach(someEntity);
entry.State = EntityState.Modified;
var collection = entry.Collection(x => x.Foo);
collection.IsModified = true;
await context.SaveChangesAsync();
}
I've also tried changing the CurrentValue property of collection, and obviously I've also tried replacing Foo directly, but nothing seems to work -- the junction table remains empty. How can this child list be entirely replaced without having to Include() / load the entire list into memory for manual tracking / removal?
Ivan (in the comments above) is right. After some trial and error, I ended up writing an extension method that works for my case. Before I get to that, I want to credit this answer for pointing me in the right direction, which I ended up modifying to get it working with auto-generated EF junction tables. First, the extension method:
// assuming your models inherit from a base class or implement an interface
public interface IEntity
{
Guid Id { get; set; } // or int or whatever your ID field is
}
public static class DbExtensions
{
// Updates the many-to-many child collections of an entity (for an auto-generated EF junction table)
public static async Task UpdateJunctionTableAsync<T, Y>(this DbContext baseContext, T entity, Expression<Func<T, IEnumerable<Y>>> property)
where T : class, IEntity
where Y : class, IEntity
{
// scope these calls to a new context -- working off the base context
// tends to cause issues down the line with the change tracking
using var context = new DbContext();
// EF internally compares with DB entities, so we'll do the same
var dbEntity = await context.FindAsync<T>(entity.Id);
var dbEntry = context.Entry(dbEntity);
// access the collection entry that resulted in a junction table
var dbItemsEntry = dbEntry.Collection(property);
// get its associated CLR collection accessor
var accessor = dbItemsEntry.Metadata.GetCollectionAccessor();
// load the entry's items
await dbItemsEntry.LoadAsync();
// build a dictionary to track what needs to be added vs removed
var dbItemsMap = dbItemsEntry.CurrentValue.ToDictionary(e => e.Id);
// get the current items in the entity (not DB)
var items = (IEnumerable<Y>)accessor.GetOrCreate(entity, false);
// add them to the DB as needed
foreach (var item in items)
{
// if this already exists, no need to process it.
if (dbItemsMap.ContainsKey(item.Id))
dbItemsMap.Remove(item.Id);
else
{
// otherwise, add a tracked version of it.
context.Set<Y>().Attach(item);
accessor.Add(dbEntity, item, false);
}
}
// anything still left here has been deleted from the entity
foreach (var oldItem in dbItemsMap.Values)
accessor.Remove(dbEntity, oldItem);
// we have to clear the junction table from the incoming model's collection,
// otherwise EF will try to attach to it again, which will cause errors
// further down the line
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
propertyInfo.SetValue(entity, null, null);
}
await context.SaveChangesAsync();
}
}
Using this is simple:
[HttpPut]
public async Task<IActionResult> UpdateFoo(EntityA model)
{
// update the junction table first
await context.UpdateJunctionTableAsync(model, x => x.Foo);
// then update whatever else you want
// e.g., if we were updating the whole row:
// context.EntityA.Attach(model).State = EntityState.Modified;
// save
await context.SaveChangesAsync();
return Ok();
}

Represent a single-rowed table in EF?

I have a configuration table in my database and it contains exactly one row.
ConfirmedScheduleColor | OverlappedScheduleColor | ColN
Currently, I'm retrieving the configuration like this:
var db = new SchedulingDbContext();
var config = db.Configurations.FirstOrDefault();
It's currently working fine and I can access my configurations and all. The thing is, the code looks awkward since I'm accessing the Configurations DbSet as if it contains many records (FirstOrDefault()); although actually, it contains only one record. I want to access my configurations like I'm accessing a static object. How to do that in EF?
You could simply add a property to your DbContext that returns Configurations.FirstOrDefault() and privatize the DbSet:
public class SchedulingDbContext : DbContext
{
private DbSet<Configuration> Configurations { get; set; }
public Configuration Configuration
{
get
{
return Configurations.FirstOrDefault();
}
}
}
I have a class in my project that has static methods to retrieve config settings. I use the ConfigurationManager rather than the database, but you could adapt it to get the setting from wherever you are storing the value.
In my example I have written a GetFromDb method for you that takes a key as parameter but that is because if I was storing my config settings in the database I wouldn't want to add a column every time I needed a new config setting. I would have a table with Key/Value columns. If you are wedded to the single row table then you might want to do without such a method.
public class Config
{
private _ConfirmedScheduleColor;
public static string ConfirmedScheduleColor
{
get
{
if(_ConfirmedScheduleColor == null)
_ConfirmedScheduleColor = GetFromDb("ConfirmedScheduleColor");
return _ConfirmedScheduleColor;
}
}
public static string OverlappedScheduleColor
{
get { return GetValue("OverlappedScheduleColor", "Pink"); }
}
public static int ColN
{
get { return GetValue("ColN", 2); }
}
private static string GetFromDb(string key)
{
if(key == "ConfirmedScheduleColor")
{
var config = db.Configurations.FirstOrDefault();
return config.ConfirmedScheduleColor;
}
}
private static string GetValue(string key, string defaultValue)
{
return ConfigurationManager.AppSettings[key] ?? defaultValue;
}
private static string GetValue(string key, int defaultValue)
{
int i;
if(int.TryParse(ConfigurationManager.AppSettings[key], out i))
return i;
return defaultValue;
}
}
In EF Core you can set the check constraint for the primary key. It enforces that column Id must have value that is equal to 1 which means only one record can exist in table if you have the primary key.
modelBuilder.Entity<YourTable>(e =>
{
e.HasCheckConstraint("CK_Table_Column", "[Id] = 1");
e.HasData(...) //optionally add some initial date for Id = 1
});

How to create generic EF Insert method?

I'd like to create a generic C# class with a method that will add a row to a database using Entity Framework.
I have one table called Address. I've written the following code to add an address to the database:
public class AddressExchange
{
public int Insert(Address address)
{
using (var db = new DemoWebEntities())
{
//db.AddObject("Address", address);
db.Addresses.AddObject(address);
db.SaveChanges();
return address.Id;
}
}
}
I would like to write a generic class that will perform this operation for any entity in my EDMX. I think that it should look something like this:
public class EntityExchange<T, KeyType>
{
public KeyType Insert(T t)
{
using (var db = new DemoWebEntities())
{
// The entity set name might be wrong.
db.AddObject(typeof(T).Name, t);
// EF doesn't know what the primary key is.
return t.Id;
}
}
}
I think it may be possible to use the AddObject method to add the object to the database, but the entityset name is not necessarily the same as the type name, especially if it has been pluralized!
I also want to return the primary key to the caller, but I don't know how to tell which field contains the primary key.
I have a generic InsertOrUpdate method in a generic repository that also ensures proxies are created. (Proxies are required to support lazy loading and if you create an entity using "new", then proxies are not created). See the question here
public class RepositoryBase<T> : IRepository<T> where T : ModelBase
{
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = context.Set<T>();
//Generate a proxy type to support lazy loading
T instance = dbSet.Create();
DbEntityEntry<T> entry;
if (e.GetType().Equals(instance.GetType()))
{
//The entity being added is already a proxy type that
//supports lazy loading just get the context entry
entry = context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Attach the proxy
//Need to set the ID before attaching or we get
//The property 'ID' is part of the object's key
//information and cannot be modified when we call SetValues
instance.ID = e.ID;
entry = context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
}
public abstract class ModelBase
{
public int ID { get; set; }
}
Note that all the models inherit ModelBase so that handles the ID issue and I return the entity rather than just the ID. That is probably not strictly necessary since a reference to the entity is passed in and EF performs fixup on the ID anyway so you can always access it from the refernce passed in.
This might be reliant on a particular version on Entity framework however this is how I do it
public void Create(T entity)
{
using (var db = new DemoWebEntities())
{
db.Set<T>().Add(entity);
}
}
For the primary key issue, can you use partial classes to make your entities implement an interface, something like this:
public interface IEntity
{
Guid PrimaryKey { get; }
}
Your entity classes would then return the appropriate value:
public partial class EntityType : IEntity
{
public Guid PrimaryKey
{
get
{
return this.WhateverId; // Return the primary key
}
}
}
Then, constrain your method to only accept IEntity:
public class EntityExchange<T, KeyType> where T : IEntity
And finally return the primary key after the insert:
return t.PrimaryKey;
May be it can help you.
public T Add(T model)
{
using (BigConceptEntities entity = new BigConceptEntities())
{
entity.Set<T>().Add(model);
entity.SaveChanges();
return model;
}
}

Using .Find() & .Include() on the same query

I have the following method automatically generated from the scaffold template with repository:-
public Group Find(int id)
{
return context.Groups.Find(id);
}
But since the Groups object has two navigation properties which I need , so I wanted to include the .Include, so I replace the .find with .where :-
public Group Find(int id)
{
return context.Groups.Where(c=>c.GroupID==id)
.Include(a => a.UserGroups)
.Include(a2 => a2.SecurityRoles)
.SingleOrDefault();
}
But my question is how can I apply the .Include with the .find() instead of using .Where()?
I was just thinking about what find actually does. #lazyberezovsky is right include and find cant be used in conjunction with each other. I think this is quite deliberate and here's why:
The Find method on DbSet uses the primary key value to attempt to find
an entity tracked by the context. If the entity is not found in the
context then a query will be sent to the database to find the entity
there. Null is returned if the entity is not found in the context or
in the database.
Find is different from using a query in two significant ways:
A round-trip to the database will only be made if the entity with the given key is not found in the context.
Find will return entities that are in the Added state. That is, Find will return entities that have been added to the context but have
not yet been saved to the database.
(from http://msdn.microsoft.com/en-us/data/jj573936.aspx)
Because find is an optimised method it can avoid needing a trip to the server. This is great if you have the entity already tracked, as EF can return it faster.
However if its not just this entity which we are after (eg we want to include some extra data) there is no way of knowing if this data has already been loaded from the server. While EF could probably make this optimisation in conjunction with a join it would be prone to errors as it is making assumptions about the database state.
I imagine that include and find not being able to be used together is a very deliberate decision to ensure data integrity and unnecessary complexity. It is far cleaner and simpler
when you are wanting to do a join to always go to the database to perform that join.
You can't. Find method defined on DbSet<T> type and it returns entity. You can't call Include on entity, so the only possible option is calling Find after Include. You need DbSet<T> type for that, but Include("UserGroups") will return DbQuery<T>, and Include(g => g.UserGroups) will also return DbQuery<T>:
public static IQueryable<T> Include<T>(this IQueryable<T> source, string path)
where T: class
{
RuntimeFailureMethods.Requires(source != null, null, "source != null");
DbQuery<T> query = source as DbQuery<T>;
if (query != null)
return query.Include(path); // your case
// ...
}
DbQuery<T> is not a child of DbSet<T> thus method Find is not available. Also keep in mind, that Find first looks for entity in local objects. How would it include some referenced entities, if they don't loaded yet?
You can try to do this:
public static class DbContextExtention
{
public static TEntity FirstOfDefaultIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source, TKey otherKeyValue)
where TEntity : class
{
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, "ID");
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.FirstOrDefault(lambda);
}
public static TEntity FirstOfDefaultIdEquals<TEntity>(
this ObservableCollection<TEntity> source, TEntity enity)
where TEntity : class
{
var value = (int)enity.GetType().GetProperty("ID").GetValue(enity, null);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, "ID");
var equal = Expression.Equal(property, Expression.Constant(value));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
var queryableList = new List<TEntity>(source).AsQueryable();
return queryableList.FirstOrDefault(lambda);
}
}
GetById:
public virtual TEntity GetByIdInclude(TId id, params Expression<Func<TEntity, object>>[] includes)
{
var entry = Include(includes).FirstOfDefaultIdEquals(id);
return entry;
}
Method include EntityFramework Core (look here(EF6 and EF Core)):
protected IQueryable<TEntity> Include(params Expression<Func<TEntity, object>>[] includes)
{
IIncludableQueryable<TEntity, object> query = null;
if (includes.Length > 0)
{
query = DbSet.Include(includes[0]);
}
for (int queryIndex = 1; queryIndex < includes.Length; ++queryIndex)
{
query = query.Include(includes[queryIndex]);
}
return query == null ? DbSet : (IQueryable<TEntity>)query;
}

Can I use strongly typed POCOs as related values with EF code first without creating new ones every time?

I have a status field on a class that has an ID and a Name. I'm not using an enum to model it, but rather a class with some static values, like this:
public class MailoutStatus : IEntity
{
public static MailoutStatus Draft = new MailoutStatus() { Id = 1, Name = "Draft" };
public static MailoutStatus Scheduled = new MailoutStatus() { Id = 2, Name = "Scheduled" };
public static MailoutStatus Cancelled = new MailoutStatus() { Id = 3, Name = "Cancelled" };
public static MailoutStatus Sent = new MailoutStatus() { Id = 4, Name = "Sent" };
public int Id { get; set; }
public string Name { get; set; }
...
}
Now I want to set this status value on the object it describes, like so:
var repo = new MailoutRepository();
var mailout = repo.Get(1);
mailout.Status = MailoutStatus.Cancelled;
repo.Update(mailout);
repo.CommitChanges();
However, this code will see MailoutStatus.Cancelled as a new entity and will insert a new row into the MailoutStatus table, ignoring the ID that is already on Cancelled and adding a new IDENTITY generated ID (for instance, 5). I can prevent this by adding an entityvalidation stuff, but that just makes the above blow up due to the validation failure.
I can work around the issue using this code:
var repo = new MailoutRepository();
var mailout = repo.Get(1);
mailout.Status = new MailoutStatusRepository().Get(MailoutStatus.Cancelled.Id);
repo.Update(mailout);
repo.CommitChanges();
This works because now Entity Framework knows about the MailoutStatus that I'm fetching and is tracking its state, etc. But it's really crappy to have to write that much code just to set a status. I also don't want to use an enum for other reasons and I don't want MailoutStatus to know anything about persistence. Any ideas?
Here's how I solved it.
I defined an attribute named NotTrackedAttribute and apply that on entities like Status. Then override the SaveChanges method of the derived context as follows. Reset the tracked changes to those entities
public override int SaveChanges()
{
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
var entity = changedEntity.Entity;
//ignore the types that are marked as NotTracked
if (Attribute.IsDefined(entity.GetType(), typeof(NotTrackedAttribute)))
{
changedEntity.State = EntityState.Unchanged;
continue;
}
}
return base.SaveChanges();
}
The attribute
/// <summary>
/// Indicates that a Type having this attribute should not be persisted.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class NotTrackedAttribute : Attribute
{
}
Then use it as follows
[NotTracked]
public class MailoutStatus
{
}
You're already duplicating what's in the database. If you change your model to now just have an integer status, then you can change the MailoutStatus to a static int and it will just work.
In other words, what are you gaining by having MailoutStatus as another entity, when in fact it's just a lookup value?
Now EF is supporting enums. http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx. In code first you can have a discriminater column to map enum.
Or else this is a good solution Enums with EF code-first - standard method to seeding DB and then using?