How to create domain entities for a NoSQL application - c#

I want to create an application that makes use of a NoSQL database in such a way to it plays nicely with my domain entities.
Right now when I create a "domain entity" for MongoDB then I have to define it something like this:
public class User
{
[BsonId]
public int Id {get;set;}
[BsonElement("Username")]
public string Username {get;set;}
[BsonElement("Email")]
public string Email {get;set;}
}
But this means my entity isn't persistence ignorant. So how can I achieve that when using a NoSQL database like MongoDB?

One way is to define a POCO class in your domain like this
namespace MyApp.Domain
{
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
}
and define the same class for each persistence type, for example MongoDB
namespace MyApp.MongoDb
{
public class User
{
[BsonId]
public int Id { get; set; }
[BsonElement("Username")]
public string Username { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
}
You business logic will interact with your domain classes through repository interfaces, and when you persist data, you just need copy the instance over.
You don't have to write copying data code manually because automapper can help you with that. Alternatively, you can use my simplified code to copy data between domain classes below:
/// <summary>
/// Copy public fields from an instance of the source type TV to an instance of the destination type T.
/// A source property will be copied if there is a property in the destination type with the same name.
/// For instance, TV.Name will be copied to T.Name
/// </summary>
/// <typeparam name="T">The destination type</typeparam>
/// <typeparam name="TV">The source type</typeparam>
/// <param name="input">The source data</param>
/// <param name="existingInstance">The instance that we want to copy values to, if it is null a new instance will be created</param>
/// <returns>An instance of type T</returns>
public static T CopyFields< TV,T>(TV input, T existingInstance=null)where T:class where TV:class
{
var sourcePublicFields = typeof (TV).GetProperties();
var instance =existingInstance ?? Activator.CreateInstance<T>();
var destinationPublicFields = typeof(T).GetProperties();
Debug.WriteLine("Copying data from source type {0} to destination type {1}", typeof(TV), typeof(T));
foreach (var field in sourcePublicFields)
{
var destinationField = destinationPublicFields.FirstOrDefault(it => it.Name == field.Name);
if (destinationField == null || destinationField.PropertyType != field.PropertyType)
{
Debug.WriteLine("No Destination Field matched with the source field. Source Field name {0}, source field type {1} ", field.Name, field.PropertyType);
continue;
}
var sourceValue = field.GetValue(input);
//Set the value
destinationField.SetValue(instance,sourceValue);
}
return instance;
}

Your entities can be POCO objects.
Almost all your rules you can resolve by using ConvetionRegistry.
Fox example this code set Id as string and ignore extra fields.
var convention = new ConventionPack {
new IgnoreExtraElementsConvention(true),
new IdGeneratorConvention() };
ConventionRegistry.Register("CubeConventions", convention, x => true);
public class IdGeneratorConvention : ConventionBase, IPostProcessingConvention
{
public void PostProcess(BsonClassMap classMap)
{
var idMemberMap = classMap.IdMemberMap;
if (idMemberMap == null || idMemberMap.IdGenerator != null)
{
return;
}
idMemberMap.SetIdGenerator(StringObjectIdGenerator.Instance);
}
}

Related

Converting Kusto client response to list of objects returns empty objects

I am trying to convert the response received from running a Kusto query in which I am retrieving the schema of a table. My Kusto query looks like this:
tableName | getschema
The response for such a query, as seen in the Kusto Explorer looks something like this (incomplete)
Back in my C# code I have defined the following class:
public class DatasetColumn
{
/// <summary>
/// Name of the column
/// </summary>
public string? ColumnName { get; set; }
/// <summary>
/// Position of this column within the schema
/// </summary>
public int ColumnOrdinal { get; set; }
/// <summary>
/// Type of data contained in this column
/// </summary>
public string? DataType { get; set; }
/// <summary>
/// Type of data contained in this column
/// </summary>
public string? ColumnType { get; set; }
}
And I am attempting to just retrieve a list of DatasetColumn objects from the IDataReader using a method defined in Kusto.Cloud.Data.ExtendedDataReader named ToEnumerable:
using (var client = storageClientFactory.CreateCslQueryProvider(new Kusto.Data.KustoConnectionStringBuilder(connectionDetails!.ClusterUrl)))
{
var schemaQuery = KustoSchemaQueryBuilder.GetKustoSchemaQuery(connectionDetails!.EntityName);
var clientRequestProperties = new ClientRequestProperties() { ClientRequestId = Guid.NewGuid().ToString() };
var queryTask = client.ExecuteQueryAsync(connectionDetails.DatabaseName, schemaQuery, clientRequestProperties);
using (var reader = await queryTask.ConfigureAwait(false))
{
using (reader)
{
return reader.ToEnumerable<DatasetColumn>().ToArray();
}
}
}
Hoping that I will get an array of DatasetColumn objects. I do in fact get a list of DatasetObjects, the number of objects in the list corresponds to the number of results that I am expecting but all the objects have all the fields set to the default values (nulls for strings and 0 for ints).
What am I doing wrong and how do I get this method to return properly initialized objects? Am I misunderstanding the point of this extension method?
Later edit: I am adding a minimal program to reproduce the issue
using Kusto.Cloud.Platform.Data;
using Kusto.Data;
using Kusto.Data.Net.Client;
using Newtonsoft.Json;
namespace ConsoleApp1
{
class DatasetColumn
{
public string? ColumnName { get; set; }
public int ColumnOrdinal { get; set; }
public string? DataType { get; set; }
public string? ColumnType { get; set; }
}
internal class Program
{
static void Main(string[] args)
{
var sb = new KustoConnectionStringBuilder("https://help.kusto.windows.net/Samples; Fed=true; Accept=true");
using (var client = KustoClientFactory.CreateCslQueryProvider(sb))
{
using (var result = client.ExecuteQuery("StormEvents | getschema"))
{
var cols = result.ToEnumerable<DatasetColumn>().ToArray();
Console.WriteLine(JsonConvert.SerializeObject(cols));
}
}
}
}
}
the underlying implementation for the extension method of the DataReader that your code is using, and that is provided as part of the client library, doesn't currently support properties or private fields.
You could either change the properties in the class DatasetColumn to fields, or write your own implementation.
e.g., replace this:
public int ColumnOrdinal { get; set; }
with this:
public int ColumnOrdinal;

Incorrect mapping with Dapper, .NET Core and Postgres

Thanks in advance. I'm seeing an issue with my mapping using Postgres and Dapper. I'm trying to map an Organization object, which has a one-to-many relationship with the Location object. The following is my mapping code:
public Organization GetOrganizationById(long id)
{
var query = #"SELECT
o.organization_id,
o.guid,
o.name,
o.is_active,
o.created,
l.location_id,
l.org_id,
l.guid,
l.internal_identifier,
l.name,
l.address,
l.city,
l.state,
l.zip,
l.phonenumber,
l.is_active,
l.opendate,
l.closedate,
l.created
FROM organization AS o
INNER JOIN location as l
ON o.organization_id = l.org_id
WHERE o.organization_id = #Id";
using (var con = new NpgsqlConnection(_connectionString))
{
var orgResult = con.Query<Organization, List<Location>, Organization>(
query,
(org, locations) =>
{
org.Locations = locations;
return org;
},
new {Id = id},
splitOn: "location_id").FirstOrDefault();
return orgResult;
}
}
I have the following objects created:
public class Organization
{
public long OrganizationId { get; set; }
public Guid Guid { get;set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public DateTime Created { get; set; }
//[JsonIgnore]
public List<Location> Locations { get; set; }
}
and
public class Location
{
public long LocationId { get; set; }
public long OrgId { get; set; }
public Guid Guid { get; set; }
public string InternalIdentifier { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string PhoneNumber { get; set; }
public bool IsActive { get; set; }
public DateTime OpenDate { get; set; }
public DateTime? CloseDate { get; set; }
public DateTime Created { get; set; }
[JsonIgnore]
public Organization Organization { get; set; }
}
Now the issue is, when I get the results it's not even close to accurate. Though the query when copied into a SQL client it's returning the correct results.
Here's what I see in the JSON response along with what's incorrect:
{
"organizationId": 0, // This is incorrect. The organization ID is one (this appears to be the default int/long)
"guid": "4fc55437-8497-4388-be48-c6b8c5dfee93", // This is correct
"name": "TestOrg", // This is correct
"isActive": false, // This is incorrect. Both locations are active, as is the organization
"created": "2021-01-27T05:20:42.287925", // This is correct
"locations": [] // Incorrect, there should be two locations present
}
Anyway, is there anything you think I'm missing with the mapping that would prevent these records from not mapping to their POCOs correctly?
Dapper requires your model property names to mirror your table (not case sensitive).
You can try using the [ColumnName()] attribute above your properties. I've heard this works for some but it didn't for me. I ended up using AutoMapper and created a mapper object.
[ColumnName("SomeFieldName")]
public string SomeProperty { get; set; }
Before you look into that (cause it's a pain) try using Dapper's QueryMultiple() method.
Dapper's Query Multiple
You may have to play around with it a bit. It's been awhile since I've written the full QueryMultiple() method the Dapper way. I made a wrapper class to containerize my dapper functions (I just containerize all 3rd party packages incase of exceptions or something). Here's one of my methods using QueryMultiple(). to return 2 data sets using out parameters. You can adjust this to return as may sets as you need.
/// <summary>
/// Executes a query with multiple results and stores the result sets in the out parameters.
/// </summary>
/// <typeparam name="T1">The type of the first result set.</typeparam>
/// <typeparam name="T2">The type of the second result set.</typeparam>
/// <typeparam name="P">The parameter type. Generally an autonomous/dynamic object, a <see cref="DynamicParameters"/>, or an <see cref="ExpandoObject"/>.</typeparam>
/// <param name="sql">The SQL query string or stored procedure.</param>
/// <param name="parameters">The parameter(s) for the stored procedure.</param>
/// <param name="results1">The first result set.</param>
/// <param name="results2">The second result set.</param>
/// <param name="queryType">
/// <para>The query's command type.</para>
/// Defaults to <strong><see cref="CommandType.StoredProcedure"/></strong>.
/// </param>
public void ExecuteQueryMultiple<T1, T2, P>(string sql, P parameters,
ConnStringKey connectionKey, // This is a personal app setting enum...
out List<T1> results1, out List<T2> results2,
CommandType queryType = CommandType.StoredProcedure)
{
using (IDbConnection connection = new
SqlConnection(configurations.GetConnectionString(connectionKey)))
{
using (SqlMapper.GridReader sqlReader = connection.QueryMultiple(sql,
parameters, commandType: queryType, commandTimeout: ConnectionTimeout))
{
results1 = sqlReader.Read<T1>().AsList();
results2 = sqlReader.Read<T2>().AsList();
}
}
}
You can use something similar if your SQL was like this:
SELECT
o.organization_id,
o.guid,
o.name,
o.is_active,
o.created,
FROM organization AS o
WHERE o.organization_id = #Id
SELECT
l.location_id,
l.org_id,
l.guid,
l.internal_identifier,
l.name,
l.address,
l.city,
l.state,
l.zip,
l.phonenumber,
l.is_active,
l.opendate,
l.closedate,
l.created
FROM location AS l
WHERE l.org_id = #Id
Of course, then you'd have to aggregate the two result sets in the code.
Usage:
var someParams = new { Id = someId };
sql = "your sql";
_sql.ExecuteQueryMultiple<Organization, Location, dynamic>(
sql, someParams,
// this is an enum of mine that ties back to my appsettings.json or app.config file to get the connection string
MyConfigurationConnectionKey,
out List<Organization> organizations,
out List<Location> locations,
CommandType.Text);
// Aggregate the two data sets...
organizations.Locations = locations; // or however you need to do it

Generic method to get an attribute from a member of a type

I created a convenience method that uses generics to retrieve an attribute applied to a type:
/// <summary>
/// Return an attribute from the provided type if it exists on that type.
/// </summary>
/// <typeparam name="T">The type whose attribute <typeparamref name="TAttType"/>
/// will be returned if it exists on that type.</typeparam>
/// <typeparam name="TAttType">The type of attribute that will be retrieved
/// on <typeparamref name="T"/> if it exists.</typeparam>
/// <returns>Return the attribute with type <typeparamref name="TAttType"/>,
/// if it exists, from target type <typeparamref name="T"/> or else
/// return null.</returns>
public static TAttType GetAttribute<T, TAttType>() where TAttType:Attribute
=> (TAttType) typeof(T).GetCustomAttribute(typeof(TAttType), false);
This only works for attributes on types though. I.e. if I have this attribute:
public class VehicleAttribute : Attribute
{
public string Color { get; }
public int NumWheels { get; }
public VehicleAttribute(string color, int numWheels)
{
Color = color;
NumWheels = numWheels;
}
}
I can do this:
[Vehicle("Yellow", 6)]
public class Bus { }
And then this:
var att = ReflectionTools.GetAttribute<Bus, VehicleAttribute>();
But if I have a property like this (not that it makes sense, but just for demo purposes):
[Vehicle("Blue", 5)]
public string Name { get; set; }
I want to be able to use a similar approach. Is there a way I can use generics to facilitate the retrieval of an attribute from any System.Reflection.MemberInfo, not just System.Type?
You can use a MemberExpression to specify the member you want to get the attribute from. Here is an example:
using System;
using System.Linq.Expressions;
using System.Reflection;
public class VehicleAttribute : Attribute
{
public string Color { get; }
public int NumWheels { get; }
public VehicleAttribute(string color, int numWheels)
{
Color = color;
NumWheels = numWheels;
}
}
[Vehicle("Yellow", 6)]
public class Bus
{
[Vehicle("Blue", 5)]
public string Name { get; set; }
}
public class Program
{
public static void Main()
{
Console.WriteLine(Test<Bus, VehicleAttribute>((x) => x.Name).Color);
}
static U Test<T, U>(Expression<Func<T, object>> expr) where U : Attribute
{
if(!(expr.Body is MemberExpression memberExpr))
{
throw new NotSupportedException("expr");
}
return (U)memberExpr.Member.GetCustomAttribute(typeof(U), false);
}
}

Foreign-Key References to the Same Entity

I have the below model:
class Client
{
[Key]
public int Id { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public Nullable<DateTime> date_naissance { get; set; }
public Sex? Sexe { get; set; }
public Client Parent { get; set; }
}
I used code first to generate my table. When I tried to save the records using the code below, I wasn't able to determine how to populate the Parent field. A Client can be a parent of other Clients.
Client client = new Client();
client.Id = int.Parse(item.ID);
client.Nom = item.Nom;
client.Prenom = item.Prenom;
client.date_naissance = DateTime.Parse(item.DateNaissance);
client.Sexe = (Sex)Enum.Parse(typeof(Sex), item.Sexe);
int parent;
bool par = int.TryParse(item.Parent, out parent);
// this does not work:
if (par)
client.Parent.Id = parent;
db.clients.Add(client);
db.SaveChanges();
If the parent Client instance is not already created, you will need to create a new one. You can always create a new Client instance and assign it the parent's Id, but the instance assigned will lack all of the other information about the parent. An example of this is below.
client.Parent = new Client() { Id = parentId };
Ideally, you will look up the parent from the context and assign it to the client:
var parent = context.Clients.Find(parentId);
if (parent != null)
{
client.Parent = parent;
}
else
{
// Handle an invalid ID
}
Additionally, I would suggest changing the Parent property to a virtual property:
public virtual Client Parent { get; set; }
This will allow you to take advantage of two useful features of the Entity Framework: lazy loading and automatic change tracking. Entities or collections of entities referenced by navigation properties specified with the virtual keyword will only be loaded the first time they are used. With the virtual keyword, accessing the Parent property the first time will load the Parent entity for that Client and return it. Without the virtual keyword, the Parent property will return null unless you explicitly load and assign a value to it.
Try making the navigation properties (i.e. Parent) virtual and don't forget to initialize them. So first you have to change your class property to:
public virtual Client Parent { get; set; }
and then in the code:
client.Parent = new Parent();
client.Parent.Id = parentId;
You may not want to pass the whole Client structure as the parent, you may pass only the integer ID to refer to the parent.
you must create an instance of the parent. or you can't set it.
class Client
{
public int Id { get; set; }
public Client Parent { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Client"/> class.
///
/// WITH PARENT
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="parent">The parent.</param>
public Client(int id, Client parent)
{
this.Id = id;
this.Parent = parent;
}
/// <summary>
/// Initializes a new instance of the <see cref="Client"/> class.
///
/// WITHOUT PARENT
/// </summary>
/// <param name="id">The identifier.</param>
public Client(int id)
{
this.Id = id;
}
}
public static void Main(string[] args)
{
Client client = new Client(1);
Client clientWithParent = new Client(2, client);
Console.Write("parent id :" + clientWithParent.Parent.Id);
}

Entity Framework updating entity with lookup table

So,
I have built a repository/service application which I have been using successfully for a while.
I have started a new project and in this project I have a lookup table attached to one of my models/entities.
The model looks like this:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public string Sport { get; set; }
public IList<Colour> Colours { get; set; }
}
I also have a binding ViewModel which looks like this:
public class TeamBindingViewModel
{
public int Id { get; set; }
[Required] public string Name { get; set; }
[Required] public string Sport { get; set; }
public IList<ColourBindingViewModel> Colours { get; set; }
}
public class ColourBindingViewModel
{
public int Id { get; set; }
[Required] public string Name { get; set; }
[Required] public string Hex { get; set; }
}
In my dbContext class I have set up this:
public class DatabaseContext : DbContext
{
// Define our tables
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Colour> Colours { get; set; }
public DbSet<Team> Teams { get; set; }
/// <summary>
/// static constructor (only gets called once)
/// </summary>
static DatabaseContext()
{
// Create the database and insert our records
//Database.SetInitializer<DatabaseContext>(new DatabaseInitializer());
}
/// <summary>
/// Default constructor
/// </summary>
public DatabaseContext()
: base("DefaultConnection")
{
// Disable Lazy Loading
base.Configuration.LazyLoadingEnabled = false;
}
/// <summary>
/// Overrides the inherited OnModelCreated method.
/// </summary>
/// <param name="modelBuilder">The DbModelBuilder</param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Remove Cascading Delete
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Map the TeamColours table
modelBuilder.Entity<Team>()
.HasMany(m => m.Colours)
.WithMany()
.Map(m =>
{
m.MapLeftKey("TeamId");
m.MapRightKey("ColourId");
m.ToTable("TeamColours");
});
}
}
(nb: I have stripped this down to make it easier to read)
So, the problem I have is when I try to do an update, if I try to add colours, I get an error.
Here is a look at my base repository class:
public class Repository<T> : IDisposable, IRepository<T> where T : class
{
private readonly DbContext context;
private readonly DbSet<T> dbEntitySet;
public Repository(DbContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
this.dbEntitySet = context.Set<T>();
}
public IQueryable<T> GetAll(params string[] includes)
{
IQueryable<T> query = this.dbEntitySet;
foreach (var include in includes)
query = query.Include(include);
return query;
}
public void Create(T model)
{
this.dbEntitySet.Add(model);
}
public void Update(T model)
{
this.context.Entry<T>(model).State = EntityState.Modified;
}
public void Remove(T model)
{
this.context.Entry<T>(model).State = EntityState.Deleted;
}
public void Dispose()
{
this.context.Dispose();
}
}
I then have a base service class like this:
public class Service<T> where T : class
{
private readonly IRepository<T> repository;
protected IRepository<T> Repository
{
get { return this.repository; }
}
public Service(IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
this.repository = unitOfWork.GetRepository<T>();
}
}
and finally here is my TeamService class:
/// <summary>
/// Team service handles all team related functions
/// </summary>
public class TeamService : Service<Team>
{
/// <summary>
/// Default constructor
/// </summary>
/// <param name="unitOfWork"></param>
public TeamService(IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
/// <summary>
/// Get all teams asynchronously
/// </summary>
/// <param name="includes">Eager loading includes</param>
/// <returns>A list of colours</returns>
public async Task<IList<Team>> GetAllAsync(params string[] includes)
{
return await this.Repository.GetAll(includes).ToListAsync();
}
/// <summary>
/// Get a team by id
/// </summary>
/// <param name="id">The id of the colour</param>
/// <param name="includes">Eager loading includes</param>
/// <returns>A colour</returns>
public async Task<Team> GetAsync(int id, params string[] includes)
{
var models = await this.GetAllAsync(includes);
return models.Where(model => model.Id == id).SingleOrDefault();
}
/// <summary>
/// Create a team
/// </summary>
/// <param name="model">The team model</param>
public void Create(Team model)
{
// Create a team
this.Repository.Create(model);
}
/// <summary>
/// Update a team
/// </summary>
/// <param name="model">The team model</param>
public void Update(Team model)
{
// Update a team
this.Repository.Update(model);
}
/// <summary>
/// Delete a team
/// </summary>
/// <param name="model">The team model</param>
public void Remove(Team model)
{
// Remove a team
this.Repository.Remove(model);
}
}
I know there is a lot of code here, but I need to give you my entire process if anyone can help me :)
So, If I create an update method in my controller like this:
private async Task<IHttpActionResult> Save(TeamBindingViewModel model)
{
// If our model is invalid, return the errors
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Create a list of colours
var colours = new List<Colour>();
// For each colour in our model, add to our list
foreach (var colour in model.Colours)
colours.Add(new Colour()
{
Id = colour.Id,
Name = colour.Name,
Hex = colour.Hex
});
// If there is an id
if (model.Id > 0)
{
// Update our team
await this.Update(model, colours);
}
else
{
// Create our team
this.Create(model, colours);
}
// Save the database changes
await this.unitOfWork.SaveChangesAsync();
// Return Ok
return Ok(model);
}
private void Create(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
var team = new Team()
{
Id = model.Id,
Name = model.Name,
Sport = model.Sport
};
// Assign our colours to our team
team.Colours = colours;
// Otherwise, create a new team
this.service.Create(team);
}
private async Task Update(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
var team = new Team()
{
Id = model.Id,
Name = model.Name,
Sport = model.Sport
};
// Update the team
this.service.Update(team);
}
private IList<Colour> GetDifference(IList<Colour> firstList, IList<Colour> secondList)
{
// Create a new list
var list = new List<Colour>();
// Loop through the first list
foreach (var first in firstList)
{
// Create a boolean and set to false
var found = false;
// Loop through the second list
foreach (var second in secondList)
{
// If the first item id is the same as the second item id
if (first.Id == second.Id)
{
// Mark it has being found
found = true;
}
}
// After we have looped through the second list, if we haven't found a match
if (!found)
{
// Add the item to our list
list.Add(first);
}
}
// Return our differences
return list;
}
The update will process and everything works. But if I change my update method to this:
private async Task Update(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
var team = new Team()
{
Id = model.Id,
Name = model.Name,
Sport = model.Sport
};
// Get our current model
var current = await this.service.GetAsync(model.Id, "Colours");
var currentColours = current.Colours;
// Assign our original colours to our team
team.Colours = currentColours;
// Get our colours to remove and add
var coloursToRemove = GetDifference(currentColours, colours);
var coloursToAdd = GetDifference(colours, currentColours);
// Loop through our colours to remove and remove them
if (coloursToRemove.Count > 0)
foreach (var colour in coloursToRemove)
team.Colours.Remove(colour);
// Loop through the colours to add and add them
if (coloursToAdd.Count > 0)
foreach (var colour in coloursToAdd)
team.Colours.Add(colour);
// Update the team
this.service.Update(team);
}
I get this error:
"exceptionMessage": "Attaching an entity of type 'Models.Team' 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 am not sure why I get this error, but I assume it has something to do with trying to add / remove colours from the lookup table.
Can anyone provide me with a solution to this problem?
your prolblem occurs because you are trying to update "team" while you have the "current" variable holdes the entity in the same scope try this:
private async Task Update(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
//var team = new Team()
//{
// Id = model.Id,
// Name = model.Name,
// Sport = model.Sport
//};
// Get our current model
var current = await this.service.GetAsync(model.Id, "Colours");
current.Name = model.Name;
current.Sport = model.Sport;
var currentColours = current.Colours;
// Assign our original colours to our team
//team.Colours = currentColours;
// Get our colours to remove and add
var coloursToRemove = GetDifference(currentColours, colours);
var coloursToAdd = GetDifference(colours, currentColours);
// Loop through our colours to remove and remove them
if (coloursToRemove.Count > 0)
foreach (var colour in coloursToRemove)
current.Colours.Remove(colour);
// Loop through the colours to add and add them
if (coloursToAdd.Count > 0)
foreach (var colour in coloursToAdd)
current.Colours.Add(colour);
// Update the team
this.service.Update(current);
}

Categories