用c#开发微信 (11) 微统计 - 阅读分享统计系统 1 基础架构搭建

时间:2021-09-17 17:11:37

微信平台自带的统计功能太简单,有时我们需要统计有哪些微信个人用户阅读、分享了微信公众号的手机网页,以及微信个人用户访问手机网页的来源:朋友圈分享访问、好友分享消息访问等。本系统实现了手机网页阅读、分享与来源统计及手机网页在朋友圈的传播路径分析。

本系统使用最传统的三层架构。本文是微统计的第一篇,主要介绍如下内容:

1. 数据库设计

2. 数据访问框架层及数据实体层

3. 数据访问层

4. 视图实体定义层

 

下面是详细实现步骤:

1. 数据库设计

阅读记录 PageNav

分享记录 PageShare

 

 

CREATE TABLE [dbo].[PageShare](

    [Id] [uniqueidentifier] NOT NULL,

    [Url] [nvarchar](4000) NOT NULL,

    [From] [smallint] NOT NULL,

    [ShareOpenId] [nvarchar](4000) NOT NULL,

    [ParentShareOpenId] [nvarchar](4000) NOT NULL,

    [ShareTime] [datetime] NOT NULL,

 CONSTRAINT [PK_PageShare] PRIMARY KEY CLUSTERED 

(

    [Id] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

) ON [PRIMARY]

 

GO

 

 

CREATE TABLE [dbo].[PageNav](

    [Id] [uniqueidentifier] NOT NULL,

    [Url] [nvarchar](4000) NOT NULL,

    [From] [smallint] NOT NULL,

    [NavOpenId] [nvarchar](4000) NOT NULL,

    [ShareOpenId] [nvarchar](4000) NOT NULL,

    [VisitTime] [datetime] NOT NULL,

 CONSTRAINT [PK_PageNav] PRIMARY KEY CLUSTERED 

(

    [Id] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

) ON [PRIMARY]

 

GO

 

 

 

 

2. 数据访问框架层及数据实体定义层 (ORM )

使用Entity Framework来实现DAL层与数据库交互的数据传输,EF已包含与数据库一致的数据实体。

添加一个ADO.NET Entity Data Model的item,连接到刚才建立的数据库,我这里用的是EF 6

添加完成后如下图:

用c#开发微信 (11) 微统计 - 阅读分享统计系统 1 基础架构搭建

 

 

3. 数据访问层(DAL)

通过调用ORM,实现数据持久化。

1)  添加一个增删改查的一个通用基类GenericRepository:
using System;

using System.Collections.Generic;

using System.Data.Entity;

using System.Data.Entity.Validation;

using System.Linq;

using System.Linq.Expressions;

using System.Web;

public class GenericRepository<TContext, TEntity> : IDisposable

        where TContext : DbContext, new()

    where TEntity : class

{

    protected TContext _context;

    protected readonly IDbSet<TEntity> _set;

 

    public GenericRepository()

    {

        _context = new TContext();

        _set = _context.Set<TEntity>();

    }

 

    protected TContext Context

    {

        get

        {

            return _context;

        }

    }

 

    protected IDbSet<TEntity> DbSet

    {

        get

        {

            return _set == null ? _context.Set<TEntity>() : _set;

        }

    }

 

    public IQueryable<TEntity> GetAll()

    {

        return DbSet.AsQueryable();

    }

 

    /// <summary>

    /// 分页查询

    /// </summary>

    /// <typeparam name="TEntity"></typeparam>      

    /// <param name="funWheres">条件表达式u => u.OpenId == userInfo.OpenId</param>

    /// <param name="sortExpress">排序属性名称</param>

    /// <param name="isSortAsc">是否升序</param>

    /// <param name="pageSize">每页大小</param>

    /// <param name="pageIndex">当前页码,以0开始</param>

    /// <param name="totalCount">返回查询总数量</param>

    /// <returns></returns>

    public IQueryable<TEntity> GetPaged(ref int totalCount, List<Expression<Func<TEntity, bool>>> funWheres, string sortPropertyName = "", bool isSortAsc = true, int pageIndex = 0, int pageSize = 20)

    {

        IQueryable<TEntity> query = DbSet;

        foreach (var funWhere in funWheres)

        {

            query = query.Where(funWhere);

        }

        query = query.Distinct();

        totalCount = query.Count();

 

        //order

        if (!string.IsNullOrEmpty(sortPropertyName))

        {

            query = query.OrderBy(sortPropertyName, isSortAsc);

            query = query.Skip(pageSize * pageIndex);

        }

 

        query = query.Take(pageSize);

        return query;

    }

 

    /// <summary>

    /// 

    /// </summary>

    /// <param name="predicate">条件表达式 u => u.OpenId == userInfo.OpenId</param>

    /// <returns></returns>

    public IQueryable<TEntity> GetByPredicate(Expression<Func<TEntity, bool>> predicate)

    {

        return DbSet.Where(predicate).AsQueryable<TEntity>();

    }

 

    /// <summary>

    /// 

    /// </summary>

    /// <param name="predicate">条件表达式 u => u.OpenId == userInfo.OpenId</param>

    /// <returns></returns>

    public bool Contains(Expression<Func<TEntity, bool>> predicate)

    {

        return DbSet.Count(predicate) > 0; ;

    }

 

    /// <summary>

    /// 

    /// </summary>

    /// <param name="keys">可用于联合主健 1, "Michael"</param>

    /// <returns></returns>

    public TEntity GetSingleByKeys(params object[] keys)

    {

        return DbSet.Find(keys);

    }

 

    /// <summary>

    /// 

    /// </summary>

    /// <param name="predicate">条件表达式 u => u.OpenId == userInfo.OpenId</param>

    /// <returns></returns>

    public TEntity GetSingleByPredicate(Expression<Func<TEntity, bool>> predicate)

    {

        return DbSet.FirstOrDefault(predicate);

    }

 

    public bool Insert(TEntity t)

    {

        var entity = DbSet.Add(t);

        return Save() > 0;

    }

 

    public TEntity InsertAndReturn(TEntity t)

    {

        var ent = DbSet.Add(t);

        Save();

        return ent;

    }

 

    public bool Delete(TEntity t)

    {

        if (Context.Entry(t).State == EntityState.Detached)

        {

            DbSet.Attach(t);

        }

        DbSet.Remove(t);

        return Save() > 0;

    }

 

    public bool Delete(Expression<Func<TEntity, bool>> predicate)

    {

        var toDelete = GetByPredicate(predicate);

        foreach (var obj in toDelete)

        {

            DbSet.Remove(obj);

        }

        return Save() > 0;

    }

 

    public bool Update(TEntity t)

    {

        var entry = Context.Entry(t);

        DbSet.Attach(t);

        entry.State = EntityState.Modified;

        return Save() > 0;

    }

 

    public int Save()

    {

        return Context.SaveChanges();

    }

 

    public int Count

    {

        get { return DbSet.Count(); }

    }

 

    public IQueryable<TEntity> GetWithRawSql(string query, params object[] parameters)

    {

        return Context.Database.SqlQuery<TEntity>(query, parameters).AsQueryable();

    }

 

    public void Dispose()

    {

        if (Context != null)

            Context.Dispose();

        GC.SuppressFinalize(this);

    }

}

 

public static class MyExtend

{

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> queryable, string propertyName, bool isSortAsc)

    {

        Expression param = Expression.Parameter(typeof(T));

        var properties = propertyName.Split('.');

        var body = param;

        //支持"User.Age"这种参数  p=>p.User.Age

        foreach (var p in properties)

        {

            body = Expression.Property(body, p);

        }

        dynamic keySelector = Expression.Lambda(body, param as ParameterExpression);

        return isSortAsc ? Queryable.OrderBy(queryable, keySelector) : Queryable.OrderByDescending(queryable, keySelector);

    }

}

 
2) 添加一个Text Template的item, 并写入下面的内容:
<#@ template language="C#" debug="false" hostspecific="true"#>

