ADO.NET distinct data bases - ado.net

What programmers needed was a way to generalize different data systems
in a standard, consistent, and powerful way. In the world of .NET
application development, Microsoft ADO.NET meets that need. Instead of
worrying about the minutiae associated with the different database
systems, programmers using ADO.NET focus on the data content itself.
From the book "ADO.NET 4 Step by Step"
Always thinking the next structure.
ADO.NET can be sepearated on two parts:
1. Classic ADO.NET (DataSets, DataTables, etc.). In classic variant there are distinct DB connection providers, every which translates DB internal data to the .NET. For example MS SQL Server holds data in one way and Oracle in another. So we can change the data base with changing provider.
Seems as magic pill, however all ADO.NET statements are hard coded.
For MS SQL top 10 select statement is SELECT TOP 10 ROWS FROM TABLE, and for Oracle SELECT ROWS FROM TABELE WHERE ROWNUM <= 10. It seems that changing DB and provider will not help, isnt' it?
2. Entity Framework. This framework has internal independent language statements that is transformed to the choosen DB statesment, which looks as truely magic pill:
LINQ -> Internal EF statement -> MS SQL DB,
LINQ -> Internal EF statement -> Oracle DB.
So can one simply change the DB and be almost independent from DB in classic ADO.NET?

So can one simply change the DB and be almost independent from DB in classic ADO.NET?
Of course you can, but to be able to do that we have to debunk this statement.
Seems as magic pill, however all ADO.NET statements are hard coded.
That's not a byproduct of ADO.NET - that's a byproduct of your architecture. You're building the SQL statements in the wrong place. You need concrete, provider specific models, that are capable of building statements that differ between providers. This isn't as bad as it sounds - most statements can be auto-generated leveraging reflection - it's just the special cases.
For example, let's say I had a model like this:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
and let's say I wanted to generate a SELECT statement out of that. Well, I'm going to first need a couple attributes to tell me what property is the PK and what properties are data fields:
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
internal sealed class DataFieldAttribute : Attribute
{
public DataFieldAttribute()
{
}
}
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class PrimaryKeyAttribute : Attribute
{
public PrimaryKeyAttribute()
{
}
}
and now I need to decorate that class:
public class Employee
{
[PrimaryKey]
public int ID { get; set; }
[DataField]
public string Name { get; set; }
[DataField]
public DateTime DateOfBirth { get; set; }
}
and now I just need a simple process to create a SELECT statement, so first let's build a base data model class:
public abstract class DataModelBase
{
protected string _primaryKeyField;
protected List<string> _props = new List<string>();
public DataModelBase()
{
PropertyInfo pkProp = this.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(PrimaryKeyAttribute), false).Length > 0).FirstOrDefault();
if (pkProp != null)
{
_primaryKeyField = pkProp.Name;
}
foreach (PropertyInfo prop in this.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(DataFieldAttribute), false).Length > 0))
{
_props.Add(prop.Name);
}
}
public virtual string TableName { get { return this.GetType().Name; } }
public virtual string InsertStatement
{
get
{
return string.Format("INSERT INTO [{0}] ({1}) VALUES ({2})",
this.TableName,
GetDelimitedSafeFieldList(", "),
GetDelimitedSafeParamList(", "));
}
}
public virtual string UpdateStatement
{
get
{
return string.Format("UPDATE [{0}] SET {1} WHERE [{2}] = #{2}",
this.TableName,
GetDelimitedSafeSetList(", "),
_primaryKeyField);
}
}
public virtual string DeleteStatement
{
get
{
return string.Format("DELETE [{0}] WHERE [{1}] = #{1}",
this.TableName,
_primaryKeyField);
}
}
public virtual string SelectStatement
{
get
{
return string.Format("SELECT [{0}], {1} FROM [{2}]",
_primaryKeyField,
GetDelimitedSafeFieldList(", "),
this.TableName);
}
}
protected string GetDelimitedSafeParamList(string delimiter)
{
return string.Join(delimiter, _props.Select(k => string.Format("#{0}", k)));
}
protected string GetDelimitedSafeFieldList(string delimiter)
{
return string.Join(delimiter, _props.Select(k => string.Format("[{0}]", k)));
}
protected string GetDelimitedSafeSetList(string delimiter)
{
return string.Join(delimiter, _props.Select(k => string.Format("[{0}] = #{0}", k)));
}
}
and now let's inherit from that data model:
public class Employee : DataModelBase
and boom, now I can get those statements any time I need them, and those statements work for any concrete provider right now.
And then I'll use Dapper to get the data because it leverages the IDbConnection interface like what you need and it's ridiculously fast - and there you go - a provider independent solution that would easily be extended to build an Oracle version of Employee if necessary.
This framework has internal independent language statements that is transformed to the choosen DB statesment, which looks as truely magic pill
Sure, it may look like a magic pill, but it's really a curse in a lot of ways. You have no flexibility (at least it's not easy) to build statements that are optimized for your needs to support high transaction and volume databases. You truly are subject to the master here. The .NET Entity Framework builds those statements for you, and I can't even count how many questions on StackOverflow have been over how can I change the SQL that's being generated by this LINQ statement leveraging the .NET Entity Framework.