<#@ include file="EF6.Utility.CS.ttinclude"#><#@ 

 output extension=".cs"#><#

 

 

 

const string inputFile = @"..\ORM\Statistics.edmx";

var usingEntity = "using Statistics.ORM;";

 

var className = "Dal";

 

 

var textTransform = DynamicTextTransformation.Create(this);

var code = new CodeGenerationTools(this);

var ef = new MetadataTools(this);

var typeMapper = new TypeMapper(code, ef, textTransform.Errors);

var    fileManager = EntityFrameworkTemplateFileManager.Create(this);

var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);

var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);

var container = itemCollection.OfType<EntityContainer>().FirstOrDefault();

if (container == null)

{

    return string.Empty;

}

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))

{

    return string.Empty;

}

 

WriteHeader(codeStringGenerator, fileManager);

 

foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))

{

    fileManager.StartNewFile(entity.Name + className + ".cs");

    BeginNamespace(code);

#>

 

<#=usingEntity#>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

<#=codeStringGenerator.EntityClassOpening(entity)#><#=className#>: GenericRepository<<#=code.Escape(container)#>,<#=entity.Name #>>

{

 

}

<#

    EndNamespace(code);

}

 

foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection))

{

    fileManager.StartNewFile(complex.Name + ".cs");

    BeginNamespace(code);

#>

<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>

{

<#

    var complexProperties = typeMapper.GetComplexProperties(complex);

    var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex);

 

    if (propertiesWithDefaultValues.Any() || complexProperties.Any())

    {

#>

    public <#=code.Escape(complex)#>()

    {

<#

        foreach (var edmProperty in propertiesWithDefaultValues)

        {

#>

        this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;

<#

        }

 

        foreach (var complexProperty in complexProperties)

        {

#>

        this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();

<#

        }

#>

    }

 

<#

    }

 

    var simpleProperties = typeMapper.GetSimpleProperties(complex);

    if (simpleProperties.Any())

    {

        foreach(var edmProperty in simpleProperties)

        {

#>

    <#=codeStringGenerator.Property(edmProperty)#>

<#

        }

    }

 

    if (complexProperties.Any())

    {

#>

 

<#

        foreach(var edmProperty in complexProperties)

        {

#>

    <#=codeStringGenerator.Property(edmProperty)#>

<#

        }

    }

#>

}