was staring to play with this. I know this is a old post. thanks to the above example herewith little modified. Still lots to be done
using System.Collections.Generic;
using System.Reflection;
using Dapper;
using System.Linq;
using AppAttributes;
using System.ComponentModel.DataAnnotations;
using System;
public abstract class DataModelBase
{
protected string _primaryKeyField;
protected List<string> _props = new List<string>();
protected List<BuildClass> _class = new List<BuildClass>();
public DataModelBase()
{
PropertyInfo pkProp = this.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Length > 0).FirstOrDefault();
if (pkProp != null)
{
_primaryKeyField = pkProp.Name;
}
foreach (PropertyInfo prop in this.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(DataFieldAttribute), false).Length > 0))
{
_props.Add(prop.Name);
}
foreach(PropertyInfo prop in this.GetType().GetProperties())
{
if(prop.GetCustomAttributes<ExcludeAttribute>().Count<ExcludeAttribute>() > 0) continue;
MaxLengthAttribute maxLength = prop.GetCustomAttribute<MaxLengthAttribute>();
MinLengthAttribute minLength = prop.GetCustomAttribute< MinLengthAttribute>();
StringLengthAttribute stringLength = prop.GetCustomAttribute< StringLengthAttribute>();
RequiredAttribute required = prop.GetCustomAttribute<RequiredAttribute>();
RangeAttribute range = prop.GetCustomAttribute<RangeAttribute>();
DataTypeAttribute dataType = prop.GetCustomAttribute<DataTypeAttribute>();
KeyAttribute key = prop.GetCustomAttribute<KeyAttribute>();
var kyk = prop.PropertyType;
//var sss = kyk.FullName.;
var cl = new BuildClass
{
Name = prop.Name,
MaxLength = maxLength != null
? (int?)maxLength.Length
: stringLength != null
? (int?)stringLength.MaximumLength : null,
MinLength = minLength != null
? (int?)minLength.Length
: stringLength != null
? (int?)stringLength.MinimumLength : null,
PrimaryKey = key != null ? true : false,
Type = prop.PropertyType.Name.ToString()
};
_class.Add(cl);
}
}
[Exclude]
public virtual string TableName { get { return this.GetType().Name; } }
[Exclude]
public virtual string InsertStatement
{
get {
return string.Format("INSERT INTO [{0}] ({1}) VALUES ({2})",
this.TableName,
GetDelimitedSafeFieldList(", "),
GetDelimitedSafeParamList(", "));
}
}
[Exclude]
public virtual string UpdateStatement
{
get {
return string.Format("UPDATE [{0}] SET {1} WHERE [{2}] = #{2}",
this.TableName,
GetDelimitedSafeSetList(", "),
_primaryKeyField);
}
}
[Exclude]
public virtual string DeleteStatement
{
get {
return string.Format("DELETE [{0}] WHERE [{1}] = #{1}",
this.TableName,
_primaryKeyField);
}
}
[Exclude]
public virtual string SelectStatement
{
get {
return string.Format("SELECT [{0}], {1} FROM [{2}]",
_primaryKeyField,
GetDelimitedSafeFieldList(", "),
this.TableName);
}
}
[Exclude]
public virtual string CreateStatement
{
get {
return "CREATE TABLE " + TableName+" (" + GetDelimetedCreateParamList(",")
+ ", CONSTRAINT PK_"
+ _class.Where(c=>c.PrimaryKey).FirstOrDefault().Name
+ " PRIMARY KEY("
+ string.Join(",", _class.Where(c=>c.PrimaryKey).Select(c=>c.Name)) + ") )";
}
}
protected string GetDelimetedCreateParamList(string delimeter)
{
return string.Join(delimeter, _class.Select(k => string.Format(" {0} {1} ({2}) {3}" + Environment.NewLine,
k.Name,
GetSqlType(k.Type),
k.MaxLength,
k.NotNull == true || k.PrimaryKey == true ? "NOT NULL " : ""
//k.PrimaryKey == true ? "PRIMARY KEY" : ""
).Replace("()", ""))
);
}
protected string GetSqlType(string type)
{
switch(type.ToUpper())
{
case "INT16":
return "smallint";
case "INT16?":
return "smallint";
case "INT32":
return "int";
case "INT32?":
return "int";
case "INT64":
return "bigint";
case "INT64?":
return "bigint";
case "STRING":
return "NVARCHAR";
case "XML":
return "Xml";
case "BYTE":
return "binary";
case "BYTE?":
return "binary";
case "BYTE[]":
return "varbinary";
case "GUID":
return "uniqueidentifier";
case "GUID?":
return "uniqueidentifier";
case "TIMESPAN":
return "time";
case "TIMESPAN?":
return "time";
case "DECIMAL":
return "money";
case "DECIMAL?":
return "money";
case "bool":
return "bit";
case "bool?":
return "but";
case "DateTime":
return "datetime";
case "datetime?":
return "datetime";
case "double":
return "float";
case "double?":
return "float";
case "char[]":
return "nchar";
}
return "UNKNOWN";
}
private string CreateField(BuildClass column)
{
return " " + column.Name + " " + column.Type + " (" + column.MaxLength + ") ";
}
protected string GetDelimitedSafeParamList(string delimiter)
{
return string.Join(delimiter, _props.Select(k => string.Format("#{0}", k)));
}
protected string GetDelimitedSafeFieldList(string delimiter)
{
return string.Join(delimiter, _props.Select(k => string.Format("[{0}]", k)));
}
protected string GetDelimitedSafeSetList(string delimiter)
{
return string.Join(delimiter, _props.Select(k => string.Format("[{0}] = #{0}", k)));
}
}
public class BuildClass
{
public string Name { get; set; }
public string Type { get; set; }
public bool PrimaryKey { get; set; }
//public bool ForeignKey { get; set; }
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public bool NotNull { get; set; } = false;
}

Related

ASP MVC EF6 Multi Tenant based on host

Sorry, another multi tenancy post. I can't find a good solution to site, I have read tons of great posts on multi tenancy for ASP MVC but I still need some good advice.
I have an ASP MVC Entity Framework 6 Code First web application. This app has to work for many different clients using a single database for all of them.
I have an entity for all the clients, and each client can have different hosts.
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
...
public ICollection<ClientHost> Hosts { get; set; }
}
public class ClientHost
{
public int ClientId { get; set; }
public Client Client { get; set; }
public string Name { get; set; }
}
I have added a column "ClientId" to all the entities I need to filter, so I can separate data from different clients.
public class SomeEntity
{
public int Id { get; set; }
...
public int ClientId { get; set; }
}
First thing I need is, base on the host, retrieve the ClientId to work with.
private static int GetClientId()
{
var currentClient = Convert.ToInt32(HttpRuntime.Cache[CacheClient]);
if (currentClient != null) return currentClient;
lock (Synclock)
{
using (var dataContext = new MyDataContext())
{
var urlHost = HttpContext.Current.Request.Url.Host;
currentClient = dataContext.Clients
.FirstOrDefault(p => p.Hosts.Any(h => h.Name == urlHost));
if (currentClient == null) return null;
HttpRuntime.Cache.Insert(CacheClient, currentClient, null, Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(0), CacheItemPriority.Default, null);
return currentClient;
}
}
}
QUESTION 1
As you see I get the clientId from DB and store it in cache, so I don't have to call DB every time I need it.
I don't know if there is a better approach to get the client Id or, better, to store it.
EDIT
After investigation I have created a variable in DbCOntext and initialize it in the Startup.cs file.
public class MyDataContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public static string ClientId { get; set; }
public MyDataContext() : base("MyDataBase") { }
public static MyDataContext Create()
{
return new myDataContext();
}
....
}
In Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
MyDataContext.ClientId = ClientConfiguration.GetCurrentClientId();
ConfigureAuth(app);
}
}
QUESTION 2
Once I have the ClientId, I need to add a filter to every query that needs it. Doing this manually can take you to make many errors or forget to do it in some places.
I need a way that the application can add the filter to all queries automatically (only those entities that need it), so I don't have to worry about a client getting other client's data. Also I need to add the ClientId to all the Insert and Update commands.
I have read about filtering and/or use EF Interceptors, but after reading some posts about that I can't figure out how to do it. Need some help here.
Thanks in advance.
EDIT
In order to solve QUESTION 2 I have followed this great post by Xabikos:
http://xabikos.com/2014/11/17/Create-a-multitenant-application-with-Entity-Framework-Code-First-Part-1/
I have changed it a little bit, since I don't use Users to get the current tenant and instead I use the host. This is part of the program I don't know yet how I'm going to solve but, assuming I already have the ClientId I can add filters to all the queries without realizing that is happening:
I have replaced all the user logic:
private static void SetTenantParameterValue(DbCommand command)
{
if (MyDataContext.ClientId == 0) return;
foreach (DbParameter param in command.Parameters)
{
if (param.ParameterName != TenantAwareAttribute.TenantIdFilterParameterName)
continue;
param.Value = MyDataContext.ClientId;
}
}
Same in all the places...
Than I only have to mark the entities that have to filter with TenantAware, indicating the property. In this case I do in my base class and then apply that base class to all the entities I need.
[TenantAware("ClientId")]
public abstract class ClientEntity : Entity, IClientEntity
{
public int ClientId { get; set; }
public Client Client { get; set; }
}
Here are a couple of things I have done in the past that might help.
Question 1:
I am not a big fan of session as the web is supposed to be stateless. However, it is sometimes necessary. Your approach is reasonable. You could also use cookies as well. What I use are Json Web Tokens (JWT) via my authentication provider (Auth0.com). For each request as it is authenticated, I look for this client id. Here is an example. This is MVC 6 as well. You could do the same type of things w/ cookies.
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
Then in my Startup.cs Configure method I plug in my claims transformer.
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
Next I use a base authentication controller that parses out my claims into properties I can use in my controller.
[Authorize]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
Finally in my controller it is trivial to extract which tenant is making the call. e.g.
public FooController : BaseController
{
public async Task<IActionResult> Get(int id)
{
var foo = await _fooService.GetMultiTenantFoo(ClientId, id);
return Ok(foo);
}
}
Question 2:
One of the ways I have used in the past is create a BaseMultiTenant class.
public class BaseMultiTenant
{
public int ClientId {get;set;}
public virtual Client Client {get;set;}//if you are using EF
}
public class ClientHost : BaseMultiTenant
{
public string Name {get;set;}
//etc
}
Then simply create an extension method for multi-tenant based entities. I know this doesn't "do it automatically" but it is an easy way to ensure each multi-tenant entity is being called only by its owner.
public static IQueryable<T> WhereMultiTenant<T>(this IQueryable<T> entity, int clientId, Expression<Func<T, bool>> predicate)
where T : BaseMultiTenant
{
return entity.Where(x => x.ClientId == clientId)
.Where(predicate);
}
Then when someone calls for their resource you can:
var clientHost = _myContext.ClientHosts
.WhereMultiTenant(ClientId,
x => x.Name == "foo")
.FirstOrDefault();
Hope this is helpful.
Also found a similar example using an interface.