<#

    EndNamespace(code);

}

 

foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection))

{

    fileManager.StartNewFile(enumType.Name + ".cs");

    BeginNamespace(code);

#>

<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>

<#

    if (typeMapper.EnumIsFlags(enumType))

    {

#>

[Flags]

<#

    }

#>

<#=codeStringGenerator.EnumOpening(enumType)#>

{

<#

    var foundOne = false;

    

    foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType))

    {

        foundOne = true;

#>

    <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>,

<#

    }

 

    if (foundOne)

    {

        this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1);

    }

#>

}

<#

    EndNamespace(code);

}

 

fileManager.Process();

 

#>

<#+

 

public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager)

{

    fileManager.StartHeader();

#>

//------------------------------------------------------------------------------

// <auto-generated>

// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#>

//

// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#>

// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#>

// </auto-generated>

//------------------------------------------------------------------------------

<#=codeStringGenerator.UsingDirectives(inHeader: true)#>

<#+

    fileManager.EndBlock();

}

 

public void BeginNamespace(CodeGenerationTools code)

{

    var codeNamespace = code.VsNamespaceSuggestion();

    if (!String.IsNullOrEmpty(codeNamespace))

    {

#>

namespace <#=code.EscapeNamespace(codeNamespace)#>

{

<#+

        PushIndent("    ");

    }

}

 

public void EndNamespace(CodeGenerationTools code)

{

    if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion()))

    {

        PopIndent();

#>

}

<#+

    }

}

 

public const string TemplateId = "CSharp_DbContext_Types_EF6";

 

public class CodeStringGenerator

{

    private readonly CodeGenerationTools _code;

    private readonly TypeMapper _typeMapper;

    private readonly MetadataTools _ef;

 