AddOrUpdate violates unique index

I'm writing an MVC app in ASP.NET with the help of EF and I'm trying to seed my database. I have the following model:
public class Team
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ForeignKey("ParentTeam")]
public int? ParentTeamId { get; set; }
[Required(ErrorMessage = "Cannot create a Team without a name")]
[Index(IsUnique = true)]
[MaxLength(30)]
public string Name { get; set; }
public IEnumerable<string> Members { get; set; }
public virtual Team ParentTeam { get; set; }
public Team() { }
public Team(string name)
{
Name = name;
}
}
My migration says:
var team = new Team("Admin");
var team2 = new Team("Test Team");
var team3 = new Team("Test Team 2");
context.Teams.AddOrUpdate(t => t.Name, team, team2, team3);
context.SaveChanges();
And then, when I run Update-Database, I get:
System.Data.SqlClient.SqlException: Cannot insert duplicate key row in
object 'dbo.Teams' with unique index 'IX_Name'. The duplicate key
value is (Admin).
It's a little confusing - I thought I told AddOrUpdate to identify rows to update by their names, but this does not happen. I cannot add Name to Team's primary key, because it has a self-referencing foreign key (I could add ParentTeamName as a property, but I don't feel that it should be necessary). Am I misunderstanding the behaviour of AddOrUpdate? Did I specify the condition wrong?
I had the exact same reason. In my case, it was working fine, until I needed to use an Unique Index, when it broke.
My solution was to create a CustomAddOrUpdate method where I try to find the existing instance first based on a Where predicate. If I find it, I just update the properties and if not, it is added to the context.
However, before updating the instance, I had to copy the key values from the original instance to the new instance, to avoid an EF exception telling you cannot change key properties.
Here are the code snippets:
1) First the code in the context class
public void CustomAddOrUpdate<TEntity>(Expression<Func<TEntity, bool>> whereExpression, TEntity entity) where TEntity : class
{
var entitySet = this.EntitySet<TEntity>();
var foundInstance = entitySet.Where(whereExpression).FirstOrDefault();
if (foundInstance != null)
{
CopyKeyProperties<TEntity>(foundInstance, entity);
Entry(foundInstance).CurrentValues.SetValues(entity);
}
else
{
entitySet.Add(entity);
}
}
private void CopyKeyProperties<TEntity>(TEntity source, TEntity target) where TEntity : class
{
string[] keys = this.GetKeyNames<TEntity>();
foreach(var keyName in keys)
{
Entry(target).Property(keyName).CurrentValue = Entry(source).Property(keyName).CurrentValue;
}
}
2) Then on my seed code:
var entityList = new List<MyExempleEntity>()
{
new MyExampleEntity { Prop1 = "a p1", Prop2 = "a p2" },
new MyExampleEntity { Prop1 = "b p1", Prop2 = "b p2" },
new MyExampleEntity { Prop1 = "c p1", Prop2 = "c p2" },
}
foreach(var item in entityList)
{
context.CustomAddOrUpdate<MyExampleEntity>(x => x.Prop1 == item.Prop1 && x.Prop2 == item.Prop2, item);
}
context.SaveChanges()
3) And to wrap up, here you are the code to get the KeyProperties from an entity:
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace System.Data.Entity
{
public static class DbContextExtensions
{
public static string[] GetKeyNames<TEntity>(this DbContext context)
where TEntity : class
{
return context.GetKeyNames(typeof(TEntity));
}
public static string[] GetKeyNames(this DbContext context, Type entityType)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MetadataWorkspace metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
// Get the mapping between CLR types and metadata OSpace
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
// Get metadata for given CLR type
var entityMetadata = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == entityType);
return entityMetadata.KeyProperties.Select(p => p.Name).ToArray();
}
}
}
The above code was grabbed from this blog:
https://romiller.com/2014/10/07/ef6-1-getting-key-properties-for-an-entity/