    public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef)

    {

        ArgumentNotNull(code, "code");

        ArgumentNotNull(typeMapper, "typeMapper");

        ArgumentNotNull(ef, "ef");

 

        _code = code;

        _typeMapper = typeMapper;

        _ef = ef;

    }

 

    public string Property(EdmProperty edmProperty)

    {

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} {1} {2} {{ {3}get; {4}set; }}",

            Accessibility.ForProperty(edmProperty),

            _typeMapper.GetTypeName(edmProperty.TypeUsage),

            _code.Escape(edmProperty),

            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),

            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));

    }

 

    public string NavigationProperty(NavigationProperty navProp)

    {

        var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} {1} {2} {{ {3}get; {4}set; }}",

            AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),

            navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,

            _code.Escape(navProp),

            _code.SpaceAfter(Accessibility.ForGetter(navProp)),

            _code.SpaceAfter(Accessibility.ForSetter(navProp)));

    }

    

    public string AccessibilityAndVirtual(string accessibility)

    {

        return accessibility + (accessibility != "private" ? " virtual" : "");

    }

    

    public string EntityClassOpening(EntityType entity)

    {

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} {1}partial class {2}{3}",

            Accessibility.ForType(entity),

            _code.SpaceAfter(_code.AbstractOption(entity)),

            _code.Escape(entity),

            _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));

    }

    

    public string EnumOpening(SimpleType enumType)

    {

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} enum {1} : {2}",

            Accessibility.ForType(enumType),

            _code.Escape(enumType),

            _code.Escape(_typeMapper.UnderlyingClrType(enumType)));

        }

    

    public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter)

    {

        var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);

        foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))

        {

            var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";

            var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";

            var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))";

            writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit);

        }

    }

    

    public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace)

    {

        var parameters = _typeMapper.GetParameters(edmFunction);

        

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} IQueryable<{1}> {2}({3})",

            AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),

            _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),

            _code.Escape(edmFunction),

            string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()));

    }

    

    public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace)

    {

        var parameters = _typeMapper.GetParameters(edmFunction);

        

        return string.Format(

            CultureInfo.InvariantCulture,

            "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});",

            _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),

            edmFunction.NamespaceName,

            edmFunction.Name,

            string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()),

            _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())));

    }

    

    public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)

    {

        var parameters = _typeMapper.GetParameters(edmFunction);

        var returnType = _typeMapper.GetReturnType(edmFunction);

 

        var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray());

        if (includeMergeOption)

        {

            paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption";

        }

 

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} {1} {2}({3})",

            AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),

            returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",

            _code.Escape(edmFunction),

            paramList);

    }

    

    public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)

    {

        var parameters = _typeMapper.GetParameters(edmFunction);

        var returnType = _typeMapper.GetReturnType(edmFunction);

 

        var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));

        if (includeMergeOption)

        {

            callParams = ", mergeOption" + callParams;

        }

        

        return string.Format(

            CultureInfo.InvariantCulture,

            "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});",

            returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",

            edmFunction.Name,

            callParams);

    }

    

    public string DbSet(EntitySet entitySet)

    {

        return string.Format(

            CultureInfo.InvariantCulture,

            "{0} virtual DbSet<{1}> {2} {{ get; set; }}",

            Accessibility.ForReadOnlyProperty(entitySet),

            _typeMapper.GetTypeName(entitySet.ElementType),

            _code.Escape(entitySet));

    }

 

    public string UsingDirectives(bool inHeader, bool includeCollections = true)

    {

        return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())

            ? string.Format(

                CultureInfo.InvariantCulture,

                "{0}using System;{3}using System.Runtime.Serialization;{1}" +

                "{2}",

                inHeader ? Environment.NewLine : "",

                includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",

                inHeader ? "" : Environment.NewLine, Environment.NewLine)

            : "";

    }

 

    public string WriteEntityTypeSerializationInfo(EntityType type)

    {

        StringBuilder output = new StringBuilder();

        output.AppendLine("[DataContract(IsReference = true)]");

 

        List<String> typeList = new List<String>();

        var complexProperties = _typeMapper.GetComplexProperties(type);

        foreach(var complexProperty in complexProperties)

        {

            typeList.Add(_code.Escape(complexProperty));

        }

 

        var navigationProperties = _typeMapper.GetNavigationProperties(type);

        foreach (var navigationProperty in navigationProperties)

        {

            typeList.Add(_code.Escape(_typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())));

        }

 

        foreach(String sItem in typeList.Distinct())

        {

            output.AppendFormat("[KnownType(typeof({0}))]{1}", sItem, Environment.NewLine);

        }

 

        return output.ToString();

    }

}

 

public class TypeMapper

{

    private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName";

 

    private readonly System.Collections.IList _errors;

    private readonly CodeGenerationTools _code;

    private readonly MetadataTools _ef;

 