Using sets of Entity Framework entities at runtime

I have an EF6 setup against a sql server db with about 60 tables in it.
I have entities for each table. What i'm trying to do is run the same method against a set of these entities that will be known at runtime.
The method is a qa/qc routine that does some data check on particular fields that are assured to be in each table.
I guess what i want to do is make the entity a parameter to the method so i can call it consecutive times.
I would also want to make a set of entities to pass as the parameter.
something like this:
List<string> entList = new List<string>(){"Table1","Table2","Table3"};
foreach (entName in entList)
{
//create an entity with the string name
//call myQAQCMethod with the entity
}
MyQAQCMethod (entity SomeEntity)
{
//run against this entity
doQAQC(SomeEntity);
}
Can this be done? Is it a job for reflection?
EDIT
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
is precisely want i want to do. The thing is I want to avoid typing this loop 60 times. I think i'm looking for a way to "feed" a set of entities to this single method.
Also, thank you very much for helping me. I'm learning a lot.
You need to abstract an interface (entity framework won't even notice):
interface IQaQcable
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
public class EntityA : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
public class EntityB : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
// in some unknown utility class
void MyQaQcMethod<T>(T entity) where T : IQaQcable
{
doSomethingWithIQaQcableProperties(entity.CommonInt, entity.CommonString);
}
// in some unknown test class
void Test()
{
var entities = new List<IQaQcable> { new EntityA(), new EntityB() };
foreach (var e in entities)
MyQaQcMethod(e);
}
Now, you could extract a base class from which each derives that actually implements the CommonInt and CommonString properties for each entity needing them, but that can get kind of tricky with Table-Per-Type/Table-Per-Hierarchy, so I'd start with this, and then consider introducing either an abstract or concrete base class as an improvement.
EDIT
Maybe your looking for something simpler than I first thought, based on your last comment.
Let's give ourselves what the DbContext for this might look like:
class Context : DbContext
{
public virtual DbSet<EntityA> EntityAs { get; set; }
public virtual DbSet<EntityB> EntityBs { get; set; }
}
So, it could just be that you wish to do this:
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
Keeping in mind, if there is some set of properties in common across entity classes, you could still do something like the following:
IEnumerable<T> MyQaQcMethod(IQueryable<T> entities, T referenceEntity) where T : IQaQcAble
{
return entities.Where(e => SomePredicate(e, referenceEntity));
}
void Test()
{
using (var context = new Context())
{
// EntityA implements IQaQcAble
var resultsForA = MyQaQcMethod(context.EntityAs, defaultEntity).ToArray();
// so does EntityB, so can call with either
var resultsForB = MyQaQcMethod(context.EntityBs, defaultEntity).ToArray();
}
}
Keep in mind, to avoid modifying the generated entity classes, you could implement the interface members — and the interface — in a separate source file using partial classes. E.g.
// IQaQcAble.cs
internal interface IQaQcAble
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
// a class whose existing property names match the interface
public partial class EntityA : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return CommonInt; }
set { CommonInt = value; }
}
string IQaQcAble.CommonString
{
get { return CommonString; }
set { CommonString = value; }
}
}
// a class whose property names differ
public partial class EntityB : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return SomeOtherInt; }
set { SomeOtherInt = value; }
}
string IQaQcAble.CommonString
{
get { return SomeOtherInt.ToString(); }
set { SomeOtherInt = Convert.ToInt32(value); }
}
}