    public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors)

    {

        ArgumentNotNull(code, "code");

        ArgumentNotNull(ef, "ef");

        ArgumentNotNull(errors, "errors");

 

        _code = code;

        _ef = ef;

        _errors = errors;

    }

 

    public static string FixNamespaces(string typeName)

    {

        return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial.");

    }

 

    public string GetTypeName(TypeUsage typeUsage)

    {

        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);

    }

 

    public string GetTypeName(EdmType edmType)

    {

        return GetTypeName(edmType, isNullable: null, modelNamespace: null);

    }

 

    public string GetTypeName(TypeUsage typeUsage, string modelNamespace)

    {

        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);

    }

 

    public string GetTypeName(EdmType edmType, string modelNamespace)

    {

        return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);

    }

 

    public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace)

    {

        if (edmType == null)

        {

            return null;

        }

 

        var collectionType = edmType as CollectionType;

        if (collectionType != null)

        {

            return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace));

        }

 

        var typeName = _code.Escape(edmType.MetadataProperties

                                .Where(p => p.Name == ExternalTypeNameAttributeName)

                                .Select(p => (string)p.Value)

                                .FirstOrDefault())

            ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ?

                _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) :

                _code.Escape(edmType));

 

        if (edmType is StructuralType)

        {

            return typeName;

        }

 

        if (edmType is SimpleType)

        {

            var clrType = UnderlyingClrType(edmType);

            if (!IsEnumType(edmType))

            {

                typeName = _code.Escape(clrType);

            }

 

            typeName = FixNamespaces(typeName);

 

            return clrType.IsValueType && isNullable == true ?

                String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) :

                typeName;

        }

 

        throw new ArgumentException("edmType");

    }

    

    public Type UnderlyingClrType(EdmType edmType)

    {

        ArgumentNotNull(edmType, "edmType");

 

        var primitiveType = edmType as PrimitiveType;

        if (primitiveType != null)

        {

            return primitiveType.ClrEquivalentType;

        }

 

        if (IsEnumType(edmType))

        {

            return GetEnumUnderlyingType(edmType).ClrEquivalentType;

        }

 

        return typeof(object);

    }

    

    public object GetEnumMemberValue(MetadataItem enumMember)

    {

        ArgumentNotNull(enumMember, "enumMember");

        

        var valueProperty = enumMember.GetType().GetProperty("Value");

        return valueProperty == null ? null : valueProperty.GetValue(enumMember, null);

    }

    

    public string GetEnumMemberName(MetadataItem enumMember)

    {

        ArgumentNotNull(enumMember, "enumMember");

        

        var nameProperty = enumMember.GetType().GetProperty("Name");

        return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null);

    }

 

    public System.Collections.IEnumerable GetEnumMembers(EdmType enumType)

    {

        ArgumentNotNull(enumType, "enumType");

 

        var membersProperty = enumType.GetType().GetProperty("Members");

        return membersProperty != null 

            ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null)

            : Enumerable.Empty<MetadataItem>();

    }

    

    public bool EnumIsFlags(EdmType enumType)

    {

        ArgumentNotNull(enumType, "enumType");

        

        var isFlagsProperty = enumType.GetType().GetProperty("IsFlags");

        return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null);

    }

 

    public bool IsEnumType(GlobalItem edmType)

    {

        ArgumentNotNull(edmType, "edmType");

 

        return edmType.GetType().Name == "EnumType";

    }

 

    public PrimitiveType GetEnumUnderlyingType(EdmType enumType)

    {

        ArgumentNotNull(enumType, "enumType");

 

        return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null);

    }

 

    public string CreateLiteral(object value)

    {

        if (value == null || value.GetType() != typeof(TimeSpan))

        {

            return _code.CreateLiteral(value);

        }

 

        return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks);

    }

    

    public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)

    {

        ArgumentNotNull(types, "types");

        ArgumentNotNull(sourceFile, "sourceFile");

        

        var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);

        if (types.Any(item => !hash.Add(item)))

        {

            _errors.Add(

                new CompilerError(sourceFile, -1, -1, "6023",

                    String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict"))));

            return false;

        }

        return true;

    }

 

    public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection)

    {

        return GetItemsToGenerate<SimpleType>(itemCollection)

            .Where(e => IsEnumType(e));

    }

    

    public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType

    {

        return itemCollection

            .OfType<T>()

            .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName))

            .OrderBy(i => i.Name);

    }

 

    public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection)

    {

        return itemCollection

            .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i))

            .Select(g => GetGlobalItemName(g));

    }

 

    public string GetGlobalItemName(GlobalItem item)

    {

        if (item is EdmType)

        {

            return ((EdmType)item).Name;

        }

        else

        {

            return ((EntityContainer)item).Name;

        }

    }

 

    public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type)

    {

        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);

    }

    

    public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type)

    {

        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);

    }

    

    public IEnumerable<EdmProperty> GetComplexProperties(EntityType type)

    {

        return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);

    }

    

    public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type)

    {

        return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);

    }

 

    public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type)

    {

        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);

    }

    

    public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type)

    {

        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);

    }

 

    public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type)

    {

        return type.NavigationProperties.Where(np => np.DeclaringType == type);

    }

    

    public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type)

    {

        return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);

    }

    

    public FunctionParameter GetReturnParameter(EdmFunction edmFunction)

    {

        ArgumentNotNull(edmFunction, "edmFunction");

 

        var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters");

        return returnParamsProperty == null

            ? edmFunction.ReturnParameter

            : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault();

    }

 

    public bool IsComposable(EdmFunction edmFunction)

    {

        ArgumentNotNull(edmFunction, "edmFunction");

 

        var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute");

        return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null);

    }

 

    public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction)

    {

        return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);

    }

 

    public TypeUsage GetReturnType(EdmFunction edmFunction)

    {

        var returnParam = GetReturnParameter(edmFunction);

        return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage);

    }

    

    public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption)

    {

        var returnType = GetReturnType(edmFunction);

        return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;

    }

}

 