How do I patch enumerables with System.Web.Http.OData.Delta?

Trying to make use of System.Web.Http.OData.Delta to implement PATCH methods in ASP.NET Web API services, but it seems unable to apply changes to properties of type IEnumerable<T>. I'm using the latest Git revision of Delta (2012.2-rc-76-g8a73abe). Has anyone been able to make this work?
Consider this data type, which it should be possible to update in a PATCH request to the Web API service:
public class Person
{
HashSet<int> _friends = new HashSet<int>();
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<int> Friends
{
get { return _friends; }
set
{
_friends = value != null ? new HashSet<int>(value) : new HashSet<int>();
}
}
public Person(int id, string firstName, string lastName)
{
Id = id;
FirstName = firstName;
LastName = lastName;
}
public Person()
{
}
}
This Web API method implements patching of a Person through Delta<Person>:
public void Patch(int id, Delta<Person> delta)
{
var person = _persons.Single(p => p.Id == id);
delta.Patch(person);
}
If I send a PATCH request with the following JSON to the service, the person's Friends property should be updated, but alas it doesn't happen:
{"Friends": [1]}
The crux of the matter is really how to make Delta update Friends with this data. See also the discussion at CodePlex.
The problem likely is that Deta will try to assign JSON's JArray to your Hashset<int>
If you are using it against JsonMEdiaTypeFormatter and you internalized the Delta code (meaning you can modify it), you'd have to do something like this (this is rough, but works):
Inside, bool TrySetPropertyValue(string name, object value) of Delta<T>, where it returns false:
if (value != null && !cacheHit.Property.PropertyType.IsPrimitive && !isGuid && !cacheHit.Property.PropertyType.IsAssignableFrom(value.GetType()))
{
return false;
}
Change to:
var valueType = value.GetType();
var propertyType = cacheHit.Property.PropertyType;
if (value != null && !propertyType.IsPrimitive && !propertyType.IsAssignableFrom(valueType))
{
var array = value as JArray;
if (array == null)
return false;
var underlyingType = propertyType.GetGenericArguments().FirstOrDefault() ??
propertyType.GetElementType();
if (underlyingType == typeof(string))
{
var a = array.ToObject<IEnumerable<string>>();
value = Activator.CreateInstance(propertyType, a);
}
else if (underlyingType == typeof(int))
{
var a = array.ToObject<IEnumerable<int>>();
value = Activator.CreateInstance(propertyType, a);
}
else
return false;
}
This will only work with collections of int or string but hopefully nudges you into a good direction.
For example, now your model can have:
public class Team {
public HashSet<string> PlayerIds { get; set; }
public List<int> CoachIds { get; set; }
}
And you'd be able to successfully update them.
You could override the TrySetPropertyValue method of the Delta class and make use of JArray class:
public sealed class DeltaWithCollectionsSupport<T> : Delta<T> where T : class
{
public override bool TrySetPropertyValue(string name, object value)
{
var propertyInfo = typeof(T).GetProperty(name);
return propertyInfo != null && value is JArray array
? base.TrySetPropertyValue(name, array.ToObject(propertyInfo.PropertyType))
: base.TrySetPropertyValue(name, value);
}
}
If you are using the ODataMediaTypeFormatter, this should be working. There are a couple of caveats to mention though.
1) your collections have to be settable.
2) the entire collection is replaced. you cannot remove/add individual elements.
Also, there is an issue tracking item 1 - '670 -Delta should support non-settable collections.'