public static void ArgumentNotNull<T>(T arg, string name) where T : class

{

    if (arg == null)

    {

        throw new ArgumentNullException(name);

    }

}

#>

 

这里要注意的是这二行:

const string inputFile = @"..\ORM\Statistics.edmx";

var usingEntity = "using Statistics.ORM;";

第一行是上面建的ORM, 第二行是ORM里的namespace.

 

指定正确path的edmax后,会自动生成各个继承基类GenericRepository的DAL类

最后DAL结构如下:

用c#开发微信 (11) 微统计 - 阅读分享统计系统 1 基础架构搭建

 

4. 视图实体定义层(ViewEntities)

主要用于用户界面交互的数据实体,以及数据实体与视图实体的转换方法,实现数据库与用户界面交互的隔离。

1) 定义一个接口IViewModel,来规定数据实体与视图实体的转换方法:
/// <summary>

    /// 数据实体与视图实体的转换

    /// </summary>

    /// <typeparam name="TV">数据实体对应的视图实体类型</typeparam>

    /// <typeparam name="TD">数据实体类型</typeparam>

    public interface IViewModel<TV, TD>

    {

        /// <summary>

        /// 数据实体转换为视图实体

        /// </summary>

        /// <param name="entity">数据实体</param>

        /// <returns>视图实体</returns>

        TV GetViewModel(TD entity);

        /// <summary>

        /// 视图实体转换为数据实体

        /// </summary>

        /// <param name="entity">视图实体</param>

        /// <returns>数据实体</returns>

        TD GetDataEntity(TV entity);

    }

}

 

2)定义枚举类型Enum:
/// <summary>

    /// 访问来源类型

    /// </summary>

    public enum NavFrom

    {

        /// <summary>

        /// 微信朋友圈

        /// </summary>

        Timeline,

        /// <summary>

        /// 微信群

        /// </summary>

        GroupMessage,

        /// <summary>

        /// 微信好友发送的链接

        /// </summary>

        SingleMessage,

        /// <summary>

        /// 直接在微信公众号中打开微信浏览器

        /// </summary>

        MicroMessenger,

        /// <summary>

        /// 其他(不是在微信中访问)

        /// </summary>

        Other

    }

 

    /// <summary>

    /// 分享类型

    /// </summary>

    public enum ShareType

    {

        /// <summary>

        /// 分享给好友

        /// </summary>

        Friend,

        /// <summary>

        /// 分享到朋友圈

        /// </summary>

        Timeline,

        /// <summary>

        /// 未知

        /// </summary>

        Unknown

    }

 

3) 定义每个数据实体对应的视图实体,并实现IViewModel接口的转换方法:

实体数据的转换这里使用了开源的Object-Object Mapping工具AutoMapper

  • PageNavEntity:
[Serializable]

   public class PageNavEntity:IViewModel<PageNavEntity,PageNav>

   {

       static PageNavEntity()

       {

           AutoMapper.Mapper.CreateMap<PageNav, PageNavEntity>().ForMember(e=>e.From, d=>d.MapFrom(n=>(NavFrom)n.From));

           AutoMapper.Mapper.CreateMap<PageNavEntity, PageNav>().ForMember(e => e.From, d => d.MapFrom(n => (short)n.From)); ;

       }

 

       [Key]

       public System.Guid Id { get; set; }

       /// <summary>

       /// 页面地址

       /// </summary>

       public string Url { get; set; }

 

       /// <summary>

       /// 访问来源

       /// </summary>

       public NavFrom From { get; set; }

 

       /// <summary>

       /// 访问者微信openid

       /// </summary>

       public string NavOpenId { get; set; }

 

       /// <summary>

       /// 当访问来源为朋友圈时的分享者微信openid

       /// </summary>

       public string ShareOpenId { get; set; }

 

       /// <summary>

       /// 访问时间

       /// </summary>

       public DateTime VisitTime { get; set; }

 

       public PageNavEntity GetViewModel(PageNav entity)

       {

           return AutoMapper.Mapper.Map<PageNav, PageNavEntity>(entity);

       }

 

       public PageNav GetDataEntity(PageNavEntity entity)

       {

           return AutoMapper.Mapper.Map<PageNavEntity, PageNav>(entity);

       }

   }

 

  • PageShareEntity:
[Serializable]

   public class PageShareEntity : IViewModel<PageShareEntity, PageShare>

   {

       static PageShareEntity()

       {

           AutoMapper.Mapper.CreateMap<PageShare, PageShareEntity>().ForMember(e => e.From, d => d.MapFrom(n => (ShareType)n.From));

           AutoMapper.Mapper.CreateMap<PageShareEntity, PageShare>().ForMember(e => e.From, d => d.MapFrom(n => (short)n.From));

       }

 

       [Key]

       public System.Guid Id { get; set; }

 

       /// <summary>

       /// 页面地址

       /// </summary>

       public string Url { get; set; }

 

       /// <summary>

       /// 分享类型

       /// </summary>

       public ShareType From { get; set; }

 

       /// <summary>

       /// 分享者微信openid

       /// </summary>

       public string ShareOpenId { get; set; }

 

       /// <summary>

       /// 上一级分享者微信openid

       /// </summary>

       public string ParentShareOpenId { get; set; }

 

       /// <summary>

       /// 分享时间

       /// </summary>

       public DateTime ShareTime { get; set; }

 

       public PageShareEntity GetViewModel(PageShare entity)

       {

           return AutoMapper.Mapper.Map<PageShare, PageShareEntity>(entity);

       }

 

       public PageShare GetDataEntity(PageShareEntity entity)

       {

           return AutoMapper.Mapper.Map<PageShareEntity, PageShare>(entity);

       }

   }

 

至于里面有些字段要转换一下,《用c#开发微信 (6) 微渠道 - 推广渠道管理系统 1 基础架构搭建》这里已经介绍过了,这里就不详细介绍了。

 
4) 定义用于前端图标显示的ChartData类
/// <summary>

    /// HighChart统计图表数据

    /// </summary>

    public class ChartData

    {

        /// <summary>

        /// 1小时间隔秒数

        /// </summary>

        private const decimal HourPointInterval = 3600 * 1000;

        /// <summary>

        /// 图表起始年

        /// </summary>

        public decimal StartYear { get; set; }

        /// <summary>

        /// 图表起始月

        /// </summary>

        public decimal StartMonth { get; set; }

        /// <summary>

        /// 图表起始日

        /// </summary>

        public decimal StartDay { get; set; }

        /// <summary>

        /// 线间隔

        /// </summary>

        public decimal LineInterval

        {

            get

            {

                int pointCount = Statistics.Length;

                return (pointCount % 8 == 0 ? (pointCount / 8) : (pointCount / 8 + 1)) * 3600 * 1000;

            }

        }

        /// <summary>

        /// 点间隔

        /// </summary>

        public decimal PointInterval

        {

            get

            {

                return HourPointInterval;

            }

        }

        /// <summary>

        /// 点数据集合

        /// </summary>

        public decimal[] Statistics { get; set; }

    }

 

最后视图类结构如下:

用c#开发微信 (11) 微统计 - 阅读分享统计系统 1 基础架构搭建

 

未完待续!!!

 

用c#开发微信 系列汇总