Entity framework: writting custom data annotaions to change CASE of values

class DemoUser
{
[TitleCase]
public string FirstName { get; set; }
[TitleCase]
public string LastName { get; set; }
[UpperCase]
public string Salutation { get; set; }
[LowerCase]
public string Email { get; set; }
}
Suppose i have demo-class as written above, i want to create some custom annotations like LowerCase,UpperCase etc so that its value gets converted automatically. Doing this will enable me to use these annotations in other classes too.
As Ladislav implied, this is two questions in one.
Assuming you follow the recipe for creating attributes in Jefim's link, and assuming you're calling those created attribute classes "UpperCaseAttribute", "LowerCaseAttribute", and "TitleCaseAttribute", the following SaveChanges() override should work in EF 4.3 (the current version as of the time of this answer post).
public override int SaveChanges()
{
IEnumerable<DbEntityEntry> changedEntities = ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added || e.State == System.Data.EntityState.Modified);
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;
changedEntities.ToList().ForEach(entry =>
{
var properties = from attributedProperty in entry.Entity.GetType().GetProperties()
where attributedProperty.PropertyType == typeof (string)
select new { entry, attributedProperty,
attributes = attributedProperty.GetCustomAttributes(true)
.Where(attribute => attribute is UpperCaseAttribute || attribute is LowerCaseAttribute || attribute is TitleCaseAttribute)
};
properties = properties.Where(p => p.attributes.Count() > 1);
properties.ToList().ForEach(p =>
{
p.attributes.ToList().ForEach(att =>
{
if (att is UpperCaseAttribute)
{
p.entry.CurrentValues[p.attributedProperty.Name] = textInfo.ToUpper(((string)p.entry.CurrentValues[p.attributedProperty.Name]));
}
if (att is LowerCaseAttribute)
{
p.entry.CurrentValues[p.attributedProperty.Name] = textInfo.ToLower(((string)p.entry.CurrentValues[p.attributedProperty.Name]));
}
if (att is TitleCaseAttribute)
{
p.entry.CurrentValues[p.attributedProperty.Name] = textInfo.ToTitleCase(((string)p.entry.CurrentValues[p.attributedProperty.Name]));
}
});
});
});
return base.SaveChanges();
}
You can override the SaveChanges method in your EF context (if you use default code-generation just write a partial class). Something like the following:
public partial class MyEntityContext
{
public override int SaveChanges(SaveOptions options)
{
IEnumerable<ObjectStateEntry> changedEntities =
this.ObjectStateManager.GetObjectStateEntries(
System.Data.EntityState.Added | System.Data.EntityState.Modified);
// here you can loop over your added/changed entities and
// process the custom attributes that you have
return base.SaveChanges(options);
}
}